mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-09-29 22:56:42 +08:00
379 lines
28 KiB
HTML
379 lines
28 KiB
HTML
<!DOCTYPE html>
|
||
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
|
||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
<head>
|
||
<head>
|
||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
|
||
<link rel="icon" href="/static/favicon.png">
|
||
<title>08 大厂面试题:有了 G1 还需要其他垃圾回收器吗?.md.html</title>
|
||
<!-- Spectre.css framework -->
|
||
<link rel="stylesheet" href="/static/index.css">
|
||
<!-- theme css & js -->
|
||
<meta name="generator" content="Hexo 4.2.0">
|
||
</head>
|
||
<body>
|
||
<div class="book-container">
|
||
<div class="book-sidebar">
|
||
<div class="book-brand">
|
||
<a href="/">
|
||
<img src="/static/favicon.png">
|
||
<span>技术文章摘抄</span>
|
||
</a>
|
||
</div>
|
||
<div class="book-menu uncollapsible">
|
||
<ul class="uncollapsible">
|
||
<li><a href="/" class="current-tab">首页</a></li>
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
<li><a href="../">上一级</a></li>
|
||
</ul>
|
||
<ul class="uncollapsible">
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/00 开篇词:JVM,一块难啃的骨头.md.html">00 开篇词:JVM,一块难啃的骨头</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/01 一探究竟:为什么需要 JVM?它处在什么位置?.md.html">01 一探究竟:为什么需要 JVM?它处在什么位置?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/02 大厂面试题:你不得不掌握的 JVM 内存管理.md.html">02 大厂面试题:你不得不掌握的 JVM 内存管理</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/03 大厂面试题:从覆盖 JDK 的类开始掌握类的加载机制.md.html">03 大厂面试题:从覆盖 JDK 的类开始掌握类的加载机制</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/04 动手实践:从栈帧看字节码是如何在 JVM 中进行流转的.md.html">04 动手实践:从栈帧看字节码是如何在 JVM 中进行流转的</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/05 大厂面试题:得心应手应对 OOM 的疑难杂症.md.html">05 大厂面试题:得心应手应对 OOM 的疑难杂症</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/06 深入剖析:垃圾回收你真的了解吗?(上).md.html">06 深入剖析:垃圾回收你真的了解吗?(上)</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/07 深入剖析:垃圾回收你真的了解吗?(下).md.html">07 深入剖析:垃圾回收你真的了解吗?(下)</a>
|
||
</li>
|
||
<li>
|
||
<a class="current-tab" href="/专栏/深入浅出 Java 虚拟机-完/08 大厂面试题:有了 G1 还需要其他垃圾回收器吗?.md.html">08 大厂面试题:有了 G1 还需要其他垃圾回收器吗?</a>
|
||
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/09 案例实战:亿级流量高并发下如何进行估算和调优.md.html">09 案例实战:亿级流量高并发下如何进行估算和调优</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/10 第09讲:案例实战:面对突如其来的 GC 问题如何下手解决.md.html">10 第09讲:案例实战:面对突如其来的 GC 问题如何下手解决</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/11 第10讲:动手实践:自己模拟 JVM 内存溢出场景.md.html">11 第10讲:动手实践:自己模拟 JVM 内存溢出场景</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/12 第11讲:动手实践:遇到问题不要慌,轻松搞定内存泄漏.md.html">12 第11讲:动手实践:遇到问题不要慌,轻松搞定内存泄漏</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/13 工具进阶:如何利用 MAT 找到问题发生的根本原因.md.html">13 工具进阶:如何利用 MAT 找到问题发生的根本原因</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/14 动手实践:让面试官刮目相看的堆外内存排查.md.html">14 动手实践:让面试官刮目相看的堆外内存排查</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/15 预警与解决:深入浅出 GC 监控与调优.md.html">15 预警与解决:深入浅出 GC 监控与调优</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/16 案例分析:一个高死亡率的报表系统的优化之路.md.html">16 案例分析:一个高死亡率的报表系统的优化之路</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/17 案例分析:分库分表后,我的应用崩溃了.md.html">17 案例分析:分库分表后,我的应用崩溃了</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/18 动手实践:从字节码看方法调用的底层实现.md.html">18 动手实践:从字节码看方法调用的底层实现</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/19 大厂面试题:不要搞混 JMM 与 JVM.md.html">19 大厂面试题:不要搞混 JMM 与 JVM</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/20 动手实践:从字节码看并发编程的底层实现.md.html">20 动手实践:从字节码看并发编程的底层实现</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/21 动手实践:不为人熟知的字节码指令.md.html">21 动手实践:不为人熟知的字节码指令</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/22 深入剖析:如何使用 Java Agent 技术对字节码进行修改.md.html">22 深入剖析:如何使用 Java Agent 技术对字节码进行修改</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/23 动手实践:JIT 参数配置如何影响程序运行?.md.html">23 动手实践:JIT 参数配置如何影响程序运行?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/24 案例分析:大型项目如何进行性能瓶颈调优?.md.html">24 案例分析:大型项目如何进行性能瓶颈调优?</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/25 未来:JVM 的历史与展望.md.html">25 未来:JVM 的历史与展望</a>
|
||
</li>
|
||
<li>
|
||
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/26 福利:常见 JVM 面试题补充.md.html">26 福利:常见 JVM 面试题补充</a>
|
||
</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
|
||
<div class="sidebar-toggle-inner"></div>
|
||
</div>
|
||
<script>
|
||
function add_inner() {
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
inner.classList.add('show')
|
||
}
|
||
function remove_inner() {
|
||
let inner = document.querySelector('.sidebar-toggle-inner')
|
||
inner.classList.remove('show')
|
||
}
|
||
function sidebar_toggle() {
|
||
let sidebar_toggle = document.querySelector('.sidebar-toggle')
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let content = document.querySelector('.off-canvas-content')
|
||
if (sidebar_toggle.classList.contains('extend')) { // show
|
||
sidebar_toggle.classList.remove('extend')
|
||
sidebar.classList.remove('hide')
|
||
content.classList.remove('extend')
|
||
} else { // hide
|
||
sidebar_toggle.classList.add('extend')
|
||
sidebar.classList.add('hide')
|
||
content.classList.add('extend')
|
||
}
|
||
}
|
||
function open_sidebar() {
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
sidebar.classList.add('show')
|
||
overlay.classList.add('show')
|
||
}
|
||
function hide_canvas() {
|
||
let sidebar = document.querySelector('.book-sidebar')
|
||
let overlay = document.querySelector('.off-canvas-overlay')
|
||
sidebar.classList.remove('show')
|
||
overlay.classList.remove('show')
|
||
}
|
||
</script>
|
||
<div class="off-canvas-content">
|
||
<div class="columns">
|
||
<div class="column col-12 col-lg-12">
|
||
<div class="book-navbar">
|
||
<!-- For Responsive Layout -->
|
||
<header class="navbar">
|
||
<section class="navbar-section">
|
||
<a onclick="open_sidebar()">
|
||
<i class="icon icon-menu"></i>
|
||
</a>
|
||
</section>
|
||
</header>
|
||
</div>
|
||
<div class="book-content" style="max-width: 960px; margin: 0 auto;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;">
|
||
<div class="book-post">
|
||
<p id="tip" align="center"></p>
|
||
<div><h1>08 大厂面试题:有了 G1 还需要其他垃圾回收器吗?</h1>
|
||
<p>本课时我们主要来看下这两个高频的面试考题:</p>
|
||
<ul>
|
||
<li>G1 的回收原理是什么?为什么 G1 比传统 GC 回收性能好?</li>
|
||
<li>为什么 G1 如此完美仍然会有 ZGC?</li>
|
||
</ul>
|
||
<p>我们在上一课时,简要的介绍了 CMS 垃圾回收器,下面我们简单回忆一下它的一个极端场景(而且是经常发生的场景)。</p>
|
||
<p>在发生 Minor GC 时,由于 Survivor 区已经放不下了,多出的对象只能提升(promotion)到老年代。但是此时老年代因为空间碎片的缘故,会发生 concurrent mode failure 的错误。这个时候,就需要降级为 Serail Old 垃圾回收器进行收集。这就是比 concurrent mode failure 更加严重的 promotion failed 问题。</p>
|
||
<p><img src="assets/Cgq2xl4lSamAVeyxAAAuIzs1H0M233.jpg" alt="img" /></p>
|
||
<p>一次简单的 Major GC,竟然能演化成耗时最长的 Full GC。最要命的是,这个停顿时间是不可预知的。</p>
|
||
<p>有没有一种办法,能够首先定义一个停顿时间,然后反向推算收集内容呢?就像是领导在年初制定 KPI 一样,分配的任务多就多干些,分配的任务少就少干点。</p>
|
||
<p>很久之前就有领导教导过我,如果你列的目标太大,看起来无法完成,不要怕。有一个叫作里程碑的名词,可以让我们以小跑的姿态,完成一次马拉松。</p>
|
||
<p>G1 的思路说起来也类似,它不要求每次都把垃圾清理的干干净净,它只是努力做它认为对的事情。</p>
|
||
<p>我们要求 G1,在任意 1 秒的时间内,停顿不得超过 10ms,这就是在给它制定 KPI。G1 会尽量达成这个目标,它能够推算出本次要收集的大体区域,以增量的方式完成收集。</p>
|
||
<p>这也是使用 G1 垃圾回收器不得不设置的一个参数:</p>
|
||
<p>-XX:MaxGCPauseMillis=10</p>
|
||
<h2><strong>为什么叫 G1</strong></h2>
|
||
<p>G1 的目标是用来干掉 CMS 的,它同样是一款软实时垃圾回收器。相比 CMS,G1 的使用更加人性化。比如,CMS 垃圾回收器的相关参数有 72 个,而 G1 的参数只有 26 个。</p>
|
||
<p>G1 的全称是 GarbageFirst GC,为了达成上面制定的 KPI,它和前面介绍的垃圾回收器,在对堆的划分上有一些不同。</p>
|
||
<p>其他的回收器,都是对某个年代的整体收集,收集时间上自然不好控制。G1 把堆切成了很多份,把每一份当作一个小目标,部分上目标很容易达成。</p>
|
||
<p>那又有一个面试题来啦:G1 有年轻代和老年代的区分吗?</p>
|
||
<p><img src="assets/CgpOIF4lSamARPiHAABC8ugXMK8124.jpg" alt="img" /></p>
|
||
<p>如图所示,G1 也是有 Eden 区和 Survivor 区的概念的,只不过它们在内存上不是连续的,而是由一小份一小份组成的。</p>
|
||
<p>这一小份区域的大小是固定的,名字叫作小堆区(Region)。小堆区可以是 Eden 区,也可以是 Survivor 区,还可以是 Old 区。所以 G1 的年轻代和老年代的概念都是逻辑上的。</p>
|
||
<p>每一块 Region,大小都是一致的,它的数值是在 1M 到 32M 字节之间的一个 2 的幂值数。</p>
|
||
<p>但假如我的对象太大,一个 Region 放不下了怎么办?注意图中有一块面积很大的黄色区域,它的名字叫作 Humongous Region,大小超过 Region 50% 的对象,将会在这里分配。</p>
|
||
<p>Region 的大小,可以通过参数进行设置:</p>
|
||
<p>-XX:G1HeapRegionSize=<N>M</p>
|
||
<p>那么,回收的时候,到底回收哪些小堆区呢?是随机的么?</p>
|
||
<p>这当然不是。事实上,垃圾最多的小堆区,会被优先收集。这就是 G1 名字的由来。</p>
|
||
<h2><strong>G1 的垃圾回收过程</strong></h2>
|
||
<p>在逻辑上,G1 分为年轻代和老年代,但它的年轻代和老年代比例,并不是那么“固定”,为了达到 MaxGCPauseMillis 所规定的效果,G1 会自动调整两者之间的比例。</p>
|
||
<p>如果你强行使用 -Xmn 或者 -XX:NewRatio 去设定它们的比例的话,我们给 G1 设定的这个目标将会失效。</p>
|
||
<p>G1 的回收过程主要分为 3 类:</p>
|
||
<p>(1)G1“年轻代”的垃圾回收,同样叫 Minor GC,这个过程和我们前面描述的类似,发生时机就是 Eden 区满的时候。</p>
|
||
<p>(2)老年代的垃圾收集,严格上来说其实不算是收集,它是一个“并发标记”的过程,顺便清理了一点点对象。</p>
|
||
<p>(3)真正的清理,发生在“混合模式”,它不止清理年轻代,还会将老年代的一部分区域进行清理。</p>
|
||
<p><img src="assets/Cgq2xl4lSamAcuN5AAA9uBTIpvw935.jpg" alt="img" /></p>
|
||
<p>在 GC 日志里,这个过程描述特别有意思,(1)的过程,叫作 [GC pause (G1 Evacuation Pause) (young),而(2)的过程,叫作 [GC pause (G1 Evacuation Pause) (mixed)。Evacuation 是转移的意思,和 Copy 的意思有点类似。</p>
|
||
<p>这三种模式之间的间隔也是不固定的。比如,1 次 Minor GC 后,发生了一次并发标记,接着发生了 9 次 Mixed GC。</p>
|
||
<h2><strong>RSet</strong></h2>
|
||
<p>RSet 是一个空间换时间的数据结构。</p>
|
||
<p>在第 6 课时中,我们提到过一个叫作卡表(Card Table)的数据结构,用来解决跨代引用的问题。RSet 的功能与此类似,它的全称是 Remembered Set,用于记录和维护 Region 之间的对象引用关系。</p>
|
||
<p>但 RSet 与 Card Table 有些不同的地方。Card Table 是一种 points-out(我引用了谁的对象)的结构。而 RSet 记录了其他 Region 中的对象引用本 Region 中对象的关系,属于 points-into 结构(谁引用了我的对象),有点倒排索引的味道。</p>
|
||
<p>你可以把 RSet 理解成一个 Hash,key 是引用的 Region 地址,value 是引用它的对象的卡页集合。</p>
|
||
<p><img src="assets/CgpOIF4lSamAXeymAABc7ztdEEU131.jpg" alt="img" /></p>
|
||
<p>有了这个数据结构,在回收某个 Region 的时候,就不必对整个堆内存的对象进行扫描了。它使得部分收集成为了可能。</p>
|
||
<p>对于年轻代的 Region,它的 RSet 只保存了来自老年代的引用,这是因为年轻代的回收是针对所有年轻代 Region 的,没必要画蛇添足。所以说年轻代 Region 的 RSet 有可能是空的。</p>
|
||
<p>而对于老年代的 Region 来说,它的 RSet 也只会保存老年代对它的引用。这是因为老年代回收之前,会先对年轻代进行回收。这时,Eden 区变空了,而在回收过程中会扫描 Survivor 分区,所以也没必要保存来自年轻代的引用。</p>
|
||
<p>RSet 通常会占用很大的空间,大约 5% 或者更高。不仅仅是空间方面,很多计算开销也是比较大的。</p>
|
||
<p>事实上,为了维护 RSet,程序运行的过程中,写入某个字段就会产生一个 post-write barrier 。为了减少这个开销,将内容放入 RSet 的过程是异步的,而且经过了很多的优化:Write Barrier 把脏卡信息存放到本地缓冲区(local buffer),有专门的 GC 线程负责收集,并将相关信息传给被引用 Region 的 RSet。</p>
|
||
<p>参数 -XX:G1ConcRefinementThreads 或者 -XX:ParallelGCThreads 可以控制这个异步的过程。如果并发优化线程跟不上缓冲区的速度,就会在用户进程上完成。</p>
|
||
<h2><strong>具体回收过程</strong></h2>
|
||
<p>G1 还有一个 CSet 的概念。这个就比较好理解了,它的全称是 Collection Set,即收集集合,保存一次 GC 中将执行垃圾回收的区间(Region)。GC 是在 CSet 中的所有存活数据(Live Data)都会被转移。</p>
|
||
<p>了解了上面的数据结构,我们再来简要看一下回收过程。</p>
|
||
<h3><strong>年轻代回收</strong></h3>
|
||
<p>年轻代回收是一个 STW 的过程,它的跨代引用使用 RSet 数据结构来追溯,会一次性回收掉年轻代的所有 Region。</p>
|
||
<p>JVM 启动时,G1 会先准备好 Eden 区,程序在运行过程中不断创建对象到 Eden 区,当所有的 Eden 区都满了,G1 会启动一次年轻代垃圾回收过程。</p>
|
||
<p><img src="assets/Cgq2xl4lSaqAP6OGAABH2k_Jpog641.jpg" alt="img" /></p>
|
||
<p>年轻代的收集包括下面的回收阶段:</p>
|
||
<p>(1)扫描根</p>
|
||
<p>根,可以看作是我们前面介绍的 GC Roots,加上 RSet 记录的其他 Region 的外部引用。</p>
|
||
<p>(2)更新 RS</p>
|
||
<p>处理 dirty card queue 中的卡页,更新 RSet。此阶段完成后,RSet 可以准确的反映老年代对所在的内存分段中对象的引用。可以看作是第一步的补充。</p>
|
||
<p>(3)处理 RS</p>
|
||
<p>识别被老年代对象指向的 Eden 中的对象,这些被指向的 Eden 中的对象被认为是存活的对象。</p>
|
||
<p>(4)复制对象</p>
|
||
<p>没错,收集算法依然使用的是 Copy 算法。</p>
|
||
<p>在这个阶段,对象树被遍历,Eden 区内存段中存活的对象会被复制到 Survivor 区中空的 Region。这个过程和其他垃圾回收算法一样,包括对象的年龄和晋升,无需做过多介绍。</p>
|
||
<p>(5)处理引用</p>
|
||
<p>处理 Soft、Weak、Phantom、Final、JNI Weak 等引用。结束收集。</p>
|
||
<p>它的大体示意图如下所示。</p>
|
||
<p><img src="assets/CgpOIF4lSaqAPBcOAABEfIhFxnI679.jpg" alt="img" /></p>
|
||
<h3><strong>并发标记(Concurrent Marking)</strong></h3>
|
||
<p>当整个堆内存使用达到一定比例(默认是 45%),并发标记阶段就会被启动。这个比例也是可以调整的,通过参数 -XX:InitiatingHeapOccupancyPercent 进行配置。</p>
|
||
<p>Concurrent Marking 是为 Mixed GC 提供标记服务的,并不是一次 GC 过程的一个必须环节。这个过程和 CMS 垃圾回收器的回收过程非常类似,你可以类比 CMS 的回收过程看一下。具体标记过程如下:</p>
|
||
<p>(1)初始标记(Initial Mark)</p>
|
||
<p>这个过程共用了 Minor GC 的暂停,这是因为它们可以复用 root scan 操作。虽然是 STW 的,但是时间通常非常短。</p>
|
||
<p>(2)Root 区扫描(Root Region Scan)</p>
|
||
<p>(3)并发标记( Concurrent Mark)</p>
|
||
<p>这个阶段从 GC Roots 开始对 heap 中的对象标记,标记线程与应用程序线程并行执行,并且收集各个 Region 的存活对象信息。</p>
|
||
<p>(4)重新标记(Remaking)</p>
|
||
<p>和 CMS 类似,也是 STW 的。标记那些在并发标记阶段发生变化的对象。</p>
|
||
<p>(5)清理阶段(Cleanup)</p>
|
||
<p>这个过程不需要 STW。如果发现 Region 里全是垃圾,在这个阶段会立马被清除掉。不全是垃圾的 Region,并不会被立马处理,它会在 Mixed GC 阶段,进行收集。</p>
|
||
<p>了解 CMS 垃圾回收器后,上面这个过程就比较好理解。但是还有一个疑问需要稍微提一下。</p>
|
||
<p>如果在并发标记阶段,又有新的对象变化,该怎么办?</p>
|
||
<p>这是由算法 SATB 保证的。SATB 的全称是 Snapshot At The Beginning,它作用是保证在并发标记阶段的正确性。</p>
|
||
<p><img src="assets/Cgq2xl4lSaqAcPP3AAAel8LUC1s541.jpg" alt="img" /></p>
|
||
<p>这个快照是逻辑上的,主要是有几个指针,将 Region 分成个多个区段。如图所示,并发标记期间分配的对象,都会在 next TAMS 和 top 之间。</p>
|
||
<h3><strong>混合回收(Mixed GC)</strong></h3>
|
||
<p>能并发清理老年代中的整个整个的小堆区是一种最优情形。混合收集过程,不只清理年轻代,还会将一部分老年代区域也加入到 CSet 中。</p>
|
||
<p>通过 Concurrent Marking 阶段,我们已经统计了老年代的垃圾占比。在 Minor GC 之后,如果判断这个占比达到了某个阈值,下次就会触发 Mixed GC。这个阈值,由 -XX:G1HeapWastePercent 参数进行设置(默认是堆大小的 5%)。因为这种情况下, GC 会花费很多的时间但是回收到的内存却很少。所以这个参数也是可以调整 Mixed GC 的频率的。</p>
|
||
<p>还有参数 G1MixedGCCountTarget,用于控制一次并发标记之后,最多执行 Mixed GC 的次数。</p>
|
||
<h2><strong>ZGC</strong></h2>
|
||
<p>你有没有感觉,在系统切换到 G1 垃圾回收器之后,线上发生的严重 GC 问题已经非常少了?</p>
|
||
<p>这归功于 G1 的预测模型和它创新的分区模式。但预测模型也会有失效的时候,它并不是总如我们期望的那样运行,尤其是你给它定下一个苛刻的目标之后。</p>
|
||
<p>另外,如果应用的内存非常吃紧,对内存进行部分回收根本不够,始终要进行整个 Heap 的回收,那么 G1 要做的工作量就一点也不会比其他垃圾回收器少,而且因为本身算法复杂了,还可能比其他回收器要差。</p>
|
||
<p>所以垃圾回收器本身的优化和升级,从来都没有停止过。最新的 ZGC 垃圾回收器,就有 3 个令人振奋的 Flag:</p>
|
||
<ol>
|
||
<li>停顿时间不会超过 10ms;</li>
|
||
<li>停顿时间不会随着堆的增大而增大(不管多大的堆都能保持在 10ms 以下);</li>
|
||
<li>可支持几百 M,甚至几 T 的堆大小(最大支持 4T)。</li>
|
||
</ol>
|
||
<p><img src="assets/CgpOIF4lSaqAXfe5AAA_S1VhbdY081.jpg" alt="img" /></p>
|
||
<p>在 ZGC 中,连逻辑上的年轻代和老年代也去掉了,只分为一块块的 page,每次进行 GC 时,都会对 page 进行压缩操作,所以没有碎片问题。ZGC 还能感知 NUMA 架构,提高内存的访问速度。与传统的收集算法相比,ZGC 直接在对象的引用指针上做文章,用来标识对象的状态,所以它只能用在 64 位的机器上。</p>
|
||
<p>现在在线上使用 ZGC 的还非常少。即使是用,也只能在 Linux 平台上使用。等待它的普及,还需要一段时间。</p>
|
||
<h2><strong>小结</strong></h2>
|
||
<p>本课时,我们简要看了下 G1 垃圾回收器的回收过程,并着重看了一下底层的数据结构 RSet。基本思想很简单,但实现细节却特别多。这不是我们的重点,对 G1 详细过程感兴趣的,可以参考纸质书籍。我也会通过其他途径分享一些细节,你也可以关注拉勾教育公众号后进学习群与大家一起多多交流。</p>
|
||
<p>相对于 CMS,G1 有了更可靠的驾驭度。而且有 RSet 和 SATB 等算法的支撑,Remark 阶段更加高效。</p>
|
||
<p>G1 最重要的概念,其实就是 Region。它采用分而治之,部分收集的思想,尽力达到我们给它设定的停顿目标。</p>
|
||
<p>G1 的垃圾回收过程分为三种,其中,并发标记阶段,为更加复杂的 Mixed GC 阶段做足了准备。</p>
|
||
<p>以下是一个线上运行系统的 JVM 参数样例。这些参数,现在你都能看懂么?如果有问题可以在评论区讨论。</p>
|
||
<pre><code>JAVA_OPTS="$JAVA_OPTS -XX:NewRatio=2 -XX:G1HeapRegionSize=8m -XX:MetaspaceSize
|
||
=256m -XX:MaxMetaspaceSize=256m -XX:MaxTenuringThreshold=10 -XX:+UseG1GC
|
||
-XX:InitiatingHeapOccupancyPercent=45 -XX:MaxGCPauseMillis=200 -verbose:gc
|
||
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintReferenceGC
|
||
-XX:+PrintAdaptiveSizePolicy -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=6
|
||
-XX:GCLogFileSize=32m -Xloggc:./var/run/gc.log.$(date +%Y%m%d%H%M)
|
||
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./var/run/heap-dump.hprof
|
||
-Dfile.encoding=UTF-8 -Dcom.sun.management.jmxremote -Dcom.sun.management.
|
||
jmxremote.port=${JMX_PORT:-0} -Dcom.sun.management.jmxremote.ssl=false
|
||
-Dcom.sun.management.jmxremote.authenticate=false"
|
||
</code></pre>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div style="float: left">
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/07 深入剖析:垃圾回收你真的了解吗?(下).md.html">上一页</a>
|
||
</div>
|
||
<div style="float: right">
|
||
<a href="/专栏/深入浅出 Java 虚拟机-完/09 案例实战:亿级流量高并发下如何进行估算和调优.md.html">下一页</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
|
||
</div>
|
||
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997a556b523cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
|
||
</body>
|
||
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
|
||
<script>
|
||
window.dataLayer = window.dataLayer || [];
|
||
function gtag() {
|
||
dataLayer.push(arguments);
|
||
}
|
||
gtag('js', new Date());
|
||
gtag('config', 'G-NPSEEVD756');
|
||
var path = window.location.pathname
|
||
var cookie = getCookie("lastPath");
|
||
console.log(path)
|
||
if (path.replace("/", "") === "") {
|
||
if (cookie.replace("/", "") !== "") {
|
||
console.log(cookie)
|
||
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
|
||
}
|
||
} else {
|
||
setCookie("lastPath", path)
|
||
}
|
||
function setCookie(cname, cvalue) {
|
||
var d = new Date();
|
||
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
|
||
var expires = "expires=" + d.toGMTString();
|
||
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
|
||
}
|
||
function getCookie(cname) {
|
||
var name = cname + "=";
|
||
var ca = document.cookie.split(';');
|
||
for (var i = 0; i < ca.length; i++) {
|
||
var c = ca[i].trim();
|
||
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
|
||
}
|
||
return "";
|
||
}
|
||
</script>
|
||
</html>
|