热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

JavaGC专家系列1:理解Java垃圾回收

了解Java的垃圾回收(GC)原理能给我们带来什么好处?对于软件工程师来说,满足技术好奇心可算是一个,但重要的是理解GC能帮忙我们更好的编写Java应用程序。
上面是我个人的主观的看法,但我相信熟练掌握GC是成为优秀Java程序员的必备技能。如果你对GC执行过程感兴趣,也许你只是有一定的开发应用的经验;如果你仔细考虑过如何选择合适的GC算法,说明你对你所开发的程序有了全面的了解。当然这对一个优秀的程序员来说未必是一个通用的标准,但很少人会反对我关于”理解GC是作为优秀Java程序员的必备技能”的看法。

本文是成为Java GC专家的案例介绍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)。

看一下下图的示意:

Java GC专家系列1:理解Java垃圾回收

图1:GC区域和数据流向

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

有些人可能认为:

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

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

Java GC专家系列1:理解Java垃圾回收

图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把数据迁移到老年代的过程:

Java GC专家系列1:理解Java垃圾回收

图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)

Java GC专家系列1:理解Java垃圾回收

图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)

Java GC专家系列1:理解Java垃圾回收

图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垃圾回收的介绍。

Java GC专家系列1:理解Java垃圾回收

图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状态以及如何做性能调优。

本文参考了2011年12月出版的《Java 性能》和Oracle网站上提供的白皮书《Java HotspotTM 虚拟机内存管理》。

作者:Sangmin Lee, 性能实验室高级工程师,NHN公司

推荐阅读
  • 探讨密码安全的重要性
    近期,多家知名网站如CSDN、人人网、多玩、开心网等的数据库相继被泄露,其中大量用户的账户密码因明文存储而暴露无遗。本文将探讨黑客获取密码的常见手段,网站如何安全存储用户信息,以及用户应如何保护自己的密码。 ... [详细]
  • 本文介绍了蓝牙低功耗(BLE)中的通用属性配置文件(GATT),包括其角色、层次结构、属性、特性和服务等内容。 ... [详细]
  • 1.前言PAP和CHAP协议是目前的在PPP(MODEM或ADSL拨号)中普遍使用的认证协议,CHAP在RFC1994中定义,是一种挑战响应式协议&#x ... [详细]
  • 本文探讨了 TypeScript 中泛型的重要性和应用场景,通过多个实例详细解析了泛型如何提升代码的复用性和类型安全性。 ... [详细]
  • 面试题总结_2019年全网最热门的123个Java并发面试题总结
    面试题总结_2019年全网最热门的123个Java并发面试题总结 ... [详细]
  • 深入解析:存储技术的演变与发展
    本文探讨了从单机文件系统到分布式文件系统的存储技术发展过程,详细解释了各种存储模型及其特点。 ... [详细]
  • 华硕笔记本无法开启热点的解决办法
    当您的华硕笔记本电脑无法开启热点时,可能是因为多种原因导致的。本文将详细介绍几种有效的解决方法,帮助您快速恢复热点功能。 ... [详细]
  • 在 Ubuntu 22.04 LTS 上部署 Jira 敏捷项目管理工具
    Jira 敏捷项目管理工具专为软件开发团队设计,旨在以高效、有序的方式管理项目、问题和任务。该工具提供了灵活且可定制的工作流程,能够根据项目需求进行调整。本文将详细介绍如何在 Ubuntu 22.04 LTS 上安装和配置 Jira。 ... [详细]
  • 本文详细介绍了Android系统的四层架构,包括应用程序层、应用框架层、库与Android运行时层以及Linux内核层,并提供了如何关闭Android系统的步骤。 ... [详细]
  • 如题:2017年10月分析:还记得在没有智能手机的年代大概就是12年前吧,手机上都会有WAP浏览器。当时没接触网络原理,也不 ... [详细]
  • 在Java开发中,保护代码安全是一个重要的课题。由于Java字节码容易被反编译,因此使用代码混淆工具如ProGuard变得尤为重要。本文将详细介绍如何使用ProGuard进行代码混淆,以及其基本原理和常见问题。 ... [详细]
  • 电商高并发解决方案详解
    本文以京东为例,详细探讨了电商中常见的高并发解决方案,包括多级缓存和Nginx限流技术,旨在帮助读者更好地理解和应用这些技术。 ... [详细]
  • Redis:缓存与内存数据库详解
    本文介绍了数据库的基本分类,重点探讨了关系型与非关系型数据库的区别,并详细解析了Redis作为非关系型数据库的特点、工作模式、优点及持久化机制。 ... [详细]
  • 网络安全实验:Telnet与SSH服务对比及抓包分析
    本实验旨在对比Telnet和SSH两种安全通信协议的服务差异,并通过搭建服务器和使用Wireshark抓包工具进行详细分析。 ... [详细]
  • CentOS7通过RealVNC实现多人使用服务器桌面
    背景:公司研发团队通过VNC登录到CentOS服务器的桌面实现软件开发工作为防止数据外泄,需要在RealVNC设置禁止传输文件、访问粘贴板等策略过程&# ... [详细]
author-avatar
茶香未散尽_385_312
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有