原文链接:http://www.cubrid.org/blog/de...

了解Java的垃圾回收(GC)原理能给我们带来什么好处?对于软件工程师来说,满足技术好奇心可算是一个,但重要的是理解GC能帮忙我们更好的编写Java应用程序。

上面是我个人的主观的看法,但我相信熟练掌握GC是成为优秀Java程序员的必备技能。如果你对GC执行过程感兴趣,也许你只是有一定的开发应用的经验;如果你仔细考虑过如何选择合适的GC算法,说明你对你所开发的程序有了全面的了解。当然这对一个优秀的程序员来说未必是一个通用的标准,但很少人会反对我关于"理解GC是作为优秀Java程序员的必备技能"的看法。

本文是成为Java GC专家系列的第一篇。我将先对GC做一下基本的概述,在下一篇文章中,我将讲述如何分析GC状态以及通过[NHN]()的案例介绍GC调优相关的内容。

本文的目的是以通俗的方式为你介绍GC概念。我希望本文会对你有所帮助。事实上,我的同事们已经发表了一些在Twitter上非常受关注的优秀文章,你同样也可以拿来参考。

回到垃圾回收上,在开始学习GC之前你应该知道一个词:stop-the-world。不管选择哪种GC算法,stop-the-world都是不可避免的。Stop-the-world意味着从应用中停下来并进入到GC执行过程中去。一旦Stop-the-world发生,除了GC所需的线程外,其他线程都将停止工作,中断了的线程直到GC任务结束才继续它们的任务。GC调优通常就是为了改善stop-the-world的时间。

基于的分代理论的垃圾回收

在Java程序里不需要显式的分配和释放内存。有些人通过给对象赋值为null或调用System.gc()以期望显式的释放内存空间。给对象设置null虽没什么用,但问题不会太大;如果调用了System.gc()却可能会为系统性能带来严重的波动,即便调用System.gc()系统也未必立即响应去执行垃圾回收。(所幸的是,在NHN未曾看到有工程师这么做。)

在使用Java时,程序员不需要在程序代码中显式的释放内存空间,垃圾回收器会帮你找到不再需要的(垃圾)对象并把他们移出。垃圾回收器的创建基于以下两个假设(也许称之为推论或前提更合适):

  • 大多数对象的很快就会变得不可达

  • 只有极少数情况会出现旧对象持有新对象的引用

这两条假设被称为"弱分代假设"。为了证明此假设,在HotSpot VM中物理内存空间被划分为两部分:新生代(young generate)老年代(old generation)

新生代:大部分的新创建对象分配在新生代。因为大部分对象很快就会变得不可达,所以它们被分配在新生代,然后消失不再。当对象从新生代移除时,我们称之为"minor GC"。

老年代:存活在新生代中但未变为不可达的对象会被复制到老年代。一般来说老年代的内存空间比新生代大,所以在老年代GC发生的频率较新生代低一些。当对象从老年代被移除时,我们称之为"major GC"(或者full GC)。

看一下下图的示意:


图1:GC区域和数据流向

图中的permanent generation称为方法区,其中存储着类和接口的元信息以及interned的字符串信息。所以这一区域并不是为老年代中存活下来的对象所定义的持久区。方法区中也会发生GC,这里的GC同样也被称为major GC

有些人可能认为:

如果老年代的对象需要持有新生代对象的引用怎么办?

为了处理这种场景,在老年代中设计了"索引表(card table)",是一个512字节的数据块。不管何时老年代需要持有新生代对象的引用时,都会记录到此表中。当新生代中需要执行GC时,通过搜索此表决定新生代的对象是否为GC的目标对象,从而降低遍历所有老年代对象进行检查的代价。该索引表使用写栅栏(write barrier)进行管理。wite barrier是一个允许高性能执行minor GC的设备。尽管它会引入一定的开销,却能带来总体GC时间的大幅降低。


图2:索引表结构

新生代的结构

为了深入理解GC,我们先从新生代开始学起。所有的对象在初始创建时都会被分配在新生代中。新生代又可分为三个部分:

  • 一个Eden

  • 两个Survivor

在三个区域中有两个是Survivor区。对象在三个区域中的存活过程如下:

  1. 大多数新生对象都被分配在Eden区。

  2. 第一次GC过后Eden中还存活的对象被移到其中一个Survivor区。

  3. 再次GC过程中,Eden中还存活的对象会被移到之前已移入对象的Survivor区。

  4. 一旦该Survivor区域无空间可用时,还存活的对象会从当前Survivor区移到另一个空的Survivor区。而当前Survivor区就会再次置为空状态。

  5. 经过数次在两个Survivor区域移动后还存活的对象最后会被移动到老年代。

如上所述,两个Survivor区域在任何时候必定有一个保持空白。如果同时有数据存在于两个Survivor区或者两个区域的的使用量都是0,则意味着你的系统可能出现了运行错误。

下图向你展示了经过minor GC把数据迁移到老年代的过程:


图3: GC前后

在HotSpot VM中,使用了两项技术来实现更快的内存分配:"指针碰撞(bump-the-pointer)"和"TLABs(Thread-Local Allocation Buffers)"。

Bump-the-pointer技术会跟踪在Eden上新创建的对象。由于新对象被分配在Eden空间的最上面,所以后续如果有新对象创建,只需要判断新创建对象的大小是否满足剩余的Eden空间。如果新对象满足要求,则其会被分配到Eden空间,同样位于Eden的最上面。所以当有新对象创建时,只需要判断此新对象的大小即可,因此具有更快的内存分配速度。然而,在多线程环境下,将会有别样的状况。为了满足多个线程在Eden空间上创建对象时的线程安全,不可避免的会引入锁,因此随着锁竞争的开销,创建对象的性能也大打折扣。在HotSpot中正是通过TLABs解决了多线程问题。TLABs允许每个线程在Eden上有自己的小片空间,线程只能访问其自己的TLAB区域,因此bump-the-pointer能通过TLAB在不加锁的情况下完成快速的内存分配。

本小节快速浏览了新生代上的GC知识。上面讲的两项技术无需刻意记忆,只需要明白对象开始是创建在Eden区,然后经过在Survivor区域上的数次转移而存活下来的长寿对象最后会被移到老年代。

老年代垃圾回收

当老年代数据满时,便会执行老年代垃圾回收。根据GC算法的不同其执行过程也会有所区别,所以当你了解了每种GC的特点后再来理解老年代的垃圾回收就会容易很多。

在JDK 7中,内置了5种GC类型:

  1. Serial GC

  2. Parallel GC

  3. Parallel Old GC(Parallel Compacting GC)

  4. Concurrent Mark & Sweep GC (or "CMS")

  5. Garbage First (G1) GC

其中Serial GC务必不要在生产环境的服务器上使用,这种GC是为单核CPU上的桌面应用设计的。使用Serial GC会明显的损耗应用的性能。

下面分别介绍每种GC的特性。

Serial GC(-XX:+UseSerialGC)

在前面介绍的年轻代垃圾回收中使用了这种类型的GC。在老年代,则使用了一种称之为"mark-sweep-compact"的算法。

  1. 首先该算法需要在老年代中标记出存活着的对象

  2. 然后从前到后检查堆空间中存活的对象,并保持位置不变(把不再存活的对象清理出堆空间,称为空间清理)

  3. 最后,把存活的对象移到堆空间的前面部分以保持已使用的堆空间的连续性,从而把堆空间分为两部分:有对象的和无对象的(称为空间压缩)

Serial GC适用于CPU核数较少且使用的内存空间较小的场景。

Parallel GC(-XX:+UseParallelGC)


图4:Serial GC与Parallel GC的区别

图中可以容易的看出serial GC与parallel GC的区别。Serial GC使用单一线程执行GC,而parallel GC则使用多个线程并发执行,因此parallel GC 较serial GC具有更快的速度。Parallel GC适用于多核CPU且使用了较大内存空间的场景。Parallel GC又被称为"高吞吐GC(throughput GC)"

Parallel Old GC(-XX:+UseParallelOldGC)

Parallel Old GC在JDK 5中被引入,与Parallel GC相比唯一的区别在于Parallel的GC算法是为老年代设计的。它的执行过程分为三步:标记(mark)--总结(summary)--压缩(compaction)。其中summary步骤会会分别为存活的对象在已执行过GC的空间上标出位置,因此与mark-sweep-compact算法中的sweep步骤有所区别,并需要一些复杂步骤才能完成。

CMS GC(-XX:+UseConcMarkSweepGC)


图5:Serial GC与CMS GC

从图上可看出并发标记-清理(Concurrent Mark-Sweep) GC比以后上其他GC都要复杂。开始时的初始标记(initial mark)比较简单,只有靠近类加载器的存活对象会被标记,因此停顿时间(stop-the-world)比较短暂。在并发标记(concurrent mark)阶段,由刚被确认和标记过的存活对象所关联的对象将被会跟踪和检测存活状态。此步骤的不同之处在于有多个线程并行处理此过程。在重标记(remark)阶段,由并发标记所关联的新增或中止的对象瘵被会检测。在最后的并发清理(concurrent sweep)阶段,垃圾回收过程被真正执行。在垃圾回收执行过程中,其他线程依然在执行。得益于CMS GC的执行方式,在GC期间系统中断时间非常短暂。CMS GC也被称为低延迟GC,适用于所有应用对响应时间要求比较严格的场景

CMS GC虽然具有中断时间断的优势,其缺点也比较明显:

  • 与其他GC相比,CMS GC要求更多的内存空间和CPU资源

  • CMS GC默认不提供内存压缩

使用CMS GC之前需要对系统做全面的分析。另外为了避免过多的内存碎片而需要执行压缩任务时,CMS GC会比任何其他GC带来更多的stop-the-world时间,所以你需要分析和判断压缩任务执行的频率及其耗时情况。

G1 GC

最后我们学习有关G1垃圾回收的介绍。


图6:G1 GC的布局

如果你想清晰的理解GC,请先忘记上面介绍的有关新生代和老年代的知识。如上图所示,每个对象在创建时会分析到一个格子中,后续的GC也是在格子中完成的。每当一个区域分配满对象后,新创建的对象就会分配到另外一个区域,并开始执行GC。在这种GC中不会出现其他GC中的对象在新生代和老生代三区域中移动的现象。G1是为了取代在长期使用中暴露出大量问题且饱受抱怨的CMS GC。

G1最大的改进在于其性能表现,它比以上任何一种GC都更快速。它在JDK6中以早期版本的形式释放出来以用于测试,它真正的发布是在JDK7中。我个人认为在NHN真正在生产环境使用JDK7至少还需要1年的测试时间,所以还需要等待一段时间。并且我听说在JDK6中使用G1偶尔会出现JVM崩溃现象。所以稳定版尚需时日。

接下来的文章中会讲解GC调优,但我想先提一个问题。如果应用中所有对象的类型和大小都是一样的,WAS上使用的GC可以设置相同的GC选项。如果在WAS上创建的对象的大小和生命周期各不相同的对象,配置的GC选项也各不相同。换名话说,不能因为一个服务使用了GC选项"A",其他的不同服务使用相同的选项"A"也能获取最好的表现。所以为了找到WAS线程的最佳值,每个WAS实例需要通过持续的调优和监控以便找到最优的配置和GC优项。这不只是来自我的个人经验,而是来自于JavaOne 2010上工程师们对于Oracle JVM讨论后的一致看法。

本节我们只简单介绍Java中的GC基础。下一章节,我将会讨论关于如何监控GC状态以及如何做性能调优。

[译]GC专家系列1:理解Java垃圾回收相关推荐

  1. [译]GC专家系列2:Java 垃圾回收的监控

    原文链接:http://www.cubrid.org/blog/dev-platform/how-to-monitor-java-garbage-collection/ 这是GC专家系列文章的第二篇. ...

  2. [译]GC专家系列5-Java应用性能优化的原则

    原文链接:http://www.cubrid.org/blog/dev-platform/the-principles-of-java-application-performance-tuning/ ...

  3. [译]GC专家系列4-Apache的MaxClients设置及其对Tomcat Full GC的影响

    2 原文链接:http://www.cubrid.org/blog/dev-platform/maxclients-in-apache-and-its-effect-on-tomcat-during- ...

  4. [译]GC专家系列3-GC调优

    原文链接:http://www.cubrid.org/blog/dev-platform/how-to-tune-java-garbage-collection/ 本篇是GC专家系列的第三篇.在第一篇 ...

  5. [牛感悟系列]JAVA(1)理解JAVA垃圾回收

    理解JAVA垃圾回收的好处是什么?满足求知欲是一方面,编写更好的JAVA应用是另外一方面. 如果一个人对垃圾回收过程感兴趣,那表明他在应用程序开发领域有相当程度的经验.如果一个人在思考如何选择正确的垃 ...

  6. 理解 Java 垃圾回收机制

    理解java垃圾回收机制有什么好处呢?作为一个软件工程师,满足自己的好奇心将是一个很好的理由,不过更重要的是,理解GC工作机制可以帮助你写出更好的Java应用程序. 这是我个人的主观观点,但我相信一个 ...

  7. 深入理解 Java 垃圾回收机制

    转载自 http://www.cnblogs.com/andy-zcx/p/5522836.html 深入理解 Java 垃圾回收机制 一:垃圾回收机制的意义 java  语言中一个显著的特点就是引入 ...

  8. axis2 webService开发 附深入理解 Java 垃圾回收机制

    相关文章: axis web服务(axis2安装和eclipse.tomcat结合开发) axis2 webService开发 axis2 webService开发(打包aar文件) 使用Axis2开 ...

  9. 深入理解Java垃圾回收——垃圾收集器

    <深入理解Java垃圾回收--虚拟机高效回收的背后>讲述了垃圾回收的理论思想,本篇文章来深入了解垃圾回收的实践:垃圾收集器. 在讲解垃圾收集器之前必要要统一几点认知: 1.用户线程:执行应 ...

  10. java stw_快速理解Java垃圾回收和jvm中的stw

    Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外).Java中一种全局暂停现象,全局停顿,所有Java代码停 ...

最新文章

  1. Linux08-日志
  2. 《Java技术》第三次作业--面向对象——继承、抽象类、接口
  3. 40+场面试,100%通过率,我想分享的14条经验
  4. eureka多台注册中心_spring cloud注册中心之Eureka
  5. python使用界面-(八)Python 图形化界面设计
  6. [ant]通过Android命令自动编译出build.xml文件
  7. 自用 x86 GetProcAddress + GetModuleHandle
  8. lnmp环境脚本自动配置
  9. 7大最重要的管理方法
  10. matlab im2double
  11. 按小时分组mysql 补齐_分组记录按小时或按天白天和mysql的
  12. (十五)非常全面的TCPIP面试宝典-进入大厂必备总结
  13. PaperNotes(19)-Learning Lane Graph Representations for Motion Forecasting
  14. 13 MM配置-主数据-定义物料状态
  15. linux 多核线程 调度,通过轻型线程提高多核设备中的Linux实时性能
  16. Selenium API-WebDriver 方法
  17. frp ---- golang编写的内网穿透工具简介
  18. java 获取当前时分_java实现获取当前年、月、日 、小时 、分钟、 秒、 毫秒
  19. ODAC(V9.5.15) 学习笔记(十六)直接访问模式
  20. iOS开发:使用大图+脚本,生成各种size的app icon和图片素材

热门文章

  1. eclipse控制台自动换行不分割单词_这 7 个实用的文档技巧,不掌握就太可惜了...
  2. java中序列化的serialVersionUID解释
  3. 广东计算机应用基础试题及答案2019,2019年计算机应用基础试题库及答案.pdf
  4. mysql不存在就批量新增_mysql批量插入,存在则修改,不存在则插入
  5. c代码实现 ifft运算_fft算法c语言_matlab fft算法_ifft c语言
  6. 小米6指主板图示_小米MIX2手机不开机,修过没修好,通病问题教你一坨锡就能搞定...
  7. linux opendir php,php opendir()列出目录下所有文件的实例代码
  8. 我的世界连锁挖矿下载JAVA_我的世界1.7.10连锁挖矿整合包
  9. mysql大量重复值建立索引_对于有大量重复数据的表添加唯一索引
  10. 西南交大计算机组成原理考试大纲,西南交大计算机组成原理实验二七段LED数码管显示译码器的设计.docx...