论坛首页 Java企业应用论坛

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

浏览 18254 次
精华帖 (5) :: 良好帖 (2) :: 新手帖 (5) :: 隐藏帖 (1)
作者 正文
   发表时间:2010-06-22   最后修改: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余年研究,不算专家的话,也是略有精通的人了。本想指点楼主一二,却可惜被反批了一顿,笑笑飘过。


能否给出您的理解呢??
0 请登录后投票
   发表时间:2010-06-22   最后修改: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或者函数执行结束后,才能成为垃圾。但无论在何种情况下,只有一个对象可能会被引用到,其它循环之前的对象在循环进入到下一轮之前已经成为垃圾。
0 请登录后投票
   发表时间: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是不是你们一直谈论的“帧”,如果是的话,我觉得您说的话可能有不对的地方。
0 请登录后投票
   发表时间:2010-06-22   最后修改:2010-06-22
你把javap -c 的代码贴出来吧。再给你解释。
0 请登录后投票
   发表时间:2010-06-22   最后修改: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

0 请登录后投票
   发表时间:2010-06-22  
第二种的差别仅仅是多了一条 obj = null的指令罢了。就是 aconst_null, astore_1,除此之外,和第一个版本是一样的。
0 请登录后投票
   发表时间:2010-06-22   最后修改: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
}

0 请登录后投票
   发表时间: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,我没有记错的话,是采用的碎片空间移动。






--------------------------
这样的表述可以完全接受,如果看完帖子你能给出这样的答案,那我不枉发布此贴~~ 谢谢.
0 请登录后投票
   发表时间:2010-06-22  
wangzaixiang 写道
晚上再看看帖子,略有感触。

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

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

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

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

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

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

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



呵呵,你发展下去肯定会达到项目经理,很有项目经理的潜质
0 请登录后投票
   发表时间: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次是一样多的,栈中也只有一个引用。

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

-----------------------------------------------------
很敬业... 足见肺腑之言哦 接受~~
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics