• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

JVM 性能优化三G1 垃圾收集器 (1),java 原理书籍

武飞扬头像
程序猿java易
帮助1

  1. 第一步:开启 G1 垃圾收集器

  2. 第二步:设置堆的最大内存

  3. 第三步:设置最大的停顿时间

G1 中提供了三种模式垃圾回收模式,Young GC、Mixed GC 和 Full GC ,在不同的条件下被触发。

二、原理


G1 垃圾收集器相比于其他收集器而言,最大的区别在于它取消了年轻代、老年代的物理划分,取而代之的是将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的年轻代、老年代区域

这样做的好处就是,我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。

学新通

学新通

在 G1 划分区域中,年轻代垃圾收集器依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者 Survivor 空间,G1 收集器通过将对象从一个区域复制到另一个区域,完成了清理工作。

这就意味着,在正常的处理过程中,G1 完成了堆的压缩(至少是部分堆的压缩),这样也就不会有 CMS 内存碎片问题的存在了,

在 G1 中,有一种特殊的区域,叫 Humongous 区域。

  • 如果一个对象占用的空间超过了分区容量 50%以上,G1 收集器就认为这是一个巨型对象

  • 这些巨型对象,默认直接会被分配在老年代,但是如果它是一个短期存在的巨型对象,就会对垃圾收集器造成负面影响。

  • 为了解决这个问题,G1 划分了一个 Humongous 区,它用来专门存放巨型对象,如果一个 H 区装不下一个巨型对象,那么 G1 会寻找连续的 H 分区存储,为了能找到连续的 H 区,有时候不得不启动 Full GC

三、Young GC


Young GC 主要是对 Eden 区进行 GC,它在 Eden 空间耗尽时会被触发。

  • Eden 空间的数据移动到 Survivor 空间中,如果 Survivor 空间不够,Eden 空间的部分数据会直接晋升到年老代空间

  • Survivor 区的数据移动到新的 Survivor 区中,也有部分数据晋升到老年代空间中

  • 最终 Eden 空间的数据为空,GC 停止工作,应用线程继续执行

学新通

3.1、Remembered Set(已记忆集合)

在 GC 年轻代的对象时,我们如何找到年轻代中对象的根对象呢?

根对象可能是在年轻代中,也可以在老年代中,那么老年代的所有对象都是根吗?

如果全量扫描老年代,那么这样扫描下来会耗费大量的时间

于是,G1 引进了 Rset 的概念,它的全称是 Remembered Set,其作用是跟踪执行某个堆内的对象引用

学新通

每个 Region 初始化时,会初始化一个 remembered set(已记忆集合),这个翻译有点拗口,以下简称 RSet,该集合用来记录并跟踪其它 Region 指向该 Region 中对象的引用,每个 Region 默认按照 512Kb 划分成多个 Card,所以 RSet 需要记录的东西应该是 xx Region 的 xx Card。

3.2、Mixed GC

当越来越多的对象晋升到老年代 Old Region 时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,既 Mixed GC,该算法并不是一个 Old GC,除了回收整个 Young Region,还会回收一部分的 Old Region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择那些 Old region 进行收集,从而可以对垃圾回收的耗时时间进行控制,也要注意的是 Mixed GC 并不是 Full GC

Mixed GC 什么时候出发?由参数 -XX:InitiatingHeapOccupancyPercent=n决定。默认:45%,该参数的意思是:当老年代大小占用整个堆大小百分比达到该阈值时触发。

它的 GC 步骤分两步:

1. 全局并发标记(global concurrent marking)

2. 拷贝存活对象(evacuation)

3.2.1 全局并发标记

全局并发标记,执行过程分为五个步骤:

  • **初始标记(initial mark ,STW):**标记从根节点直接可达的对象,这个阶段会执行一次年轻代 GC,会产生全局停顿。

  • 根区域扫描(root region scan):

  1. G1 GC 在初始标记的存活区扫描对老年代的引用,并标记被引用的对象

  2. 该阶段与应用程序(非 STW)同时运行,并且只有完成该阶段后,才能开始下一次 STW 年轻代垃圾回收。

  • 并发标记(Concurrent Marking): G1 GC 在整个堆中查找可访问的(存活的)对象,该阶段与应用程序同时运行,可以被 STW 年轻代垃圾回收中断

  • 重新标记(Remark,STW): 该阶段是 STW 回收,因为程序在运行,针对上一次的标记进行修改。

  • 清楚垃圾(Cleanup,STW): 轻点和重置标记状态,该阶段会 STW,这个阶段并不会实际上去做垃圾的收集,等待 evacuation 阶段来回收

3.2.2 拷贝存活对象

Evacuation 阶段是全暂停的,该阶段把一部分 Region 里的活对象拷贝到另一部分 Region 中,从而实现垃圾的回收清理。

四、G1 收集器相关参数


  • -XX: UseG1GC: 使用 G1 垃圾收集器

  • -XX:MaxGCPauseMillis: 设置期望达到最大 GC 停顿时间指标(JVM 会尽力实现,但不保证达到),默认值是 200 毫秒

  • -XX:G1HeapRegionSize=n:

  1. 设置的 G1 区域的大小,值是 2 的幂,范围是 1MB 到 32MB 之间,目标是根据最小的 Java 堆大小划分出约 2048 个区域

  2. 默认 是堆内存的 1/2000

  • -XX:ParallelGCThreads=n: 设置 STW 工作线程数的值,将 n 的值设置为逻辑处理器的数量,n 的值与逻辑处理器的数量相同,最多为 8

  • -XX:ConcGCThreads=n: 设置并行标记的线程数,将 n 设置为并行垃圾回收线程数(ParallelGCThreads)的 1/4 左右

  • **-XX:InitiatingHeapOccupancyPercent=n: **设置出发标记周期的 java 堆占用率阈值,默认占用率是这个 Java 堆的 45%

五、测试


5.1 测试代码:

public class TestGC {

// 实现:不断的产生新的数据(对象),随机的去废弃对象(垃圾)

public static void main(String[] args) throws Exception {

List<Object> list = new ArrayList<>();

while (true){

int sleep = new Random().nextInt(100);

if(System.currentTimeMillis() % 2 ==0){

//当前的时间戳,是偶数

list.clear();

}else{

//向 List 中添加 1000 个对象

for (int i = 0; i < 10000; i ) {

Properties properties = new Properties();

properties.put("key_" i,"value_" System.currentTimeMillis() i);

list.add(properties);

}

}

Thread.sleep(sleep);

}

}

}

5.2 测试参数:

-XX: UseG1GC -XX:MaxGCPauseMillis=100 -XX: PrintGCDetails -Xmx256m

学新通

5.3 日志输出:

[GC pause (G1 Evacuation Pause) (young), 0.0027884 secs]

[Parallel Time: 2.2 ms, GC Workers: 8]

[GC Worker Start (ms): Min: 542.1, Avg: 542.1, Max: 542.2, Diff: 0.1]

扫描根节点

[Ext Root Scanning (ms): Min: 0.1, Avg: 0.3, Max: 0.5, Diff: 0.4, Sum: 2.1]

更新 RS 区域所消耗的时间

[Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]

[Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]

[Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]

[Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]

对象拷贝

[Object Copy (ms): Min: 1.6, Avg: 1.8, Max: 1.9, Diff: 0.4, Sum: 14.6]

[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.3]

[Termination Attempts: Min: 1, Avg: 5.5, Max: 7, Diff: 6, Sum: 44]

[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]

[GC Worker Total (ms): Min: 2.1, Avg: 2.1, Max: 2.2, Diff: 0.1, Sum: 17.2]

[GC Worker End (ms): Min: 544.2, Avg: 544.2, Max: 544.2, Diff: 0.0]

[Code Root Fixup: 0.0 ms]

[Code Root Purge: 0.0 ms]

[Clear CT: 0.1 ms] # 清空 CardTable

[Other: 0.5 ms]

[Choose CSet: 0.0 ms] # 选取 CSet

[Ref Proc: 0.4 ms] # 弱引用、软引用的处理耗时

[Ref Enq: 0.0 ms] # 弱引用、软引用的入队耗时

[Redirty Cards: 0.0 ms]

[Humongous Register: 0.0 ms] # 大对象区域注册耗时

[Humongous Reclaim: 0.0 ms] # 大对象区域回收耗时

[Free CSet: 0.0 ms]

[Eden: 12.0M(12.0M)->0.0B(13.0M) Survivors: 0.0B->2048.0K Heap: 12.0M(252.0M)->3282.5K(252.0M)] # 年轻代的大小统计

[Times: user=0.00 sys=0.00, real=0.00 secs]

五、对于 G1 垃圾回收器优化建议


  • 年轻代大小:

  1. 避免使用 -Xmn 选项或 -XX:NewRatio 等其他相关选项显示设置年轻代大小

  2. 固定年轻代的大小会覆盖暂停时间目标

  • 暂停时间目标不要太过严苛

  1. G1 GC 的吞吐量目标是 90% 的应用程序时间和 10%的垃圾回收时间

  2. 评估 G1 GC 的吞吐量时,暂停时间目标不要太严苛,目标太过严苛表示你愿意承受更多的垃圾回收开销,而这会直接影响到吞吐量

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhieckib
系列文章
更多 icon
同类精品
更多 icon
继续加载