[讨论] 关于本地变量表中的对象垃圾回收时间

subchen 2013-09-03
public Object method() {
   Object obj1 = new Object();
   Object obj2 = new Object();
   return obj2;
}


这里的 obj1 是在方法返回的时候就释放还是和其他的对象一样在触发GC的时候才释放?

如果是立即释放,那么释放的时候怎么判断 需要释放 obj1 还是 obj2. 原理是什么?
subchen 2013-09-06
public class JVMTest {
  public static void test() {
    byte[] a = new byte[100 * 1024 * 1024];
  }

  public static void main(String[] args) throws Exception {
    for (int i = 0; i < 1000; i++) {
      test();
    }
  }
}


实测结果是和其他的对象一样,只有在触发GC的时候才释放。 可以从 jvisualvm 监控台看出内存一直的飙升,然后在 GC 的时候,一下子恢复到原始位置。
yasenagat 2013-09-06
我觉得
1、obj1这个引用跳出方法就消失了,通常情况,这个应该不属于gc的回收范围
2、obj1指向的Object,GC的时候可能会被回收
subchen 2013-09-06
yasenagat 写道
我觉得
1、obj1这个引用跳出方法就消失了,通常情况,这个应该不属于gc的回收范围
2、obj1指向的Object,GC的时候可能会被回收


没错,obj1这个引用是在栈帧中的本地变量表中分配的,方法退出就被释放了,但是obj1指向的Object需要等到GC的时候才会被回收
RednaxelaFX 2013-09-09
首先从JVM规范所规定的抽象概念的JVM来看。

JVM规范明确说了不指定使用某种自动内存管理方式:
JVM Specification, Java SE 7 写道
The heap is created on virtual machine start-up. Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements. The heap may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger heap becomes unnecessary. The memory for the heap does not need to be contiguous.

也就是说任何自动内存管理方式都可以。事实上就算完全没有GC,只分配对象而从来不释放也可以算是合法的JVM实现——从表明行为上说JVM的Java heap只是一块“无限大”的存储空间,只管分配;如果真这样实现的话大不了在内存用满的时候就抛OutOfMemoryError就完事了。

从这个层面上说,对象释放不释放啥的概念完全没规定。具体的JVM实现想怎么搞都行。

===================================================

然后从一般的自动内存管理角度看。

自动内存管理一般有两大分类,一种是引用计数(reference counting)法,另外一种是跟踪(trace)法。刚在知乎上回答了一个相关的问题,楼主请参考:http://www.zhihu.com/question/21539353/answer/18596488

现在主流的JVM无一使用引用计数法,全部都使用基于跟踪的方法来实现GC。要等到实际在对象图上做跟踪的时候(例如说GC的mark阶段)才知道哪些对象是活的,所以通常不会在某个对象刚符合被回收条件时就马上释放掉它(因为不做跟踪就还不知道它已经死了)。

在楼主给的例子里,obj1所指向的新对象虽然在method返回时就已经无活引用,但还是会等到GC时才有机会识别出它是死的并将其释放。

要注意的是obj1与obj2这两个变量自身并不是对象,而是局部变量,是指向对象的引用。它们自身也要占据空间,而这个空间分配在JVM调用栈上。当一次method的调用返回时,它对应栈帧就会整体被释放,于是obj1与obj2这两个局部变量的存储空间也就随之被释放了。这与它们原本所指向的对象的释放完全是两码事。

如果要实现obj1所指向的对象在method返回是理解释放,一种办法是实现Java对象的“栈上分配”(stack allocation)。简单来说就是把对象自身也分配在栈上,那么栈帧释放的时候那个对象也自然就释放掉了。栈上分配需要做“逃逸分析”(escape analysis)来判断到底某个对象是否有引用“逃逸”到某个方法之外,如果没有,就意味着该对象与该方法调用的生命周期一致,就可以将对象分配在栈上。
但这样做非常复杂而且实际好处不够大,所以现在主流JVM也无一实现对象的栈上分配。所以这里就不详细说了,在现实生产中您是不会见到这种做法的。

===================================================

最后从HotSpot VM的实际情况来看看。

HotSpot VM实现了“标量替换”(scalar replacement)的优化。这里我写过一个例子:http://rednaxelafx.iteye.com/blog/659108
标量替换可以看做是对象的栈上分配的一种特殊例子,同样要基于逃逸分析来保证对象的生命周期与方法调用的周期一致,不过限制更多一些,而且优化的效果更好一些——连对象结构都不需要分配了。
对应到楼主的例子,如果楼主用Oracle/Sun JDK的Server VM(而不是Client VM)来跑您给的“JVMTest”的例子,把循环次数修改到例如说100000000并且数组大小改为不大于64(这是EliminateAllocationArraySizeLimit参数的默认值),再看看VisualVM的监控数据,会发现过了一段时间之后就不做任何GC了,内存的使用量几乎不增加。因为那个byte[]可以被标量替换掉,而里面的元素都没被使用所以test方法就被优化成了一个空方法。

如果不想修改代码的话也可以修改VM的启动参数,加上-XX:CompileThreshold=10 -XX:EliminateAllocationArraySizeLimit=104857600

可以给楼主看看我跑楼主的例子(没修改数组大小)的状况:
public class JVMTest {
  public static void test() {
    byte[] a = new byte[100 * 1024 * 1024];
  }

  public static void main(String[] args) throws Exception {
    for (int i = 0; i < 100000000; i++) {
      test();
    }
  }
}

D:\test\x>java -version
java version "1.7.0_09"
Java(TM) SE Runtime Environment (build 1.7.0_09-b05)
Java HotSpot(TM) 64-Bit Server VM (build 23.5-b02, mixed mode)

D:\test\x>java -XX:+PrintGC -XX:CompileThreshold=10 -XX:EliminateAllocationArraySizeLimit=104857600 JVMTest
[GC 615364K->614960K(701376K), 0.0099493 secs]
[GC 614960K->614968K(717440K), 0.0101712 secs]
[Full GC 614968K->482K(65408K), 0.0824360 secs]

D:\test\x>java -XX:+PrintGC -XX:CompileThreshold=10 -XX:EliminateAllocationArraySizeLimit=104857599 JVMTest
[GC 615364K->614992K(701376K), 0.0124849 secs]
[GC 614992K->614952K(717440K), 0.0100962 secs]
[Full GC 614952K->482K(65344K), 0.0823610 secs]
[GC 616168K->614914K(717440K), 0.0065670 secs]
[GC 614914K->614914K(746368K), 0.0055593 secs]
[Full GC 614914K->484K(95488K), 0.0715432 secs]
[GC 614884K->614884K(746368K), 0.0104201 secs]
[GC 614884K->614884K(800256K), 0.0117261 secs]
[Full GC 614884K->482K(150144K), 0.0741686 secs]
[GC 717282K->614882K(800256K), 0.0109455 secs]
[GC 717282K->614882K(907264K), 0.0096421 secs]
[GC 824158K->614882K(907200K), 0.0109403 secs]
[GC 821968K->614882K(1023232K), 0.0096254 secs]
[GC 924365K->614882K(1023232K), 0.0196061 secs]
[GC 923584K->614882K(1023232K), 0.0227030 secs]
[GC 923070K->614882K(1023296K), 0.0087877 secs]
[GC 922732K->614882K(1023296K), 0.0088492 secs]
[GC 922510K->614882K(1023360K), 0.0075170 secs]
[GC 922364K->614882K(1023360K), 0.0090487 secs]
[GC 922267K->614882K(1023424K), 0.0095972 secs]
[GC 922204K->614882K(1023552K), 0.0093021 secs]
[GC 922162K->614882K(1023168K), 0.0121443 secs]
[GC 922135K->614882K(1023488K), 0.0061937 secs]
[GC 922117K->614882K(1023488K), 0.0060109 secs]
[GC 922105K->614882K(1023552K), 0.0060192 secs]
[GC 922097K->614882K(1023552K), 0.0098024 secs]
[GC 922092K->614882K(1023680K), 0.0088807 secs]
[GC 922089K->614882K(1023424K), 0.0094522 secs]
[GC 922087K->614882K(1023616K), 0.0097761 secs]
[GC 922085K->614882K(1023616K), 0.0095535 secs]
[GC 922084K->614882K(1023616K), 0.0094657 secs]
[GC 922084K->614882K(1023680K), 0.0090462 secs]
[GC 922084K->614882K(1023808K), 0.0061527 secs]
[GC 922084K->614882K(1023808K), 0.0061610 secs]
[GC 922084K->614882K(1023808K), 0.0061315 secs]
[GC 922084K->614882K(1023808K), 0.0061315 secs]
[GC 922084K->614882K(1023680K), 0.0128678 secs]
[GC 922084K->614882K(1023744K), 0.0159224 secs]
[GC 922084K->614882K(1023872K), 0.0116100 secs]
[GC 922084K->614882K(1023872K), 0.0082777 secs]
[GC 922084K->614882K(1023872K), 0.0090616 secs]
[GC 922084K->614882K(1023872K), 0.0094676 secs]
[GC 922084K->614882K(1023872K), 0.0076100 secs]
[GC 922084K->614882K(1023872K), 0.0128576 secs]
[GC 922084K->614882K(1023808K), 0.0060507 secs]
[GC 922084K->614882K(1023872K), 0.0060154 secs]
[GC 922084K->614882K(1023936K), 0.0059936 secs]
[GC 922084K->614882K(1023936K), 0.0060571 secs]
[GC 922084K->614882K(1023936K), 0.0127222 secs]
[GC 922084K->614882K(1023936K), 0.0126748 secs]
[GC 922084K->614882K(1023936K), 0.0096074 secs]
[GC 922084K->614882K(1023936K), 0.0119910 secs]
[GC 922084K->614882K(1023936K), 0.0094554 secs]
[GC 922084K->614882K(1023936K), 0.0094304 secs]
[GC 922084K->614882K(1023936K), 0.0082668 secs]
[GC 922084K->614882K(1023936K), 0.0060038 secs]
[GC 922084K->614882K(1023936K), 0.0061873 secs]
[GC 922084K->614882K(1023936K), 0.0060462 secs]
[GC 922084K->614882K(1023936K), 0.0096164 secs]
[GC 922084K->614882K(1023936K), 0.0217036 secs]
[GC 922084K->614882K(1023936K), 0.0086966 secs]
[GC 922084K->614882K(1023936K), 0.0141539 secs]
[GC 922084K->614882K(1023936K), 0.0087787 secs]
[GC 922084K->614882K(1023936K), 0.0094079 secs]
[GC 922084K->614882K(1023936K), 0.0094285 secs]
[GC 922084K->614882K(1023936K), 0.0060564 secs]
[GC 922084K->614882K(1023936K), 0.0194009 secs]
[GC 922084K->614882K(1023936K), 0.0098730 secs]
[GC 922084K->614882K(1023936K), 0.0092623 secs]
[GC 922084K->614882K(1023936K), 0.0183072 secs]
[GC 922084K->614882K(1023936K), 0.0129153 secs]
[GC 922084K->614882K(1023936K), 0.0126555 secs]
[GC 922084K->614882K(1023936K), 0.0094753 secs]
[GC 922084K->614882K(1023936K), 0.0081199 secs]
[GC 922084K->614882K(1023936K), 0.0082226 secs]
[GC 922084K->614882K(1023936K), 0.0086055 secs]
[GC 922084K->614882K(1023936K), 0.0060333 secs]
[GC 922084K->614882K(1023936K), 0.0059859 secs]
[GC 922084K->614882K(1023936K), 0.0061077 secs]
[GC 922084K->614882K(1023936K), 0.0122591 secs]
[GC 922084K->614882K(1023936K), 0.0093945 secs]
[GC 922084K->614882K(1023936K), 0.0154644 secs]
[GC 922084K->614882K(1023936K), 0.0091777 secs]
[GC 922084K->614882K(1023936K), 0.0167363 secs]
[GC 922084K->614882K(1023936K), 0.0135420 secs]
[GC 922084K->614882K(1023936K), 0.0094573 secs]
[GC 922084K->614882K(1023936K), 0.0094246 secs]
[GC 922084K->614882K(1023936K), 0.0254695 secs]
[GC 922084K->614882K(1023936K), 0.0085689 secs]
[GC 922084K->614882K(1023936K), 0.0128948 secs]
[GC 922084K->614882K(1023936K), 0.0079153 secs]
[GC 922084K->614882K(1023936K), 0.0061276 secs]
[GC 922084K->614882K(1023936K), 0.0063098 secs]
[GC 922084K->614882K(1023936K), 0.0093720 secs]
[GC 922084K->614882K(1023936K), 0.0090353 secs]
[GC 922084K->614882K(1023936K), 0.0098287 secs]
[GC 922084K->614882K(1023936K), 0.0091603 secs]
[GC 922084K->614882K(1023936K), 0.0104143 secs]
[GC 922084K->614882K(1023936K), 0.0110244 secs]
[GC 922084K->614882K(1023936K), 0.0098762 secs]
[GC 922084K->614882K(1023936K), 0.0061013 secs]
[GC 922084K->614882K(1023936K), 0.0060609 secs]
[GC 922084K->614882K(1023936K), 0.0060423 secs]
[GC 922084K->614882K(1023936K), 0.0059602 secs]
[GC 922084K->614882K(1023936K), 0.0062546 secs]
[GC 922084K->614882K(1023936K), 0.0059596 secs]
[GC 922084K->614882K(1023936K), 0.0109878 secs]
[GC 922084K->614882K(1023936K), 0.0122848 secs]
[GC 922084K->614882K(1023936K), 0.0093104 secs]
[GC 922084K->614882K(1023936K), 0.0092046 secs]
[GC 922084K->614882K(1023936K), 0.0094092 secs]
[GC 922084K->614882K(1023936K), 0.0093374 secs]
[GC 922084K->614882K(1023936K), 0.0094644 secs]
[GC 922084K->614882K(1023936K), 0.0092431 secs]
[GC 922084K->614882K(1023936K), 0.0060115 secs]
[GC 922084K->614882K(1023936K), 0.0160455 secs]
[GC 922084K->614882K(1023936K), 0.0059583 secs]
[GC 922084K->614882K(1023936K), 0.0060500 secs]
[GC 922084K->614882K(1023936K), 0.0060738 secs]
[GC 922084K->614882K(1023936K), 0.0093804 secs]
[GC 922084K->614882K(1023936K), 0.0105561 secs]
[GC 922084K->614882K(1023936K), 0.0096183 secs]
[GC 922084K->614882K(1023936K), 0.0102065 secs]
[GC 922084K->614882K(1023936K), 0.0093412 secs]
[GC 922084K->614882K(1023936K), 0.0104163 secs]
[GC 922084K->614882K(1023936K), 0.0100231 secs]
[GC 922084K->614882K(1023936K), 0.0066017 secs]
[GC 922084K->614882K(1023936K), 0.0064099 secs]
[GC 922084K->614882K(1023936K), 0.0063137 secs]
[GC 922084K->614882K(1023936K), 0.0060494 secs]
[GC 922084K->614882K(1023936K), 0.0060199 secs]
[GC 922084K->614882K(1023936K), 0.0061103 secs]
[GC 922084K->614882K(1023936K), 0.0060526 secs]
[GC 922084K->614882K(1023936K), 0.0091443 secs]
[GC 922084K->614882K(1023936K), 0.0090115 secs]
[GC 922084K->614882K(1023936K), 0.0090539 secs]
[GC 922084K->614882K(1023936K), 0.0096985 secs]
......此处省略非常多行,我直接把进程杀了

这里演示了有做标量替换跟没做的区别:把EliminateAllocationArraySizeLimit参数设到不小于楼主例子的byte数组的大小时,标量替换就能优化掉那个数组的分配,于是可以看到我这边第一次运行就只GC了几次就完事了。而当那个参数不够大的时候标量替换就无法优化掉那个数组,就会跟原本一样不断做GC。

另外楼主请参考我之前写的另一帖:http://rednaxelafx.iteye.com/blog/1042471,也有些您会感兴趣的信息,主要是解释模式与优化编译后的代码对对象生命周期的影响。
Global site tag (gtag.js) - Google Analytics