`

Java中的垃圾回收与对象生命周期

阅读更多
    在<<Java编程思想>>书中,作者对垃圾回收的精要概述:"基于系统平台自适应的停止-复制"垃圾回收技术和"sun早期自适应的标记-清扫"垃圾回收技术。当然,对一个系统来说,初始化和清理是系统整个运行过程中大两大核心"进程",我觉得"初始化"和"清理"已经称为一种专业术语,不管是小段代码,一个大程序,一个系统,甚至是软件架构,在其本身构建和运行过程中,垃圾回收如同模拟现实世界场景一样,已称为一种模式,一种"行为"或是对象的"习惯"。

    模拟现实场景来说,人是如何清理垃圾的,被丢弃的废纸,废品... 人会有专门的垃圾清理站或是保洁公司来处理这些“已耗尽”的能量,使其在土壤中循环再造。然而,这些垃圾也可以看做是"现实对象"。然而要模拟到用计算机实现这种垃圾回收系统,定是很有趣的事情,或许你早已在脑海中构建一副用计算机实现的垃圾回收场景,可那画面的内幕还是需要细细设计。于是有兴趣参考优化编程,java垃圾回收有向图等资料,简单地整理了一些基本的概念,分享之。若是很了解垃圾回收内幕的高手或是大师,还望你指点,我是这方面的新手,还在门前虚心求教和批评指正。

1. 垃圾回收

   垃圾回收是Java程序设计中内存管理的核心概念,JVM的内存管理机制被称为垃圾回收机制。

  一个对象创建后被放置在JVM的堆内存中,当永远不再引用这个对象时,它将被JVM在堆内存中回收。被创建的对象不能再生,同时也没有办法通过程序语句释放它们。即当对象在JVM运行空间中无法通过根集合到达(找到)时,这个对象被称为垃圾对象。根集合是由类中的静态引用域与本地引用域组成的。JVM通过根集合索引对象。

    在做Java应用开发时经常会用到由JVM管理的两种类型的内存:堆内存和栈内存。简单来讲,堆内存主要用来存储程序在运行时创建或实例化的对象与变量。例如通过new关键字创建的对象。而栈内存则是用来存储程序代码中声明为静态或非静态的方法。


(1) 堆内存

    堆内存在JVM启动的时候被创建,堆内存中所存储的对象可以被JVM自动回收,不能通过其他外部手段回收,也就是说开发人员无法通过添加相关代码的手段来回收堆内存中的对象。堆内存通常情况下被分为两个区域:新对象区域与老对象区域。

    新对象区域:又可细分为三个小区域:伊甸园区域、From区域与To区域。伊甸园区域用来保存新创建的对象,它就像一个堆栈,新的对象被创建,就像指向该栈的指针在增长一样,当伊甸园区域中的对象满了之后,JVM系统将要做到可达性测试,主要任务是检测有哪些对象由根集合出发是不可达的,这些对象就可以被JVM回收,并且将所有的活动对象从伊甸园区域拷贝到To区域,此时一些对象将发生状态交换,有的对象就从To区域被转移到From区域,此时From区域就有了对象。上面对象迁移的整个过程,都是由JVM控制完成的。

    老对象区域:在老对象区域中的对象仍然会有一个较长的生命周期,大多数的JVM系统垃圾对象,都是源于"短命"对象,经过一段时间后,被转入老对象区域的对象,就变成了垃圾对象。此时,它们都被打上相应的标记,JVM系统将会自动回收这些垃圾对象,建议不要频繁地强制系统作垃圾回收,这是因为JVM会利用有限的系统资源,优先完成垃圾回收工作,导致应用无法快速地响应来自用户端的请求,这样会影响系统的整体性能。


(2) 栈内存

    堆内存主要用来存储程序在运行时创建或实例化的对象与变量。例如通过new关键字创建的对象。而栈内存则是用来存储程序代码中声明为静态或非静态的方法。

2. JVM中对象的生命周期

   在JVM运行空间中,对象的整个生命周期大致可以分为7个阶段:
   创建阶段;
   应用阶段;
   不可视阶段;
   不可到达阶段;
   可收集阶段;
   终结阶段;
   释放阶段
 

   上面这7个阶段,构成了JVM中对象的完整的生命周期。


   (1) 创建阶段

       在对象的创建阶段,系统主要通过下面的步骤,完成对象的创建过程:
    
       <1> 为对象分配存储空间;
       <2> 开始构造对象;
       <3> 从超类到子类对static成员进行初始化;
       <4> 超类成员变量按顺序初始化,递归调用超类的构造方法;
       <5> 子类成员变量按顺序初始化,子类构造方法调用。

       在创建对象时应注意几个关键应用规则:
      
       <1> 避免在循环体中创建对象,即使该对象占用内存空间不大。
       <2> 尽量及时使对象符合垃圾回收标准。比如 myObject = null。
       <3> 不要采用过深的继承层次。
       <4> 访问本地变量优于访问类中的变量。


      
比如:
       for (int i = 0; i < 10000; i++) {
             Object obj = new Object();
             System.out.println(obj);
       }

       和
       Object obj = null;
       for (int i = 0; i < 10000; i++) {
             obj = new Object();
             System.out.println(obj);
       }


       这里修正一下,在绝对情况下,效率还是上面的要好, 因为后面的多了一条初始化语句。
        而且各自作用域不同。 谢谢各位~~

   (2) 应用阶段

       在对象的引用阶段,对象具备如下特征:

      <1> 系统至少维护着对象的一个强引用(Strong Reference);
      <2> 所有对该对象的引用全部是强引用(除非我们显示地适用了:软引用(Soft Reference)、弱引用(Weak Reference)或虚引用(Phantom Reference)).


      强引用(Strong Reference):是指JVM内存管理器从根引用集合出发遍历堆中所有到达对象的路径。当到达某对象的任意路径都不含有引用对象时,这个对象的引用就被称为强引用。

       软引用(Soft Reference):软引用的主要特点是有较强的引用功能。只有当内存不够的时候,才回收这类内存,因此内存足够时它们通常不被回收。另外这些引用对象还能保证在Java抛出OutOfMemory异常之前,被设置为null。它可以用于实现一些常用资源的缓存,实现Cache功能,保证最大限度地使用内存你而不引起OutOfMemory。

                              
下面是软引用的实现代码:

                                import java.lang.ref.SoftReference;
                                ...
                                
                                A a = new A();
                                ...

                                // 使用a
                                ...
                                 
                                // 使用完了a, 将它设置为soft引用类型,并且释放强引用
                                SoftReference sr = new SoftReference(a);
                                a = null;
                                ...

                                // 下次使用时
			        if (sr != null) {
 				    a = sr.get();
				} else {
				    // GC由于低内存,已释放a,因此需要重新装载
                                    a = new A();
				    sr = new SoftReference(a);
				}


                                软引用技术的引进使Java应用可以更好地管理内存,稳定系统,防止系统内存溢出,避免系统崩溃。因此在处理一些占用内存较大且生命周期较长,但使用并不繁地对象时应尽量应用该技术。提高系统稳定性。
            
                                     
       弱引用(Weak Reference):弱应用对象与软引用对象的最大不同就在于:GC在进行垃圾回收时,需要通过算法检查是否回收Soft应用对象,而对于Weak引用,GC总是进行回收。Weak引用对象更容易、更快地被GC回收。Weak引用对象常常用于Map结构中。
                             
 下面是弱引用的实现代码:

                                import java.lang.ref.WeakReference;
                                ...
                                
                                A a = new A();
                                ...

                                // 使用a
                                ...
                                 
                                // 使用完了a, 将它设置为Weak引用类型,并且释放强引用
                                WeakReference wr = new WeakReference(a);
                                a = null;
                                ...

                                // 下次使用时
			        if (wr != null) {
 				    a = wr.get();
				} else {
                                    a = new A();
				    wr = new WeakReference(a);
				}
   
  
虚引用(Phantom Reference): 虚引用的用途较少,主要用于辅助finalize函数的使用。

虚引用(Phantom Reference)对象指一些执行完了finalize函数,并为不可达对象,但是还没有被GC回收的对象。这种对象可以辅助finalize进行一些后期的回收工作,我们通过覆盖了Refernce的clear()方法,增强资源回收机制的灵活性。

      
       在实际程序设计中一般很少使用弱引用和虚引用,是用软引用的情况较多,因为软引用可以加速JVM对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生。


    (3) 不可视阶段
         当一个对象处于不可视阶段,说明我们在其他区域的代码中已经不可以在引用它,其强引用已经消失,例如,本地变量超出了其可视
的范围。

     
 try {
             Object localObj = new Object();
	     localObj.doSomething();
       } catch (Exception e) {
           e.printStackTrace();
       }

       if (true) {
	    // 此区域中localObj 对象已经不可视了, 编译器会报错。
	    localObj.doSomething();
       }
   

   (4) 不可到达阶段
       处于不可达阶段的对象在虚拟机的对象引用根集合中再也找不到直接或间接地强引用,这些对象一般是所有线程栈中的临时变量。所有已经装载的静态变量或者是对本地代码接口的引用。


   (5) 可收集阶段、终结阶段与释放阶段

       当一个对象处于可收集阶段、终结阶段与释放阶段时,该对象有如下三种情况:

       <1> 回收器发现该对象已经不可达。

       <2> finalize方法已经被执行。

       <3> 对象空间已被重用。

     
       
      
分享到:
评论
48 楼 wangzaixiang 2010-06-24  
gong_leon 写道
不要写成:
1。 
       for (int i = 0; i < 10000; i++) { 
             Object obj = new Object(); 
             System.out.println(obj); 
       } 
 
       要写成:
2。 
       Object obj = null; 
       for (int i = 0; i < 10000; i++) { 
             obj = new Object(); 
             System.out.println(obj); 
       }
-------------------
可不可以理解为 方法2 只做了一次栈空间分配 而方法1 却做了10000次栈空间的分配
虽然两个在数量上都只 用了1个栈空间,这上边应该也有性能损耗吧。


答案是:在性能上二者完全一致,不存在任何耗损。
为什么呢?我是很难解释清楚,给你个建议,去看看javap -c,也就是字节码,看懂了你就完全明白了。这就是所谓的汇编思维。

我的解释是:在函数执行时,在循环中,并不需要每次分配一个变量的,变量的空间在栈上,随时可用,没有“分配”这个操作,因此,没有开销,并不是“开销很小”,而是完全没有,这与new不同,每次new都是有具体开销的。
47 楼 mercyblitz 2010-06-23  
gong_leon 写道
不要写成:
1。 
       for (int i = 0; i < 10000; i++) { 
             Object obj = new Object(); 
             System.out.println(obj); 
       } 
 
       要写成:
2。 
       Object obj = null; 
       for (int i = 0; i < 10000; i++) { 
             obj = new Object(); 
             System.out.println(obj); 
       }
-------------------
可不可以理解为 方法2 只做了一次栈空间分配 而方法1 却做了10000次栈空间的分配
虽然两个在数量上都只 用了1个栈空间,这上边应该也有性能损耗吧。



字节码不会复制N次,它也有循环语句的。
46 楼 mercyblitz 2010-06-23  
H_eaven 写道
mercyblitz 写道

4.我的理解是:
  <1> 比如方法中的局部对象,并不需要JVM标记不可达,在方法执行完之后,马上就被回收。这就是为什么不需要在局部对象使用后,显示地设置为null.



对象引用的作用域与对象的生命周期搞混了吧。


这里作用域没有关系。
45 楼 gong_leon 2010-06-23  
不要写成:
1。 
       for (int i = 0; i < 10000; i++) { 
             Object obj = new Object(); 
             System.out.println(obj); 
       } 
 
       要写成:
2。 
       Object obj = null; 
       for (int i = 0; i < 10000; i++) { 
             obj = new Object(); 
             System.out.println(obj); 
       }
-------------------
可不可以理解为 方法2 只做了一次栈空间分配 而方法1 却做了10000次栈空间的分配
虽然两个在数量上都只 用了1个栈空间,这上边应该也有性能损耗吧。
44 楼 H_eaven 2010-06-23  
mercyblitz 写道

4.我的理解是:
  <1> 比如方法中的局部对象,并不需要JVM标记不可达,在方法执行完之后,马上就被回收。这就是为什么不需要在局部对象使用后,显示地设置为null.



对象引用的作用域与对象的生命周期搞混了吧。

43 楼 mercyblitz 2010-06-22  
wangzaixiang 写道
mercyblitz 写道

  <3>重用是肯定的,但是对象空间能不能马上重用,很难说,G1的算法和CMS等算法不同。G1是等大小区域,相对来说比较容易。CMS,我没有记错的话,是采用的碎片空间移动。




CMS缺省是不做碎片处理的。G1则是。



Thanks,果然我记错了。
42 楼 xiaojing3517 2010-06-22  
这个得留下个印,交流肯定会继续。

静观
41 楼 H_eaven 2010-06-22  
Thinking in Java是本不错的书籍,但它仍然是一个适合入门的作用,很多概念都提到了,但所讲的有的地方并不太深入。
像对象的生命周期管理,如果只是入门理解,Thinking in Java讲的差不多,但更深入的理解,
就要细分这些概念了:jvm的一个核心作用就是管理类型的生命周期,对象的生命周期是被包含在类型的生命周期范围之内的;
生命周期中的行为(不同时间点的不同行为了)与结构(类型信息数据,对象信息数据的存储)问题 ;垃圾回收的目的,不同的实现算法,不同实现算法之间的利弊.
在这些的讲述上,Thinking in Java并没有过多的讲解,当然如果要把这些都进行稍详细的讲解,书籍的页码至少会增加100页吧,
但Thinking in Java的简述意义是很大的,它至少告诉我们有这些概念,而且这些对于jvm来说是至关重要的。
40 楼 maozj 2010-06-22  
wangzaixiang 写道
maozj 写道
joinhack 写道
  for (int i = 0; i < 10000; i++) { 
             Object obj = new Object(); 
             System.out.println(obj); 
       } 
 
       要写成: 
       Object obj = null; 
       for (int i = 0; i < 10000; i++) { 
             obj = new Object(); 
             System.out.println(obj); 
       }
大家都感觉作者说的是错误的,但是并没有说出为什么,因此作者毛了,呵呵
其实,作者的意思是这么写会造成很多引用留在栈里面,但是作者还有个东西没有考虑,就是作用域。
句柄会随着作用域消失而消失,因此上面的2种写法,对于句柄来说不会不断扩大,作者可以放心使用。

恩 成很多引用留在栈里面 说的正确。。。
Object obj = null;  后者还多了一条初始化

您的说法 完全接受。。。 表述不错 足见很有方法哲理~~


虽然会冒着被骂的风险,还是在指点楼主两句。“成很多引用留在栈里面 说的正确”的理解还是错的。告诉你一个结果,第一段代码,哪怕你循环100万次,你需要的内存和循环1次是一样多的,栈中也只有一个引用。

再给你一点为学的指点:很多时候,你说明白了,你还真傻也不明白,如果你知道你其实不明白,可能比你认为自己明白更明白,然后你才有可能明白,否则,你以为明白了,永远也不会明白,却一直以为自己明白,其实不明不白。而要想明白,需要谦虚一些,低调一些,不要不懂装懂,或者看点皮毛,就迫不及待的觉得发现了真理。慢慢的,或许你会明白,不过,如果你还是不明白,那就慢慢的去明白吧。

-----------------------------------------------------
很敬业... 足见肺腑之言哦 接受~~
39 楼 maozj 2010-06-22  
wangzaixiang 写道
晚上再看看帖子,略有感触。

1、这个初哥不理解我的问题也就罢了,放屁的词语也出来了。现在,不知道到底明白是我在放屁还是楼主搞错了。

2、谢谢mercyblitz替我回复,与我的表述基本相符,但可能存在笔误,我再修订一下。

每一行执行代码称为一帧 =》 应为每一次函数调用对应一帧。

引用
确实没有差别,理由:这两代码都生成了10000个对象,楼主对引用的理解有误。第二段代码中,相对与第一段而言,少开辟了几个栈对象,也就是说 Object obj只是个一个标示,关键是new了10000次,因此在执行效率和内存消耗没有不同,只是变量的作用域变化了。

其实,第二段代码和第一段代码在帧上分配的变量是一样的,也是一个栈变量(不合适成为栈对象)。

4、当对象处于释放状态后,空间状态为“已被重用”,这个是典型的字面翻译,其错误不值一提。

感觉楼主应该是一个很好学的程序员,不过,目前,Java水平还处在幼儿园级别,谦虚程度又颇有博士生的感觉。谈到放屁二字,我稍微吹吹水,在JVM上,我小有10余年研究,不算专家的话,也是略有精通的人了。本想指点楼主一二,却可惜被反批了一顿,笑笑飘过。



呵呵,你发展下去肯定会达到项目经理,很有项目经理的潜质
38 楼 maozj 2010-06-22  
mercyblitz 写道
maozj 写道
wangzaixiang 写道
写了很多,错误不少。
1. 而栈内存则是用来存储程序代码中声明为静态或非静态的方法。
完全错误的表达。

2、为对象分配存储空间;
       <2> 开始构造对象;
       <3> 从超类到子类对static成员进行初始化;
       <4> 超类成员变量按顺序初始化,递归调用超类的构造方法;
       <5> 子类成员变量按顺序初始化,子类构造方法调用。
static的初始化与new操作没有任何关系。

3、比如:
       不要写成:
       for (int i = 0; i < 10000; i++) {
             Object obj = new Object();
             System.out.println(obj);
       }

       要写成:
       Object obj = null;
       for (int i = 0; i < 10000; i++) {
             obj = new Object();
             System.out.println(obj);
       }
完全错误。两者在运行时几无差别,前置在编码风格上更为合理。

4、 (5) 可收集阶段、终结阶段与释放阶段
       当一个对象处于可收集阶段、终结阶段与释放阶段时,该对象有如下三种情况:

       <1> 回收器发现该对象已经不可达。

       <2> finalize方法已经被执行。

       <3> 对象空间已被重用。
前面的理解都不正确,这里的表述更为问题多多。

1. 而栈内存则是用来存储程序代码中声明为静态或非静态的方法。
完全错误的表达。  请您解释下?

2. static的初始化与new操作没有任何关系。放屁

3.完全错误。两者在运行时几无差别,前置在编码风格上更为合理。 理由?

4. 请解释。


呵呵,我来解释吧,首先楼主,你要明白一点,wangzaixiang说的是对的。为什么?

1.栈内存是由栈帧(frame)组成的,在Java代码中的,每一行执行代码称为一帧。比如Object obj=null;obj这样的标示对象会保存在栈里面。同时,方法参数对象标示也在栈中。对象的实际物理保存都在堆中。由于Java的重进入,
所以栈对象是线程安全,而堆是JVM线程共享的,因此需要合理的同步。

2.确实没有关系,static成员隶属于类对象,类的static成员的初始化是在ClassLoader第一次加载该类的时候。和new确实没有什么关系,只不过第一次出现new SomeObject的时候,会加载SomeObject,然后静态成员初始化了。楼主这里就误会了,不一定new 才是加载类的开始,通过调用类的静态可访问的方法也是可以的。

3.确实没有差别,理由:这两代码都生成了10000个对象,楼主对引用的理解有误。第二段代码中,相对与第一段而言,少开辟了几个栈对象,也就是说Object obj只是个一个标示,关键是new了10000次,因此在执行效率和内存消耗没有不同,只是变量的作用域变化了。

4.我的理解是:
  <1> 比如方法中的局部对象,并不需要JVM标记不可达,在方法执行完之后,马上就被回收。这就是为什么不需要在局部对象使用后,显示地设置为null.
  <2>finalize 不一定可靠,或者被调用。规范中没有规定,垃圾收集器必须要在对象回收时,调用这种方法或者并不是每个对象回收时均被调用。不等同于free或者C++的析构。
  <3>重用是肯定的,但是对象空间能不能马上重用,很难说,G1的算法和CMS等算法不同。G1是等大小区域,相对来说比较容易。CMS,我没有记错的话,是采用的碎片空间移动。






--------------------------
这样的表述可以完全接受,如果看完帖子你能给出这样的答案,那我不枉发布此贴~~ 谢谢.
37 楼 beneo 2010-06-22  
wangzaixiang 写道
第二种的差别仅仅是多了一条 obj = null的指令罢了。就是 aconst_null, astore_1,除此之外,和第一个版本是一样的。


你好,我后来用eclipse插件看,http://andrei.gmxhome.de/bytecode/index.html

第一种是frame same,第二种是frame append

参考 3.15 frame, http://download.forge.objectweb.org/asm/asm-guide.pdf

如果我的猜测没有错的话,第一种比第二种还要快,你看看

// class version 50.0 (50)
// access flags 0x21
public class com/tencent/BytecodeUnitTests {

  // compiled from: BytecodeUnitTests.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init>()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/tencent/BytecodeUnitTests; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 6 L0
    FRAME SAME
    NEW java/lang/Object
    DUP
    INVOKESPECIAL java/lang/Object.<init>()V
    ASTORE 1
   L1
    LINENUMBER 7 L1
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/Object;)V
   L2
    LINENUMBER 5 L2
    GOTO L0
   L3
    LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
    LOCALVARIABLE obj Ljava/lang/Object; L1 L2 1
    MAXSTACK = 2
    MAXLOCALS = 2
}




// class version 50.0 (50)
// access flags 0x21
public class com/tencent/BytecodeUnitTests {

  // compiled from: BytecodeUnitTests.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init>()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/tencent/BytecodeUnitTests; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 5 L0
    ACONST_NULL
    ASTORE 1
   L1
    LINENUMBER 7 L1
    FRAME APPEND
    NEW java/lang/Object
    DUP
    INVOKESPECIAL java/lang/Object.<init>()V
    ASTORE 1
   L2
    LINENUMBER 8 L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    ALOAD 1
    INVOKEVIRTUAL java/io/PrintStream.println(Ljava/lang/Object;)V
   L3
    LINENUMBER 6 L3
    GOTO L1
   L4
    LOCALVARIABLE args [Ljava/lang/String; L0 L4 0
    LOCALVARIABLE obj Ljava/lang/Object; L1 L4 1
    MAXSTACK = 2
    MAXLOCALS = 2
}

36 楼 wangzaixiang 2010-06-22  
第二种的差别仅仅是多了一条 obj = null的指令罢了。就是 aconst_null, astore_1,除此之外,和第一个版本是一样的。
35 楼 beneo 2010-06-22  
wangzaixiang 写道
你把javap -c 的代码贴出来吧。再给你解释。


  Code:
   0:	aload_0
   1:	invokespecial	#8; //Method java/lang/Object."<init>":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	new	#3; //class java/lang/Object
   3:	dup
   4:	invokespecial	#8; //Method java/lang/Object."<init>":()V
   7:	astore_1
   8:	getstatic	#16; //Field java/lang/System.out:Ljava/io/PrintStream;
   11:	aload_1
   12:	invokevirtual	#22; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   15:	goto	0



第二种

  Code:
   0:	aload_0
   1:	invokespecial	#8; //Method java/lang/Object."<init>":()V
   4:	return

public static void main(java.lang.String[]);
  Code:
   0:	aconst_null
   1:	astore_1
   2:	new	#3; //class java/lang/Object
   5:	dup
   6:	invokespecial	#8; //Method java/lang/Object."<init>":()V
   9:	astore_1
   10:	getstatic	#16; //Field java/lang/System.out:Ljava/io/PrintStream;
   13:	aload_1
   14:	invokevirtual	#22; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   17:	goto	2

34 楼 wangzaixiang 2010-06-22  
你把javap -c 的代码贴出来吧。再给你解释。
33 楼 beneo 2010-06-22  
wangzaixiang 写道
beneo 写道

1. 请问,如果考虑GC的话,哪个比较快呢?

2. 我可以这么认为么?一个函数对应了一帧,所以2个的帧数是一样的,所以2者的效率一样?不过我javap -c 看了一下,两者还是有区别的
我用while代替了for,这样更容易看
        while(true){
            Object obj = new Object();
            System.out.println(obj);
        }

   FRAME SAME
        Object obj = null;
        while(true){
            obj = new Object();
            System.out.println(obj);
        }


FRAME APPEND [java/lang/Object]

这两者的Frame是不同的,请问跟你说的是不是违背呢??



1、即便考虑GC,上述的两段代码效率仍然是一样的。这里的一样是100%的一样。
2、从java字节码来看,二者确实是有区别的。主要是obj的作用域,第一段代码更短,第二段代码则要长一些。但是作用域主要是一个语法的范畴,和GC关系并不大。(不是说完全没有关系,比如说,第一段代码理论上你无需obj = null,在离开循环后,即可成为垃圾。而后者则需要:手动设为null或者函数执行结束后,才能成为垃圾。但无论在何种情况下,只有一个对象可能会被引用到,其它循环之前的对象在循环进入到下一轮之前已经成为垃圾。


那么bytecodek后我所看到的FRAME APPENDFRAME SAME是不是你们一直谈论的“帧”,如果是的话,我觉得您说的话可能有不对的地方。
32 楼 wangzaixiang 2010-06-22  
beneo 写道

1. 请问,如果考虑GC的话,哪个比较快呢?

2. 我可以这么认为么?一个函数对应了一帧,所以2个的帧数是一样的,所以2者的效率一样?不过我javap -c 看了一下,两者还是有区别的
我用while代替了for,这样更容易看
        while(true){
            Object obj = new Object();
            System.out.println(obj);
        }

   FRAME SAME
        Object obj = null;
        while(true){
            obj = new Object();
            System.out.println(obj);
        }


FRAME APPEND [java/lang/Object]

这两者的Frame是不同的,请问跟你说的是不是违背呢??



1、即便考虑GC,上述的两段代码效率仍然是一样的。这里的一样是100%的一样。
2、从java字节码来看,二者确实是有区别的。主要是obj的作用域,第一段代码更短,第二段代码则要长一些。但是作用域主要是一个语法的范畴,和GC关系并不大。(不是说完全没有关系,比如说,第一段代码理论上你无需obj = null,在离开循环后,即可成为垃圾。而后者则需要:手动设为null或者函数执行结束后,才能成为垃圾。但无论在何种情况下,只有一个对象可能会被引用到,其它循环之前的对象在循环进入到下一轮之前已经成为垃圾。
31 楼 beneo 2010-06-22  
wangzaixiang 写道
晚上再看看帖子,略有感触。

1、这个初哥不理解我的问题也就罢了,放屁的词语也出来了。现在,不知道到底明白是我在放屁还是楼主搞错了。

2、谢谢mercyblitz替我回复,与我的表述基本相符,但可能存在笔误,我再修订一下。

每一行执行代码称为一帧 =》 应为每一次函数调用对应一帧。

确实没有差别,理由:这两代码都生成了10000个对象,楼主对引用的理解有误。第二段代码中,相对与第一段而言,少开辟了几个栈对象,也就是说 Object obj只是个一个标示,关键是new了10000次,因此在执行效率和内存消耗没有不同,只是变量的作用域变化了其实,第二段代码和第一段代码在帧上分配的变量是一样的,也是一个栈变量(不合适成为栈对象)。


1. 请问,如果考虑GC的话,哪个比较快呢?

2. 我可以这么认为么?一个函数对应了一帧,所以2个的帧数是一样的,所以2者的效率一样?不过我javap -c 看了一下,两者还是有区别的
我用while代替了for,这样更容易看
        while(true){
            Object obj = new Object();
            System.out.println(obj);
        }

   FRAME SAME
        Object obj = null;
        while(true){
            obj = new Object();
            System.out.println(obj);
        }


FRAME APPEND [java/lang/Object]

这两者的Frame是不同的,请问跟你说的是不是违背呢??

引用

4、当对象处于释放状态后,空间状态为“已被重用”,这个是典型的字面翻译,其错误不值一提。

感觉楼主应该是一个很好学的程序员,不过,目前,Java水平还处在幼儿园级别,谦虚程度又颇有博士生的感觉。谈到放屁二字,我稍微吹吹水,在JVM上,我小有10余年研究,不算专家的话,也是略有精通的人了。本想指点楼主一二,却可惜被反批了一顿,笑笑飘过。


能否给出您的理解呢??
30 楼 wangzaixiang 2010-06-22  
mercyblitz 写道

  <3>重用是肯定的,但是对象空间能不能马上重用,很难说,G1的算法和CMS等算法不同。G1是等大小区域,相对来说比较容易。CMS,我没有记错的话,是采用的碎片空间移动。




CMS缺省是不做碎片处理的。G1则是。
29 楼 wangzaixiang 2010-06-22  
maozj 写道
joinhack 写道
  for (int i = 0; i < 10000; i++) { 
             Object obj = new Object(); 
             System.out.println(obj); 
       } 
 
       要写成: 
       Object obj = null; 
       for (int i = 0; i < 10000; i++) { 
             obj = new Object(); 
             System.out.println(obj); 
       }
大家都感觉作者说的是错误的,但是并没有说出为什么,因此作者毛了,呵呵
其实,作者的意思是这么写会造成很多引用留在栈里面,但是作者还有个东西没有考虑,就是作用域。
句柄会随着作用域消失而消失,因此上面的2种写法,对于句柄来说不会不断扩大,作者可以放心使用。

恩 成很多引用留在栈里面 说的正确。。。
Object obj = null;  后者还多了一条初始化

您的说法 完全接受。。。 表述不错 足见很有方法哲理~~


虽然会冒着被骂的风险,还是在指点楼主两句。“成很多引用留在栈里面 说的正确”的理解还是错的。告诉你一个结果,第一段代码,哪怕你循环100万次,你需要的内存和循环1次是一样多的,栈中也只有一个引用。

再给你一点为学的指点:很多时候,你说明白了,你还真傻也不明白,如果你知道你其实不明白,可能比你认为自己明白更明白,然后你才有可能明白,否则,你以为明白了,永远也不会明白,却一直以为自己明白,其实不明不白。而要想明白,需要谦虚一些,低调一些,不要不懂装懂,或者看点皮毛,就迫不及待的觉得发现了真理。慢慢的,或许你会明白,不过,如果你还是不明白,那就慢慢的去明白吧。

相关推荐

    Java 详解垃圾回收与对象生命周期

    主要介绍了Java 详解垃圾回收与对象生命周期的相关资料,这里对堆内存与栈内存进行详解及JVM 的生命周期介绍,需要的朋友可以参考下

    Java内存与垃圾回收调优.docx

    Java内存与垃圾回收调优,Java内存与垃圾回收的调优是一个重要的主题,特别是在高性能和大规模的应用程序中。以下是一些关键的调优建议和步骤: 理解内存结构: Java堆是主要的内存区域,用于存储对象实例。 堆内存...

    java经典面试题目-面经-java-Java语言的进阶概念-常用的库和框架-并发编程-网络编程-Web开发-面经

    Java中的GC(垃圾回收)是什么?如何手动触发对象的垃圾回收? 什么是Java中的设计模式?列举一些常见的设计模式。 什么是Java中的单例模式?如何实现线程安全的单例模式? 什么是Java中的生命周期回调方法?列举...

    类和对象的生命周期

    目录1、类加载2、使用2.1对象实例化2.1.1 为Student对象分配空间2.1.2对象的内存布局2.1.3对象的访问定位直接访问句柄访问2.2、垃圾收集2.2.1、回收区域、何时回收2.2.1.1 回收内容(可达性分析算法)2.2.2、如何...

    Java零基础-对象的创建和使用-内存分析.md

    了解对象的生命周期和垃圾回收机制。 阅读建议: 阅读前建议已具备基本的Java语法知识和编程经验。 请按照顺序阅读对象的创建、内存分配和使用的部分,确保理解每一部分的内容。 在阅读示例代码时,结合自己的实际...

    java内存泄漏

    在C++语言中,如果需要动态分配一块内存,程序员需要负责这块内存的整个生命周期。从申请分配、到使用、再到最后的释放。这样的过程非常灵活,但是却十分繁琐,程序员很容易由于疏忽而忘记释放内存,从而导致内存的...

    java虚拟机(jvm)介绍以及相关参数设置与调优

    虚拟机的相关参数设置与调优,介绍了一些jvm的体系结构,垃圾回收的方法,java对象的生命周期与分代。

    免费超全面的Java基础类型,容器,并发,IO流,面向对象,Web编程等代码总结

    JVM执行引擎和垃圾回收 基础语法 理解Java中对象基础Object类 基本数据类型,核心点整理 特殊的String类,以及相关扩展API 日期与时间API详解 流程控制语句,和算法应用 函数式编程概念和应用 集合容器 基于分析...

    mysql面试题-mysql经典面试题目-数据库的基本概念-SQL语法-事务处理-索引优化-性能调优-mysql-面试题目

    Java中的GC(垃圾回收)是什么?如何手动触发对象的垃圾回收? 什么是Java中的设计模式?列举一些常见的设计模式。 什么是Java中的单例模式?如何实现线程安全的单例模式? 什么是Java中的生命周期回调方法?列举...

    [java]读书笔记整理:一切都是对象

    Java有个“垃圾回收器”,用来监视用new创建的所有对象,并辨别哪些不会再被引用的对象。随后,释放这些对象的内存空间,以便供其他新的对象使用。也就是说,你根本不必担心内存回收的问题。你只需要创建对象,一旦...

    笔记,2、内存分配与回收策略~深入理解垃圾回收器1

    3、写代码有好处栈:栈中的生命周期是跟随线程,所以一般不需要关注堆:堆中的对象是垃圾回收的重点方法区/元空间:这一块也会发生垃圾回收,不过这块的效率比较低,一般

    预习,2、内存分配与回收策略~深入理解垃圾回收器1

    3、写代码有好处栈:栈中的生命周期是跟随线程,所以一般不需要关注堆:堆中的对象是垃圾回收的重点方法区/元空间:这一块也会发生垃圾回收,不过这块的效率比较低,一般

    java高级工程师、技术专家、架构师、项目经理面试宝典.rar

    2.Java对象的生命周期 答:创建阶段 、 应用阶段 、不可见阶段 、不可达阶段 、收集阶段 、终结阶段、 对象空间重新分配阶段等等。 3.Map或者HashMap的存储原理 答:HashMap是由数组+链表的一个结构组成。

    21天学会Java之(Java SE第四篇):Java虚拟机、垃圾回收机制

    文章目录Java虚拟机(JVM)JVM的基本结构类加载机制类的生命周期类加载器的种类类加载机制运行时数据区(内存分析)垃圾回收机制新生代(Young Generation)老年代(Old Generation)元空间(Meta Space) Java虚拟机...

    Java后端面试问题整理.docx

    • 熟悉Java多线程并发中线程基本方法,线程池,线程生命周期,熟悉Java锁中常见锁分类(乐观/悲观锁、自旋锁、独/共享锁、可重入锁、公平/非公平锁、分段锁、偏向锁,轻/重量级锁)和基本的锁升级策略

    Java清华教程.rar

     ■ Java语言的基础知识:Java语言的主要特点,设计思想,Java虚拟机,垃圾回收机制,安全性的保证机制;  ■ Java语言的基本语法规范,包括标识符、关键字、数据类型、表达式和流控制,程序基本结构;  ■ ...

    Android中的内存泄漏

    长生命周期的对象持有了短生命周期的对象,从而导致短生命周期的对象不能被释放 垃圾回收机制 垃圾回收机制分为:引用计数法、可达性分析法 引用计数法(有循环引用的问题):Python、Object-C、Swift 用一个计数器...

    Java面试技术面知识扩展包第三弹.rar

    开发者不需要手动进行内存分配和释放,通过“对象生命周期”的概念,垃圾收集器会自动回收不再使用的内存。 4. 异常处理:Java提供了异常处理机制,可以在出现异常情况时捕获、处理并恢复程序的运行。通过try-catch...

Global site tag (gtag.js) - Google Analytics