Hotspot Architecture
Hotspot虚拟机架构
JVM主要组件包括类加载器,运行时数据区和执行引擎。
Key Hotspot Components
关键组件
在进行性能调优时,主要关注虚拟机的三个组件。java堆保存着对象实例,由JVM启动时选定的GC进行维护。大部分调优参数都涉及调整java堆及内部各区域的大小、选择最适合的GC。JIT编译器对性能也有巨大影响,但新近的虚拟机很少需要针对它进行调优。
Performance Basics
性能基础
java应用调优一般关注两个指标:响应时间和吞吐量。
Responsiveness
响应时间
响应时间指的是应用对请求的响应时间,如:
- 一个桌面应用对时间的响应时间
- 网站返回一个页面的时间
- 数据库查询结果返回时间
对于专注于最小响应时间的应用,长时间停顿是无法接受的。
Throughput
吞吐量
吞吐量专注于应用在一段时间的的最大工作量。如:
- 给定时间内完成的事物数
- 每小时批处理程序能够完成的任务
- 每小时数据库可以完成的查询操作数
较长停顿时间在此情况下是可以接受的。比起低响应时间,吞吐量优先应用更看重一段时间内的表现。
The G1 Garbage Collector
G1垃圾收集器
Garbage-First(G1)收集器时一款服务端垃圾收集器,主要针对多处理器、大内存环境设计。在尽可能满足GC停顿时间目标的同时获取更高的吞吐量。
G1的设计目标是满足:
- 像CMS那样做到并发GC,提高GC并行和并发表现
- 没有内存碎片问题,内存整理过程不需要延长GC时间,不需要虚拟机停顿STW
- 可预测的GC停顿时间
- 更高的吞吐量
- 在不增加堆内存大小下更好地利用堆内存
G1的远期目标时替代CMS+ParNew(自适应的、分代的、停顿-复制、标记-清理并发垃圾回收器)组合。与CMS相比,G1优点包括:G1是内存整理虚拟机,G1通过对堆内存划分区域(region),分区管理,避免使用细粒度空闲列表来实现高效内存整理;G1提供比CMS更加可预测的GC停顿时间,并允许用户设定停顿时间目标。
G1 Operational Overview
G1实现概览
老式垃圾回收器(Serial,Parallel,CMS)统一将java堆分成大小固定的三部分:新生代、老年代和永久代。(开启GC Ergonomics自适应调整策略,如Parallel Scavange,可以动态调整新生代大小、eden与survivor比例)所有内存对象最终都保存在这三个区域内。
G1收集器将java堆均分成大小相同的区域(region,1M-32M,最多2000个,最大支持堆内存64G)。一个或多个不连续的区域共同组成eden、survivor或old区,但大小不再固定,这为内存应用提供了极大地弹性。G1垃圾收集过程与CMS类似。G1在堆内存中并发地对所有对象进行标记,决定对象的可达性。经过全局标记,G1了解哪些区域几乎是空的,然后优先收集这些区域,这就是GarbageFirst的命名由来。G1将垃圾收集和内存整理活动专注于那些几乎全是垃圾的区域,并建立停顿预测模型来决定每次GC时回收哪些区域,以满足用户设定的停顿时间。
对于区域的回收通过复制算法实现。在完成标记清理后,G1将这几个区域的存活对象复制到一个单独区域中,实现内存整理和空间释放。这一过程通过多线程并行进行来降低停顿时间,提高吞吐量。通过这样的方式,G1在每次GC过程中持续清理碎片,控制停顿时间满足用户要求。这时过去虚拟机无法做到的。CMS不清理内存碎片(除非通过虚拟机参数设置,在每次或多次FullGC后进行整理),ParallelOld进行全堆整理,会导致较长的停顿时间。
G1不是实时垃圾收集器,它会尽量让停顿时间低于用户设置的停顿时间目标但不能保证一定如此。G1根据历史垃圾收集监测数据来 预测每个区域的回收时间,然后根据用户设定的目标停顿时间决定每次GC时可以回收哪些区域。G1通过这种方式建立比较精确的区域回收时间预测模型。
注意:G1有并发阶段(与应用线程并行,无停顿时间)和并行阶段(多线程,应用工作线程停顿)。FullGC仍是单线程,但如果调优合适,FullGC是可以避免的。
G1 Footprint
内存占用
如果从ParallelOldGC或CMS迁移至G1,你会发现JVM线程占用内存增大。增大的部分主要与'accouting'数据结构有关,如Remembered Sets(RSets)和Collection Sets(CSets)。
-
Remembered Sets RSets跟踪指向某个区域的对象引用。每个区域对应一个RSet。RSets对整体内存占用的影响少于5%。
-
Collection Sets CSets 在一次GC中将被回收的区域集合。所有CSet区域中的存活对象都会被移动到新的区域中,这些区域可以是Eden区、survivor区或老年代。CSets对JVM内存占用影响少于1%。
Recommended Use Cases for G1
G1的推荐使用场景
G1的设计初衷是为用户提供大内存、低GC停顿时间的应用解决方案。这意味着堆内存6G或更大,停顿时间稳定且少于0.5秒。
如果应用正在使用CMS或ParallelOld且面临以下问题,推荐将应用迁移至G1
- FullGC发生频繁或总时间过长
- 对象分配率或对象升级至老年代的比例波动较大
- 较长的垃圾收集或内存整理停顿(大于0.5至1秒)
注意:如果应用没有上述问题,不需要迁移虚拟机。G1并不是最新JDK要求的虚拟机。
Reviewing Generational GC and CMS
回顾分代GC和CMS
CMS(并发低停顿收集器)收集老年代。通过将大部分垃圾回收工作与应用线程并发进行,尽可能降低停顿时间。通常CMS不回去复制和整理内存。GC过程不会移动对象。如果内存碎片问题严重,扩充堆内存。
CMS Collection Phases
CMS回收步骤
CMS在老年代上回收步骤如下:
阶段 | 描述 |
---|---|
初始标记(STW) | 标记GCRoot直接引用的的老年代对象,包括从新生代引用的对象,停顿时间相比minorGC非常短 |
并发标记 | 遍历老年代对象,此时应用线程并发执行。从已经标记的对象和GCRoot开始标记所有可达对象。所有并发阶段新分配的对象或从新生代升级到老年代的对象都标记为可达对象 |
重新标记(STW) | 暂停应用现成,修正并发标记阶段由于应用线程运行导致没有标记的对象 |
并发清除 | 收集标记为不可达的对象。对象内存空间被添加到空闲列表中等待分配,这些回收对象的空间可以被合并。存活对象在这一阶段不会移动 |
重置 | 重置GC数据结构,为下次GC做准备 |
Heap Structure for CMS Collector
CMS收集器堆内存结构
堆内存被分配为三部分。
新生代分为Eden区和两个survivor区。老年代是一块连续区域。只有FullGC时才可能发生内存整理。
How Young GC works in CMS
CMS中的新生代GC
新生代淡绿色,老年代蓝色。系统运行一段时间后CMS堆内存可能如下图所示,对象分散在老年代各处。
老年代对象空间原地回收,CMS不会移动它们。内存整理只会发生在FullGC时。
Young Generation Collection
新生代回收
新生代存活对象从Eden区和survivor区复制到另一个空闲的survivor区。任何minorGC年龄达到阈值的老对象被升级至老年代。
After Young GC
新生代GC后
youngGC(minorGC)后,Eden区和一个survivor区被清空。
新近升级至老年代的对象以深蓝色表示。绿色对象是新生代仍存活的对象。
Old Generation Collection with CMS
CMS老年代垃圾回收
在初始标记和重新标记阶段发生STW。当老年代剩余空间达到阈值时(发生Concurrent Mode Failure),使用SerialOld替代CMS进行老年代GC。
(1)初始标记停顿时间很短,简单的标记GCRoot引用的对象。(2)并发标记在标记存活对象时,应用继续执行。(3)重新标记阶段,标记并发阶段遗漏的存活对象。
Old Generation Collection - Concurrent Sweep
老年代垃圾收集-并发清除
未被标记的对象直接被回收,不会发生内存整理。
Old Generation Collection - After Sweeping
老年代垃圾收集-清扫后
完成并发清扫后,许多老年代空间被释放出来。同时内存整理仍没有发生,老年代存在大量内存碎片。
最终,CMS经过重置阶段,等待下一次GC。
The G1 Garbage Collector Step by Step
G1垃圾收集步骤
G1 Heap Structure
G1堆内存结构
G1的堆内存被分成许多固定大小的区域。
区域大小相同,在JVM启动时确定。JVM通常管理2000个区域,区域大小介于1M到32M之间。
G1 Heap Allocation
G1堆内存分配
这些区域被映射为逻辑上的Eden、survivor和老年代区域,相同类型区域地址可以不连续。
区域分为五种,Eden、survivor和老年代,另外还有一种大对象区,这些区域用来存放占据50%以上区域空间的对象,这些对象被存储在一组连续区域内,最后一种区域就是未被使用区域。
G1不要求区域连续。
A Young GC in G1
G1的新生代GC
存活对象移动到一个或多个survivor区域。如果对象达到晋升年龄,将被移动到老年代区域。
这一阶段包括STW。eden区和survivor区大小重新计算为下一次youngGC做准备。这一过程使得动态调整各区大小非常简单。
End of a Young GC with G1
G1的youngGC结束
存活对象被移动到survivor区或老年代区域。
G1新生代有如下总结:
- 堆内存是一个单独的内存区域,被分为多个大小相同的区域
- 新生代内存由一系列不连续的区域组成。按需调整内存大小很简单。
- 新生代垃圾回收(youngGC)需要STW,所有应用线程需要停顿。
- youngGC多线程并行执行。
- 存活对象复制到新的survivor区或老年代区。
Old Generation Collection with G1
G1老年代垃圾回收
G1 Collection Phases - Concurrent Marking Cycle Phases
G1回收阶段-并发标记周期阶段
G1回收器在对内存的老年代区域执行以下阶段。注意这些阶段也包括新生代的回收。
阶段 | 描述 |
---|---|
初始标记STW | 捎带一次youngGC。标记可能引用了老年代区域对象的survivor区域(根区域) |
根区域扫描 | 扫描根区域中指向老年代的引用。与应用现成并发。此阶段完成后才可以进行youngGC |
并发标记 | 全堆扫描存活对象。与应用现成并发。这一阶段可以被youngGC打断 |
重新标记STW | 完成堆中所有存活对象的扫描,使用snapshot-at-the-beginningSATB算法 |
清除 | 统计存活对象和空区域(STW),更新RSets(STW),重置空区域,加入空白列表(并发) |
复制STW | 将存活对象移动至未使用区域 |
G1 Old Generation Collection Step by Step
G1老年代回收步骤
Initial Marking Phase
初始标记阶段
新生代垃圾收集捎带着一次存活对象的初始标记。在GC日志中打印为GCpause(young)(inital-mark)
Concurrent Marking Phase
并发标记阶段
此阶段如果发现空区域,将会在再次标记阶段立即回收。同时,计算各区域活跃度(回收优先级)的所需的信息在这个阶段统计。
Remark Phase
再次标记阶段
这一阶段空区域被回收,计算出所有区域活跃度。
Copying/Cleanup Phase
复制/清理阶段
G1选择那些活跃度最低,回收速度最快的区域进行回收。这些区域回收的同时进行youngGC。GC日志中记录为[GC pause (mixed)]。因此新生代和老年代垃圾回收同时发生。
After Copying/Cleanup Phase
复制/清理阶段后
被选中区域的存活对象移动到新的区域中,原区域被回收加入空白列表。
Summary of Old Generation GC
老年代GC总结
- 并发标记阶段
- 区域活跃度信息的统计与应用线程并发进行
- 活跃度信息决定了那个区域最先在清理阶段被回收
- 没有CMS的清理阶段
- 再次标记阶段
- SATB算法比CMS使用的算法更快
- 空区域在这个阶段被回收
- 复制/清理阶段
- 新生代和老年代同时被回收
- 老年代根据活跃度确定回收优先级
关于Remembered Set概念:G1收集器中,Region之间的对象引用以及其他收集器中的新生代和老年代之间的对象引用是使用Remembered Set来避免扫描全堆。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序对Reference类型数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之间(在分代中例子中就是检查是否老年代中的对象引用了新生代的对象),如果是便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当内存回收时,在GC根节点的枚举范围加入Remembered Set即可保证不对全局堆扫描也不会有遗漏。
Command Line Options and Best Practices
JVM参数最佳实践
Basic Command Line
基本命令
启用G1 -XX:+UserG1GC
以下是使用G1收集器的javademo演示
java -Xmx50m -Xms50m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar c:javademosdemojfcJava2DJava2demo.jar
Key Command Line Switches
关键命令
- -XX:+UseG1GC - 启用G1
- -XX:MaxGCPauseMillis=200 - GC停顿的最长时间。这是一个软目标,即虚拟机会尽最大可能满足这一时间,但某些情况下仍可能超过。默认值为200ms。
- -XX:InitiatingHeapOccupancyPercent=45 - 堆内存使用率达到多少时启动一次GC过程。GC过程涉及整个堆内存,不单指某个年龄代。设为0时表示循环进行并发GC。默认值为45,即堆内存使用45%后触发一次GC。
Best Practices
最佳实践
Do not Set Young Generation Size
不要设置新生代大小
设置新生代大小-Xmn会影响G1的回收表现。
- G1不在遵守目标停顿时间,即设置新生代大小后,目标停顿时间失效。
- G1不能按需扩展或收缩新生代空间。
Response Time Metrics
响应时间指标
不要根据平均响应时间ART设置MaxGCPauseMillis参数,将这个值设置为90%的用户发起请求时的等待时间不会超过的值。
What is an Evacuation Failure?
转移失败
在GC过程中,堆内存区域耗尽,新生代对象晋升老年代失败。堆内存由于已经达到最大而无法扩张。在GClog中打印to-space overflow日志。这会耗费大量资源。
- GC仍要继续,所以空间必须被释放。
- 拷贝失败的对象必须留在原区域。
- 这一过程中,CSets中区域的RSets如果发生变化,RSets需要重新生成。
- 这些步骤都非常耗费资源。
How to avoid Evacuation Failure
如何避免转移失败
- 增加堆内存大小
- 提高-XX:G1ReservePercent,默认值是10
- G1为堆内存设置虚拟使用上限,预留一部分空间防止to-space情况出现
- 提前进行标记阶段
- 提高标记阶段的线程数,-XX:ConcGCThreads
Complete List of G1 GC Switches
G1 GC参数一览
参数与默认值 | 说明 |
---|---|
-XX:+UserG1GC | 使用G1收集器 |
-XX:MaxGCPauseMillis=200 | 设置最大停顿时间,软目标,虚拟机不保证总是达到要求 |
-XX:InitiatingHeapOccupancyPercent=45 | 当堆内存使用率达到多少时启动一次GC周期。GC周期针对整个堆内存而不是某个年龄代。设为0时表示持续进行GC周期 |
-XX:NewRatio=2 | 新生代与老年代大小比例 |
-XX:SurvivorRatio=8 | Eden区与survivor区大小比例 |
-XX:MaxTenuringThreshold=15 | 新生代存活过多少次youngGC后晋升老年代 |
-XX:ConcGCThreads | 并发垃圾回收器使用的线程数,默认值根据JVM运行平台不同而变化 |
-XX:ParallelGCThreads | 在并行阶段垃圾回收器线程数 |
-XX:G1ReservePercent=10 | 预留多少个区域,防止出现转移失败导致to-space overflow |
-XX:G1HeapRegionSize | G1将堆内存划分为大小相同的多个区域。区域大小由这个参数确定。默认值与堆内存大小有关。不小于1M,不大于32M。总数2000 |
作者:哇噜噜大王没有巴
链接:https://www.jianshu.com/p/d3bbb4dee88e
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。