<2-2> 垃圾收集器与内存分配

[b]2.4 垃圾收集器[/b]
如果说垃圾收集算法是内存回收的[b]方法论[/b],垃圾收集器就是内存回收的[b]具体实现[/b]。Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商,不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别,并且一般都会提供参数共用户根据自己的应用特点和要求组合出各个年代所使用的收集器。这里讨论的收集器基于Sun HotSpot虚拟机1.6版Update 22,这个虚拟机包含的所有收集器如图:
[img]http://dl2.iteye.com/upload/attachment/0100/4511/b8e14f95-4cf8-3d13-be1a-970fd1fc5006.bmp[/img]

上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明他们可以搭配使用。具体收集器的实现和特点这里就不分析了,需要的话可以查看详细资料。

[b]2.5 内存分配与回收策略 ---回答何时回收[/b]
上一篇《2-1》里解答了java内存的两个问题,回收哪些内存,如何回收内存。这一小节重点讲述内存分配,但也间接回答了何时回收内存。
Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:[color=red]给对象分配内存以及回收分配给对象的内存。[/color]关于回收内存这一点,我们已经介绍了挺多,现在我们再看下给对象分配内存的那点事。

对象的内存分配,往大方向说,就是堆上分配(但也可能经过JIT编译后被拆散为标量类型并间接地在栈上分配),对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配。少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其[b]细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存有关的参数设置。[/b]
接下来我们将会介绍几条最普遍的内存分配规则。

[color=red][b]2.5.1 对象优先在Eden分配[/b][/color]
在大多数情况下,对象在新生代Eden区中分配。[color=red]当Eden区没有足够空间进行分配时,虚拟机就发起一次Minor GC。[/color]
虚拟机提供了-XX:+PrintGCDetails这个收集器[b]日志[/b]参数,告诉虚拟机在发生垃圾收集行为时打印内存回收日志,并且在进程退出时输出当前内存各区域的分配情况。在实际应用中,内存回收日志一般是打印到文件后通过日志工具进行分析。
下面从实例验证内存分配、Minor GC的时机以及学习查看GC日志。这里先介绍下Minor GC和Full GC:
[color=red][b](1) Minor GC,就是新生代GC[/b][/color]。指发生在新生代的垃圾收集动作,因为java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。
[color=red][b](2) Full GC, 也叫Major GC, 老年代GC[/b][/color]。指发生在老年代的GC,出现了Major GC, 经常伴随至少一次Minor GC(但也不是绝对的,在ParallelScavenge收集器的收集策略里就有直接进行Major GC的策略选择过程)。Major GC的速度一般会比Minor GC慢10倍以上。
[img]http://dl2.iteye.com/upload/attachment/0100/4513/3872d5af-07af-378b-934f-df590996b4d5.bmp[/img]

上面例子尝试分配3个2MB的对象和1个4MB的对象。在运行时通过-Xmx20M, -Xms20M和-Xmn10M这3个参数限制java堆大小为20M,且不可扩展,其中10M分配给新生代,剩下的10M分配给老年代。默认的-XX:SurvivorRatio=8决定了新生代中Eden区与一个Survivor区的空间比例是8比1,新生代总可用空间为9M。

理论分析上面的代码,执行testAllocation()中分配allocation4对象的语句时会发生一次MinorGC,因为分配4M内存时发现Eden已经被占用了6M, 可用的只有3M,因此发生了Minor GC。GC期间又发现已有的3个2MB的对象全部无法放入1MB的Survivor空间,所以只好通过分配担保机制提前转移到老年代中。
这次GC结束后,4MB的allocation4对象被顺利分配在Eden中。因此程序执行完的结果是Eden占用4MB, Survivor空闲,老年代被占用6MB。
现在查看运行结果输出的GC日志:
[img]http://dl2.iteye.com/upload/attachment/0100/4516/f0acd2e3-d6af-3082-8613-f682aca25ae9.bmp[/img]

从GC日志我们看出,垃圾回收后新生代内存明显减少,但总堆区内存变化不大,主要是6M内存从新生代转移到了老年代。
关于GC日志的查看,可以参考:
[url]http://blog.csdn.net/huangzhaoyang2009/article/details/11860757[/url]
[url]http://wenku.baidu.com/link?url=HNGi_DOsSPWnxFyXFXpyB1gkNpndcgOom6kwU22KSnuTasH8hxZvzf_xfY4Au21vOc60HMlXWXyFYCXyDjJykPM4QpPK-6Ob6bikfGIN4Y_[/url]
简单的日志可以人工阅读,复杂的日志还是要借助专门的gC日志分析工具来分析。

[color=red][b]2.5.2 大对象直接进入老年代[/b][/color]
所谓大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串及数组。[b]大对象对虚拟机的内存分配来说就是一个坏消息[/b](比遇到一个大对象更加坏的消息就是遇到一群“[color=red]朝生夕灭”的“短命大对象”,写程序的时候应当避免[/color]),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来安置它们。
虚拟机提供了一个-XX:PretenureSizeThreshold参数(只是对Serial和ParNew两款收集器有效),令大于这个设置值得对象直接到老年代中分配。这样做的目的是避免在Eden区以及两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。

[color=red][b]2.5.3 长期存活的对象将进入老年代[/b][/color]
虚拟机既然采用了分代收集的思想来管理内存,那内存回收时就必须能识别哪些对象应当放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给这个对象定义了一个[b]对象年龄(Age)计数器[/b]。如果对象在Eden出生并经过第一次Minor GC仍然存活,并且能被Survivor容纳的话,将被[color=red]移动到Survivor空间中,并将对象年龄设为1[/color].对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁)时,就会晋升到老年代中。对象晋升老年代的年龄阀值,可以通过-XX:MaxTenuringThreshold设置。

[color=red][b]2.5.4 动态对象年龄判定[/b][/color]
为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无需等到maxTenuringThreshlold中要求的年龄。

[color=red][b]2.5.5 空间分配担保[/b][/color]
在发生Minor GC时,虚拟机会检测之前每次晋升到老年代的平均大小是否大于老年代剩余空间大小,如果大于,则改为直接进行一次Full GC。如果小于,则查看HandlePromotionFailure设置是否允许担保失败;如果允许,那只会进行Minor GC;如果不允许,那也要改为进行一次Full GC。
前面提到过,[color=red]新生代使用复制收集算法,但为了内存利用率[/color],只使用其中一个Survivor空间来作为轮换备份,因此当出现大量对象在Minor GC后仍然存活的情况时(最极端就是内存回收后新生代中所有对象都存活),就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代。与生活中的贷款担保类似,老年代要进行这样的担保,前提是老年代本身还有容纳这些对象的剩余空间,一共有多少对象会活下来,在实际完成内存回收之前是无法明确知道的,所以只好取之前每一次回收晋升到老年代对象容量的平均大小值作为经验值,与老年代的剩余空间进行比较,决定是否进行full GC来让老年代腾出更多空间。

取平均值进行比较其实仍然是一种动态概率的手段,也就是说如果某次Minor GC存活后的对象突增,远远高于平均值的话,依然会导致担保失败,那就只好在失败后重新出发一次Full GC。虽然担保失败时绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开,避免full GC过于频繁。

[b]2.6 小结[/b]
《2-1》和本文《2-2》介绍了垃圾收集的算法思想,以及java虚拟机中自动该内存分配及回收的主要规则。
内存回收与垃圾收集器在很多时候都是影响系统性能,并发能力的主要因素之一,虚拟机之所以提供多种不同的收集器及大量的调节参数,是因为只有根据实际应用需求,实现方式选择最优的收集方式才能获取最好的性能。没有固定收集器,参数组合,也没有最优的调优方法,虚拟机也没有什么必然的内存回收行为。因此学习虚拟机内存知识,如果要用到实践调优阶段,必须了解每个具体收集器的行为,优势和劣势、调节参数。

参考资料:
《深入理解java虚拟机》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值