论坛首页 Java企业应用论坛

HotSpot VM 内存堆的两个Servivor区

浏览 21928 次
精华帖 (1) :: 良好帖 (6) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-01-24  

   今天看了一下 HotSpot JVM (下简称JVM)的内存管理,先来简单说说我的理解吧。


   JVM将堆分成了 二个大区  Young 和 Old 如下图:
  
    



   而Young 区又分为 Eden、Servivor1、Servivor2, 两个Survivor 区相对地作为为From 和 To 逻辑区域, 当Servivor1作为 From 时 , Servivor2 就作为 To, 反之亦然

   如下图:

  


   因此当Eden区满的时候 GC执行,这时会将 Eden 区和 From 区中还被引用的对象会被移到 To区 ,个别大对象和部分From对象在To已满的情况下会被放到Old区,如下图:

  



GC操作执行完之后 Eden和 From 区将会为空(无引用对象被回收,有引用对象被移到To和Old区) ,并且From 和 To在逻辑上的 概念调换 , From 概念上变成了To,To变成了From(如果Servior1 原来作为 From 区 ,现在Servior1 现在就作为 To 区),GC执行后结果如下图:

      

HotSpot VM 内存堆的两个Servivor区

    



       第一次发贴,有什么问题请大家指正一下。有个地方不太清楚 ,为什么需要From 和 To 两个平行的区呢,为什么不直接从Survivor 移到 Old? 这样设计的好处是什么?难道是因为在移动对象的时候需要压缩调整对象空间,所以这种整体移动的设计会快一点吗?希望大家一起来讨论一下 ^_^


顺便附上资料 《内存管理白皮书》

 

   发表时间:2011-01-25  
楼主讲的挺不错的,针对你的疑问,一般new区的对象往往需要经历几次GC之后才会放入old区,一般是通过-XX:MaxTenuringThreshold这个参数来设置的GC几次才放入old区。
设计成From 和 To 两个平行的区,我觉得是为了筛选真正符合old区的要求的对象(即需要长时间持有的引用的对象),然后再将他们放入old区。
本人才疏学浅,欢迎大家拍砖,讨论。
0 请登录后投票
   发表时间:2011-01-25  
感觉这是和GC的copying算法相关吧,既然是拷贝,就得有地方做from,有地方做to?
0 请登录后投票
   发表时间:2011-01-26   最后修改:2011-01-26
cesc 写道
楼主讲的挺不错的,针对你的疑问,一般new区的对象往往需要经历几次GC之后才会放入old区,一般是通过-XX:MaxTenuringThreshold这个参数来设置的GC几次才放入old区。
设计成From 和 To 两个平行的区,我觉得是为了筛选真正符合old区的要求的对象(即需要长时间持有的引用的对象),然后再将他们放入old区。
本人才疏学浅,欢迎大家拍砖,讨论。



嗯,我想也应该是这样的。我现在的理解是这样的:
1.young 区和old 区使用的回收对象算法不一样,因为回收young 区满了需要回收时,Old不需要被回收,而当Old区满了要回收对象时,整个内存堆都要清理,而且使用者可以设置 young区和old区的回收是多线程还是单线程的,所以设计者是希望对象能够多点时间留在young 区,以提高回收对象的效率。

2.当回收非引用对象后,还需要对内存区进行压缩(使引用对象在内存排列更紧凑,以提高新对象申请内存的速度),如果使用一个区会使压缩操作效率变慢同时也不方便。

基于以上两点,设计者将Servivor分成两个平行的区,分别交替操作。这个是我个人的理解,不知道是否正确
0 请登录后投票
   发表时间:2011-01-26  
haigui.chen 写道
感觉这是和GC的copying算法相关吧,既然是拷贝,就得有地方做from,有地方做to?

嗯,正是如此。在原始的copying收集算法里,空间被分为两半,叫做semispace。空间分配和回收的过程就是把其中一半用作from来分配空间,当from快满或满足别的一些条件时将可到达的对象复制到to,并将from与to逻辑交换的过程。

单纯的copying收集不能很好的应对长时间存活的对象,因为那样的对象每次经历收集的时候都还活着,带来拷贝的开销。出于权衡,HotSpot里目前除G1外都采用相似的方式实现分代式GC,并且在young gen都使用copying收集算法。不过它使用的copying算法是原始算法的变种,留一块较大的区域作为eden,在eden与old gen之间设置semispace来作为缓冲,让“中等寿命”的对象尽量在进入old gen之前被收集掉。这就是HotSpot的survivor spaces了。

嗯虽说是相似的方式,不过HotSpot里Parallel Scavenge与另外几种GC在实现上有点不同。其它的几种是共用了一套从JDK 1.4开始用的分代式GC框架,PS则是自己实现了类似的功能而没用那套框架。
0 请登录后投票
   发表时间:2011-01-27  
RednaxelaFX 写道
haigui.chen 写道
感觉这是和GC的copying算法相关吧,既然是拷贝,就得有地方做from,有地方做to?

嗯,正是如此。在原始的copying收集算法里,空间被分为两半,叫做semispace。空间分配和回收的过程就是把其中一半用作from来分配空间,当from快满或满足别的一些条件时将可到达的对象复制到to,并将from与to逻辑交换的过程。

单纯的copying收集不能很好的应对长时间存活的对象,因为那样的对象每次经历收集的时候都还活着,带来拷贝的开销。出于权衡,HotSpot里目前除G1外都采用相似的方式实现分代式GC,并且在young gen都使用copying收集算法。不过它使用的copying算法是原始算法的变种,留一块较大的区域作为eden,在eden与old gen之间设置semispace来作为缓冲,让“中等寿命”的对象尽量在进入old gen之前被收集掉。这就是HotSpot的survivor spaces了。

嗯虽说是相似的方式,不过HotSpot里Parallel Scavenge与另外几种GC在实现上有点不同。其它的几种是共用了一套从JDK 1.4开始用的分代式GC框架,PS则是自己实现了类似的功能而没用那套框架。


请教下前辈们:

1.手动System.gc()与JVM自动gc有什么根本上的区别么?在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。

2.用户能自己设置JVM选择何种GC算法来进行GC么?
0 请登录后投票
   发表时间:2011-01-27  
我没看过GC算法,不过对于LS说置为NULL就马上释放的问题,我认为是引用计数的问题,如果该对象还在别的地方有引用也是不会马上释放的。
0 请登录后投票
   发表时间:2011-01-27  
phyeas 写道
我没看过GC算法,不过对于LS说置为NULL就马上释放的问题,我认为是引用计数的问题,如果该对象还在别的地方有引用也是不会马上释放的。


引用计数的问题?呵呵,能说说它底层的实现机制吗?
因为随便写一个main()中,new一个对象(最好是图片对象,够大),再设置为null。
整个过程使用
Runtime.getRuntime().totalMemory();
Runtime.getRuntime().freeMemory();
来监控内存使用数。
看到内存使用数的变化,如果是引用计数的问题,难道这个数据是虚的?
0 请登录后投票
   发表时间:2011-01-27  
kingkan 写道

请教下前辈们:

1.手动System.gc()与JVM自动gc有什么根本上的区别么?在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。

2.用户能自己设置JVM选择何种GC算法来进行GC么?

1,System.gc()只是提醒JVM去花点精力去回收,具体JVM回收不回收那要看JVM自己啦,你把object设置为null,内存被释放那是JVM去回收了...

2,可以设置JVM选择何种GC算法的,你自己参考LZ的附件...
0 请登录后投票
   发表时间:2011-01-28   最后修改:2011-05-09
kingkan 写道
请教下前辈们:

1.手动System.gc()与JVM自动gc有什么根本上的区别么?在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。

2.用户能自己设置JVM选择何种GC算法来进行GC么?

1、根本的区别之一:System.gc()可能在自动GC原本不会进入GC的位置上进入GC。
正常情况下,Java代码要尝试在GC堆上分配空间的时候才会触发GC;换句话说,基本上是“new”的时候才会触发GC。但System.gc()、JVMTI的强制GC等动作都在正常情况之外提示系统要做一次GC。
kingkan 写道
在程序里面一个对象用完的时候,马上使用System.gc(),通过内存使用数据查看,该对象的内存是还没释放的。但是对象使用完,马上设置为null,再System.gc(),该对象的内存就被释放了,不解。

这个情况即便在HotSpot上也不一定,要看被测的方法有没有被JIT编译过。解释执行的时候基本上没啥优化,所以局部变量的存活范围跟源码里看起来是一致的。但因为HotSpot的JIT编译带有优化,一个局部变量在一个方法里被使用过的赋值才会有效,而未被使用过的赋值很可能被消除掉。这样,在方法最后把局部变量设为null就是徒劳的,赋值动作本身都会被消除。事实上不需要额外的赋值为null的动作,JIT编译器也会尽可能的缩小变量的有效范围,所以完全没必要在方法末尾将局部变量置null。
在.NET的CLR上,由于方法总是要被编译了才可以执行(AOT或者JIT),而且编译也带有优化,源码里变量的引用状况跟实际运行时的引用状况的差异可能更明显些,像这个例子就比较极端。

话说回来,Runtime.totalMemory()和Runtime.freeMemory()都是很RP的方法,其实并不适合细粒度观察…不知道您是用什么方式来“通过内存使用数据查看,该对象的内存是还没释放的”呢?
HotSpot默认会对方法调用次数的计数器做“衰减”,每进一次GC就会检查是否已到半衰周期,到了就会把所有方法的调用次数的计数器减半。如果写microbenchmark的话,在被测的方法里插入System.gc()很可能会带来干扰,使被测方法的调用次数始终达不到编译的条件,导致其不被JIT编译。要禁用计数器衰减的话,启动VM的时候要给参数-XX:-UseCounterDecay。要确认某个方法有没有被JIT编译请使用-XX:+PrintCompilation
靠microbenchmark来观察某个对象有没有被GC回收多半是不准确的。可以具体情况具体分析。

另外值得注意的是,System.gc()不一定是触发所谓的“full GC”或者叫“major GC”。
在Sun JDK6与OpenJDK 6的HotSpot里,"GCCause_java_lang_system_gc"的时候,如果VM启动参数DisableExplicitGC为false,则会触发一次full GC,如果该参数为true则完全不触发任何GC。要将这个参数设置为true,启动的时候写上-XX:+DisableExplicitGC就行。
HotSpot对System.gc()有特别处理,最主要的地方体现在一次System.gc()是否与普通GC一样会触发GC的统计/阈值数据的更新——HotSpot里的许多GC算法都带有自适应的功能,会根据先前收集的效率来决定接下来的GC中使用的参数,但System.gc()默认不更新这些统计数据,避免用户强行调用GC对这些自适应功能的干扰(可以参考HotSpot的UseAdaptiveSizePolicyWithSystemGC参数,默认是false)。除此之外,在HotSpot里,System.gc()所触发的full GC跟普通的full GC没啥大差别。
当使用Concurrent Mark-Sweep (CMS)时,可以通过-XX:+ExplicitGCInvokesConcurrent-XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 参数来指定System.gc()触发并发GC而不是full GC。在CMS中,并发GC不对old gen做压缩,而full GC是stop-the-world的并且会做压缩。

------------------

在Oracle JRockit里,System.gc()触发的是nursery GC(如果选择了分代GC的话;如果选择的不是分代式GC算法则谈不上nursery还是old)。与HotSpot相同,可以通过一个参数禁用System.gc():-XXnoSystemGC。也可以通过另一个参数来强制System.gc()做full GC:-XXfullSystemGC
JRockit R28里,禁用System.gc()的推荐参数是-XX:AllowSystemGC=false,而设定System.gc()触发full GC的参数是-XX:FullSystemGC=true

------------------

在IBM JDK的JVM里,System.gc()同样可以禁用——使用-Xdisableexplicitgc参数。另外也有一些可以调节System.gc()触发的GC内容的参数,如-Xcompactexplicitgc、-Xnocompactexplicitgc之类。

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

2、对OpenJDK 6里的HotSpot VM,请看这个文件,grep出/Use.*GC,/就知道了:
curl 'http://hg.openjdk.java.net/jdk6/jdk6/hotspot/raw-file/tip/src/share/vm/runtime/globals.hpp' | grep -A 2 -E 'Use.*GC,'

  product(bool, UseSerialGC, false,                                         \
          "Use the serial garbage collector")                               \
                                                                            \
  product(bool, UseG1GC, false,                                             \
          "Use the Garbage-First garbage collector")                        \
                                                                            \
  product(bool, UseParallelGC, false,                                       \
          "Use the Parallel Scavenge garbage collector")                    \
                                                                            \
  product(bool, UseParallelOldGC, false,                                    \
          "Use the Parallel Old garbage collector")                         \
                                                                            \
--
  product(bool, UseMaximumCompactionOnSystemGC, true,                       \
          "In the Parallel Old garbage collector maximum compaction for "   \
          "a system GC")                                                    \
--
  product(bool, UseConcMarkSweepGC, false,                                  \
          "Use Concurrent Mark-Sweep GC in the old generation")             \
                                                                            \
--
  develop(bool, UseAsyncConcMarkSweepGC, true,                              \
          "Use Asynchronous Concurrent Mark-Sweep GC in the old generation")\
                                                                            \
--
  product(bool, UseParNewGC, false,                                         \
          "Use parallel threads in the new generation.")                    \
                                                                            \
--
  product(bool, UseAdaptiveSizePolicyWithSystemGC, false,                   \
          "Use statistics from System.GC for adaptive size policy")         \
                                                                            \

这样grep出来的启动参数中,UseMaximumCompactionOnSystemGC和UseAdaptiveSizePolicyWithSystemGC不是选择GC算法类型的参数,另外几个都是。它们分别是
·UseSerialGC
·UseG1GC
·UseParallelGC
·UseParallelOldGC
·UseAsyncConcMarkSweepGC(产品模式不可调)
·UseConcMarkSweepGC
·UseParNewGC
它们之间的关系请参考:Jon Masamitsu: Our Collectors
Jon Masamitsu 写道


Sun(=> Oracle)的产品版JDK 6里的HotSpot同上。

------------------

JRockit R28的话,GC算法的基本设定可以用下面几个参数:
-Xgc:singlecon
-Xgc:gencon
-Xgc:singlepar
-Xgc:genpar
不过更推荐并且也更简单的是设定优化的目标,例如这几个参数:
-XgcPrio:throughput
-XgcPrio:pausetime
-XgcPrio:deterministic

老帖描述了当时的JRockit该如何选择GC实现

------------------

IBM J9有诸如下面几种设定GC算法的VM参数:
-Xgcpolicy:optthruput
-Xgcpolicy:optavgpause
-Xgcpolicy:gencon
-Xgcpolicy:subpool
1 请登录后投票
论坛首页 Java企业应用版

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