对于Java开发人员来说,了解垃圾回收机制(GC)有哪些好处呢先可以满足作为一名软件工程师的求知欲,其次,深入了解GC如何工作可以帮你写出更好的Java应用。
这仅仅代表我个人的意见,但我坚信一个精通GC的人往往是一个好的Java开发者。如果你对GC的处理过程感兴趣,说明你已经具备较大规模应用的开发经验。如果你曾经想过如何正确的选择GC算法,那意味着你已经完全理解你所开发的应用的特点。当然,我们不能以偏概全,这不能作为评价一个好的开发人员的共通标准。但是,我要说的是,深入理解GC是成为一名伟大的程序员的必经之路。
这是成为JavaGC专家系列文章的第一篇,本篇主要针对GC机制进行介绍,在下一篇中,我们将重点探讨分析GC状态以及来自NHN的GC调优的例子。
回到正题,咱们继续谈垃圾回收,在学习GC之前,你首先应该记住一个单词:“stop-the-world”。Stop-the-world会在任何一种GC算法中发生。Stop-the-world意味着 JVM 因为要执行GC而停止了应用程序的执行。当Stop-the-world发生时,除了GC所需的线程以外,所有线程都处于等待状态,直到GC任务完成。GC优化很多时候就是指减少Stop-the-world发生的时间。
按代的垃圾回收机制
在Java程序中不能显式地分配和注销内存。有些人把相关的对象设置为null或者调用System.gc()来试图显式地清理内存。设置为null至少没什么坏处,但是调用System.gc()会显著地影响系统性能,必须彻底杜绝(还好,我还没有见到NHN的哪个开发者调用这个方法)。
在Java中,开发人员无法直接在程序代码中清理内存,而是由垃圾回收器自动寻找不必要的垃圾对象,并且清理掉他们。垃圾回收器会在下面两种假设(hypotheses)成立的情况下被创建(称之为假设不如改为推测(suppositions)或者前提(preconditions))。
- 大多数对象会很快变得不可达
- 只有很少的由老对象(创建时间较长的对象)指向新生对象的引用
这些假设我们称之为弱年代假设( weak generational hypothesis)。为了强化这一假设,HotSpot虚拟机将其物理上划分为两个区域:年轻代(young generation)和年老代(old generation)。
年轻代(Young generation): 绝大多数最新被创建的对象会被分配到这里,由于大部分对象在创建后会很快变得不可到达,所以很多对象被创建在年轻代,然后消失。对象从这个区域消失的过程我们称之为”minor GC“。
年老代(Old generation): 对象没有变得不可达,并且从新生代中存活下来,会被拷贝到这里。其所占用的空间要比新生代多。也正由于其相对较大的空间,发生在年老代上的GC要比年轻代少得多。对象从老年代中消失的过程,我们称之为”major GC“(或者”full GC“)
请看下面这个图表。
图 2: Card Table 结构
年轻代的构成
为了更好地理解GC,我们现在来学习年轻代,
一共有三个空间,其中包含两个幸存者空间。每个空间的执行顺序如下:
- 绝大多数刚刚被创建的对象会存放在伊甸园空间。
- 在伊甸园空间执行了第一次GC之后,存活的对象被移动到其中一个幸存者空间。
- 此后,在伊甸园空间执行GC之后,存活的对象会被堆积在同一个幸存者空间。
- 当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。之后会清空已经饱和的那个幸存者空间。
- 在以上的步骤中重复几次依然存活的对象,就会被移动到老年代。
如果你仔细观察这些步骤就会发现,其中一个幸存者空间必须保持是空的。如果两个幸存者空间都有数据,或者两个空间都是空的,那一定标志着你的系统出现了某种错误。
通过频繁的minor GC将数据移动到年老代的过程可以用下图来描述:
图 4: Serial GC 与 Parallel GC的区别
从上图中,你可以轻易地看出serial GC和parallel GC的区别,serial GC只使用一个线程执行GC,而parallel GC使用多个线程,因此parallel GC更高效。这种GC在内存充足以及多核的情况下会很有用,因此我们也称之为”throughput GC“。
3. Parallel Old GC(-XX:+UseParallelOldGC)
Parallel Old GC在JDK5之后出现。与parallel GC相比,唯一的区别在于针对老年代的GC算法。Parallel Old GC分为三步:标记-汇总-压缩(mark – summary – compaction)。汇总(summary)步骤与清理(sweep)的不同之处在于,其将依然幸存的对象分发到GC预先处理好的不同区域,算法相对清理来说略微复杂一点。
4. CMS GC (-XX:+UseConcMarkSweepGC)
图 6: G1 GC的结构
如果你想要理解G1,首先你要忘记你所学过的新生代和老年代的概念。正如你在上图所看到的,每个对象被分配到不同的格子,随后GC执行。当一个区域装满之后,对象被分配到另一个区域,并执行GC。这中间不再有从新生代移动到老年代的三个步骤。这个类型是为了替代CMS GC而被创建的,因为CMS GC在长时间持续运作时会产生很多问题。
G1最大的好处是性能,他比我们在上面讨论过的任何一种GC都要快。但是在JDK 6中,他还只是一个早期试用版本。在JDK7之后才由官方正式发布。就我个人看来,NHN在将JDK 7正式投入商用之前需要很长的一段测试期(至少一年)。因此你可能需要再等一段时间。并且,我也听过几次使用了JDK 6中的G1而导致Java虚拟机宕机的事件。请耐心的等到它更稳定吧。
下一次我将讨论GC优化相关的问题,但是在此之前我要先明确一件事情,假如应用中创建的所有对象的大小和类型都是统一的,那么公司使用的WAS的GC参数可以是相同的。但是WAS所创建对象的大小和生命周期根据服务以及硬件的不同而不同。换句话说,不能因为某个应用使用的GC参数“A”,就说明同样的参数也能给其他服务带来最佳的效果。而是要因地制宜,有的放矢。我们需要找到适合每个WAS线程的参数,并且持续的监控和优化每个设备上的WAS实例。这并不是我的一家之谈,而是负责Oracle Java虚拟机研发的工程师在 JavaOne 2010上已经讨论过的。
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览91438 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!