英文原文:cubrid,编译:ImportNew- 王晓杰 html
对于Java开发人员来讲,了解垃圾回收机制(GC)有哪些好处呢?首先能够知足做为一名软件工程师的求知欲,其次,深刻了解GC如何工做能够帮你写出更好的Java应用。 java
这仅仅表明我我的的意见,但我坚信一个精通GC的人每每是一个好的Java开发者。若是你对GC的处理过程感兴趣,说明你已经具有较大规模应用的开发经验。若是你曾经想过如何正确的选择GC算法,那意味着你已经彻底理解你所开发的应用的特色。固然,咱们不能以偏概全,这不能做为评价一个好的开发人员的共通标准。可是,我要说的是,深刻理解GC是成为一名伟大的程序员的必经之路。 程序员
这是成为JavaGC专家系列文章的第一篇,本篇主要针对GC机制进行介绍,在下一篇中,咱们将重点探讨分析GC状态以及来自NHN的GC调优的例子。 算法
本文的目的是以一种简单的方式向你介绍GC机制。我但愿这些文章可以帮到你。实际上,个人学生已经在Twitter上发布了一些很好的关于Java内核的文章,而且大受欢迎。有兴趣的话,你也能够关注他们。 安全
回到正题,我们继续谈垃圾回收,在学习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“)
请看下面这个图表。
图1 : GC 空间 & 数据流
上图中的持久代( permanent generation )也被称为方法区(method area)。他用来保存类常量以及字符串常量。所以,这个区域不是用来永久的存储那些从老年代存活下来的对象。这个区域也可能发生GC。而且发生在这个区域上的GC事件也会被算为major GC。
有些人可能会问:
若是老年代的对象须要引用一个新生代的对象,会发生什么呢?
为了解决这个问题,老年代中存在一个”card table“,他是一个512 byte大小的块。全部老年代的对象指向新生代对象的引用都会被记录在这个表中。当针对新生代执行GC的时候,只须要查询card table来决定是否能够被收集,而不用查询整个老年代。这个card table由一个write barrier来管理。write barrier给GC带来了很大的性能提高,虽然由此可能带来一些开销,但GC的总体时间被显著的减小。
图 2: Card Table 结构
新生代的构成
为了更好地理解GC,咱们如今来学习新生代,新生代是用来保存那些第一次被建立的对象,他能够被分为三个空间
一共有三个空间,其中包含两个幸存者空间。每一个空间的执行顺序以下:
若是你仔细观察这些步骤就会发现,其中一个幸存者空间必须保持是空的。若是两个幸存者空间都有数据,或者两个空间都是空的,那必定标志着你的系统出现了某种错误。
经过频繁的minor GC将数据移动到老年代的过程能够用下图来描述:
图 3: GC执行先后对比
须要注意的是HotSpot虚拟机使用了两种技术来加快内存分配。他们分别是是”bump-the-pointer“和“TLABs(Thread-Local Allocation Buffers)”。
Bump-the-pointer技术跟踪在伊甸园空间建立的最后一个对象。这个对象会被放在伊甸园空间的顶部。若是以后再须要建立对象,只须要检查伊甸园空间是否有足够的剩余空间。若是有足够的空间,对象就会被建立在伊甸园空间,而且被放置在顶部。这样以来,每次建立新的对象时,只须要检查最后被建立的对象。这将极大地加快内存分配速度。可是,若是咱们在多线程的状况下,事情将大相径庭。若是想要以线程安全的方式以多线程在伊甸园空间存储对象,不可避免的须要加锁,而这将极大地的影响性能。TLABs 是HotSpot虚拟机针对这一问题的解决方案。该方案为每个线程在伊甸园空间分配一块独享的空间,这样每一个线程只访问他们本身的TLAB空间,再与bump-the-pointer技术结合能够在不加锁的状况下分配内存。
以上是针对新生代空间GC技术的简要介绍,你不须要刻意记住我刚刚提到的两种技术。不知道他们不会对你产生什么影响,可是请务必记住在对象刚刚被建立以后,是保存在伊甸园空间的。那些长期存活的对象会经由幸存者空间转存在老年代空间。
老年代GC处理机制
老年代空间的GC事件基本上是在空间已满时发生,执行的过程根据GC类型不一样而不一样,所以,了解不一样的GC类型将有助于你理解本节的内容。
JDK7一共有5种GC类型:
其中,Serial GC不该该被用在服务器上。这种GC类型在单核CPU的桌面电脑时代就存在了。使用Serial GC会显著的下降应用的性能指标。
如今,让咱们共同窗习每一种GC类型
1. Serial GC (-XX:+UseSerialGC)
新生代空间的GC方式咱们在前面已经介绍过了,在老年代空间中的GC采起称之为”mark-sweep-compact“的算法。
最后一步,从头开始,顺序地填满堆内存空间,而且将对内存空间分红两部分:一个保存着对象,另外一个空着(压缩)。
2. Parallel GC (-XX:+UseParallelGC)
图 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)
图 5: Serial GC & CMS GC
就像你从上图看到的那样, CMS GC比我以前解释的各类算法都要复杂不少。第一步初始化标记(initial mark) 比较简单。这一步骤只是查找那些距离类加载器最近的幸存对象。所以,停顿的时间很是短暂。在以后的并行标记( concurrent mark )步骤,全部被幸存对象引用的对象会被确认是否已经被追踪和校验。这一步的不一样之处在于,在标记的过程当中,其余的线程依然在执行。在从新标记(remark)步骤,会再次检查那些在并行标记步骤中增长或者删除的与幸存对象引用的对象。最后,在并行交换( concurrent sweep )步骤,转交垃圾回收过程处理。垃圾回收工做会在其余线程的执行过程当中展开。一旦采起了这种GC类型,由GC致使的暂停时间会极其短暂。CMS GC也被称为低延迟GC。它常常被用在那些对于响应时间要求十分苛刻的应用之上。
固然,这种GC类型在拥有stop-the-world时间很短的优势的同时,也有以下缺点:
在使用这个GC类型以前你须要慎重考虑。若是由于内存碎片过多而致使压缩任务不得不执行,那么stop-the-world的时间要比其余任何GC类型都长,你须要考虑压缩任务的发生频率以及执行时间。
5. G1 GC
最后,咱们来学习垃圾回收优先(G1)GC类型。
图 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的GC机制,请继续关于咱们的后续文章,咱们将会讨论如何监控Java GC状态以及优化GC。
另外,我特别推荐一本2011年12月发布的《Java性能》(Amazon,也能够经过safari在线阅读),还有在Oracle官网发布的白皮书《Java HotSpotTM虚拟机内存管理》(这本书与Java性能优化不是同一本) 做者Sangmin Lee, NHN公司,性能工程师实验室高级工程师。