一,七款经典垃圾收集器
七款经典的垃圾收集器:
收集器 | 串行、并行or并发 | 新生代/老年代 | 算法 | 目标 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行 | 新生代 | 复制算法 | 响应速度优先 | 单CPU环境下的Client模式 |
Serial Old | 串行 | 老年代 | 标记-整理 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
ParNew | 并行 | 新生代 | 复制算法 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 并行 | 新生代 | 复制算法 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
Parallel Old | 并行 | 老年代 | 标记-整理 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 并发 | 老年代 | 标记-清除 | 响应速度优先 | 集中在互联网站或B/S系统服务端上的Java应用 |
G1 | 并发 | both | 标记-整理+复制算法 | 响应速度优先 | 面向服务端应用,将来替换CMS |
串行和并行在垃圾回收时,会暂停一切工作,又称为Stop The World。
七款经典的垃圾收集器垃圾收集器之间组合关系:
- 黑色实线表示可以搭配使用。
- 红色虚线表示JDK8中这种组合被废弃,但是还可以使用,在JDK9中就被移除了,完全不能使用这两种组合了。
- 蓝色虚线表示JDK14中,这种组合被废弃了。
JDK8
中默认的组合是Parallel Scavenger GC
和Parallel Old GC
,JDK9
以后默认的垃圾回收器是G1 GC
,没有哪一种垃圾回收器可以绝对的说是最好的,不同的场景选择不同的垃圾回收器会更好。
1.1 Serial收集器
1.1.1 Serial
Serial在JDK1.3之前是Java虚拟机新生代收集器的唯一选择,这是一个单线程工作的收集器。在进行垃圾回收的时候,需要暂停所有的用户线程,直到回收结束。虽然历史久远,但它依然是HotSpot虚拟机运行在客户端模式下,或者4核4GB以下服务端的默认新生代收集器,这种核心数和内存空间较小的场景下,它单线程的优势就体现出来了,没有线程交互的开销,加上内存空间不大,单次回收耗时几十毫秒,这点停顿时间,完全是可以接受的。
1.1.2 SerialOld
SerialOld是Serial 收集器的老年代版本,和Serial一样,它也是单线程的收集器。目前主要应用在客户端模式(Client VM)下的HotSpot虚拟机使用,如果在服务端模式(Server VM)下,它也有两种用途:一个是在JDK5以及之前,和Parallel Scavenge收集器搭配使用,另外一个就是作为CMS收集器在出现并发模式故障(Concurrent Mode Failure) 时作为后备收集器。
1.2 ParNew收集器
随着计算机的核心数和内存容量都在飞速发展,多核心和大内存容量的场景下,Serial 收集器单线程的性能明显比较落后了,ParNew 就是在Serial 收集器的基础之上,实现了它的多线程版本。它可以多条线程同时进行垃圾收集,这也是它和 Serial 收集器的最大的区别,其他的功能性、配置、策略等等的和 Serial 基本一致。
ParNew 是JDK7之前 Server VM 模式下的首选的新生代收集器,但是在单CPU的情况下,它的效率不会比 Serial收集器高的,所以要注意使用场景
ParNew在JDK9之后,ParNew只能和CMS收集器老年代收集器的搭配使用,取消了和其它老年代收集器的搭配使用,而且还取消了 -XX:+UseParNewGC
这个参数。所以JDK9之后,ParNew只能和CMS搭配使用了.
1.3 Parallel收集器
1.3.1 Parallel Scavenge
Parallel Scavenge 和 ParNew 很相似,都是新生代的收集器,支持多线程并行回收,也同样是使复制来作为回收算法。但 Parallel Scavenge 和 ParNew 关注点不一样,区别如下所示:
- ParNew出发点在于加速资源回收的速度,以减少应用的STW时间。
- Parallel Scavenge 出发点在于资源回收的吞吐量(吞吐量:用户线程时间/(用户线程时间 + GC线程时间))。
Parallel Scavenge 收集器提供了一些参数,给用户按自身需求控制吞吐量:
-XX:MaxGCPauseMillis
:控制垃圾收集停顿的最大时间,单位是毫秒,可以设置一个大于0的数值。不要想着把这个数值设置得很小来提升垃圾收集的速度,这里缩短的停顿时间是以牺牲新生代空间大小换来的,空间小,回收自然就快,停顿时间自然也短,但是空间小,吞吐量自然也会小。所以得综合考虑。-XX:GCTimeRatio
:设置垃圾收集时间占比的计算因子,参数范围是0 - 100的整数。它的公式是 1 / (1+GCTimeRatio)。举个栗子:当设置成15,那就是 1 / (1+15) = 0.0625,就是允许最大垃圾收集时间占总时间的6.25%,当设置成99的时候,就是 1 / (1+99) = 0.01,也就是允许最大垃圾收集时间占总时间的1%,依次类推。-XX:+UseAdaptiveSizePolicy
:动态调整开关,这个参数和 Parallel Scavenge 收集器无关,但是搭配起来使用是一个很好的选择。
当这个参数被激活,就不需要人工指定新生代的大小、Eden和Survivor区的比例、对象直接进入老年代的大小等等细节参数了,JVM会根据当前运行的情况动态调整,给出最合适的停顿时间和吞吐量。搭配以上两个参数,和把基本的内存数据设置好即可,例如堆的最大占用空间等等。
1.3.2 Parallel Old
Parallel Old就像 Serial Old 是 Serial 的老年代版本一样,Parallel Old 是 Parallel Scavenge 的老年代版本。
Parallel Old 也支持多线程并行回收的能力,使用标记整理来作为回收算法。这个收集器是JDK6的时候推出的,和 Parallel Scavenge 搭配,在多CPU核心和大内存的场景下,吞吐性能优秀.
在注重吞吐量和多CPU核心的情况下,都可以优先考虑 Parallel Scavenge + Parallelo Old 收集器,这也是JDK8默认的垃圾收集器组合。
1.4 CMS收集器
CMS(Concurrent Mark Sweep)收集器是jdk4推出的第一款真正意义上的并发收集器**(**针对老年代),实现了让垃圾收集器与用户线程近似同时工作,其具有以下特点:
- 基于"标记-清除"算法;
- 以获取最短回收停顿时间为目标;
- 并发收集,停顿时间短。
CMS的垃圾收集过程比较复杂,主要步骤如下所示:
①,初始标记阶段
- 单线程执行
- 需要“Stop The World”
- 但仅仅把GC Roots的直接关联可达的对象给标记一下,由于直接关联对象比较小,所以这里的速度非常快
②,并发标记(concurrent mark)
- 对于初始标记过程所标记的初始标记对象,进行并发追踪标记,
- 此时其他线程仍可以继续工作。
- 此处时间较长,但不停顿。
- 并不能保证可以标记出所有的存活对象;
③,重新标记(remark)
- 在并发标记的过程中,由于可能还会产生新的垃圾,所以此时需要重新标记新产生的垃圾。
- 此处执行并行标记,与用户线程不并发,所以依然是“Stop The World”,
- 且停顿时间比初始标记稍长,但远比并发标记短。
④,并发清除(concurrent sweep)
- 并发清除之前所标记的垃圾。
- 其他用户线程仍可以工作,不需要停顿。
以上步骤中,最为耗费时间的并发标记与并发清除阶段,不需要应用程序暂停执行,所以垃圾回收的停顿时间较短。
CMS确实是非常优秀的垃圾收集器,但它也是有缺点的:
-
对CPU资源敏感:并发收集虽然不会暂停应用程序,但是会占用CPU资源从而降低应用程序的执行效率(CMS默认收集线程数量=(CPU数量 + 3) / 4);
-
产生浮动垃圾:在并发清除时,用户线程会产生新的垃圾,称之为浮动垃圾(并发清除时需要预留内存空间,不能像其他收集器在老年代几乎填满之后再进行收集工作)。
-
产生空间碎片:使用"标记-清除"算法,会产生大量不连续的内存碎片,从而导致在分配大内存对象时,无法找到足够的连续内存,从而需要提前触发一次Full GC操作。
针对以上缺点,可以从如下参数进行改进:
-XX:ConcGCThreads
: 并发的GC线程数,从而降低CPU敏感度;-XX:CMSInitiatingOccupancyFraction
:合理设置CMS的预留内存空间;-XX:+UseCMSCompactAtFullGCCollection
: FullGC之后执行压缩操作,消减内存碎片;-XX:CMSFullGCBeforeCompaction
: 执行多次FullGC之后执行压缩操作,消减内存碎片。
1.5 G1收集器
需要注意的是G1垃圾收集器在新生代以及老年代都能进行工作,这是因为相比于前面所介绍的垃圾收集器,它具有不同的堆内存结构。以前的垃圾收集器分代是划分为新生代、老牛代、持久带等,G1将内存划分为多个大小相同的Region(1-32M,上限2048个),每个Region均拥有自己的分代属性,这些分代不需要连续。通过划分Region,G1可以根据计算老年代对象的效益率,优先回收具有最高效益率的对象(分代的内存不连续,GC搜索垃圾时需要全盘扫描找出对象引用情况,G1通过在每个Region中维护一个Remembered Set记录对象引用情况解决此问题)。具体如下图所示:
G1提供了两种GC模式:
- Young GC:选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。
- Mixed GC:选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高的若干老年代Region,在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
Young GC以及Mixed GC,两种GC都会STW,Mixed GC不是Full GC,它只能回收部分老年代的Region,如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap(此时效率就会很低下)。所以我们可以知道,G1是不提供Full GC的。
在执行Mixed GC之前需要进行并发标记过程(Global Concurrent Marking),具体步骤如下:
①、初始标记(Initial Marking)
初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。(OopMap)
②、并发标记(Concurrent Marking)
进行GC Roots Tracing的过程,从刚才产生的集合中标记出存活对象;(也就是从GC Roots 开始对堆进行可达性分析,找出存活对象。)耗时较长,但应用程序也在运行;并不能保证可以标记出所有的存活对象;
③、最终标记(Final Marking)
最终标记和CMS的重新标记阶段一样,也是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,也需要“Stop The World”。(修正Remebered Set)
④、筛选回收(Live Data Counting and Evacuation)
- 首先排序各个Region的回收价值和成本;
- 然后根据用户期望的GC停顿时间来制定回收计划;
- 最后按计划回收一些价值高的Region中垃圾对象;
- 回收时采用"复制"算法,从一个或多个Region复制存活对象到堆上的另一个空的Region,并且在此过程中压缩和释放内存;
- 可以并发进行,降低停顿时间,并增加吞吐量;
G1收集器参数设置
-XX:+UseG1GC
:使用G1收集器-XX:ParallelGCThreads
:指定GC工作的线程数量-XX:G1HeapRegionSize
:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区-XX:MaxGCPauseMillis
:目标暂停时间(默认200ms)-XX:G1NewSizePercent
:新生代内存初始空间(默认整堆5%,值配置整数,默认就是百分比)-XX:G1MaxNewSizePercent
:新生代内存最大空间-XX:TargetSurvivorRatio
:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代-XX:MaxTenuringThreshold
:最大年龄阈值(默认15)-XX:InitiatingHeapOccupancyPercent
:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能就要触发MixedGC了-XX:G1MixedGCLiveThresholdPercent
(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这个值,存活对象过多,回收的的意义不大。-XX:G1MixedGCCountTarget
:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。-XX:G1HeapWastePercent
(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。
G1垃圾收集器优化建议
假设参数 -XX:MaxGCPauseMills
设置的值很大,导致系统运行很久,年轻代可能都占用了堆内存的60%了,此时才触发年轻代gc。
那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。或者是你年轻代gc过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor区域的50%,也会快速导致一些对象进入老年代中。所以这里核心还是在于调节 -XX:MaxGCPauseMills
这个参数的值,在保证他的年轻代gc别太频繁的同时,还得考虑每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc.
什么场景适合使用G1
-
50%以上的堆被存活对象占用
-
对象分配和晋升的速度变化非常大
-
垃圾回收时间特别长,超过1秒
-
8GB以上的堆内存(建议值)
-
停顿时间是500ms以内
1.6 ZGC收集器
ZGC是一款JDK 11中新加入的具有实验性质的低延迟垃圾收集器,ZGC可以说源自于是Azul System公司开发的C4(Concurrent Continuously Compacting Collector) 收集器。
Oracle官方提到了它最大的优点是,它的停顿时间不会随着堆的增大而增长!也就是说,几十G堆的停顿时间是10ms以下,几百G甚至上T堆的停顿时间也是10ms以下。
不分代(暂时):单代,即ZGC「没有分代」。我们知道以前的垃圾回收器之所以分代,是因为源于“「大部分对象朝生夕死」”的假设,事实上大部分系统的对象分配行为也确实符合这个假设。那么为什么ZGC就不分代呢?因为分代实现起来麻烦,作者就先实现出一个比较简单可用的单代版本,后续会优化。
ZGC的目标:
- 支持TB量级的堆。我们生产环境的硬盘还没有上TB呢,这应该可以满足未来十年内,所有JAVA应用的需求了吧。
- 最大GC停顿时间不超10ms。目前一般线上环境运行良好的JAVA应用Minor GC停顿时间在10ms左右,Major GC一般都需要100ms以上(G1可以调节停顿时间,但是如果调的过低的话,反而会适得其反),之所以能做到这一点是因为它的停顿时间主要跟Root扫描有关,而Root数量和堆大小是没有任何关系的。
- 奠定未来GC特性的基础。
- 最糟糕的情况下吞吐量会降低15%。这都不是事,停顿时间足够优秀。至于吞吐量,通过扩容分分钟解决。
1.6.1 ZGC内存布局
ZGC收集器是一款基于Region内存布局的, 暂时不设分代的, 使用了读屏障、 颜色指针等技术来实现可并发的标记整理算法的, 以低延迟为首要目标的一款垃圾收集器。
ZGC的Region可以具有如所示的大、 中、 小三类容量:
- 小型Region(Small Region) : 容量固定为2MB, 用于放置小于256KB的小对象。
- 中型Region(Medium Region) : 容量固定为32MB, 用于放置大于等于256KB但小于4MB的对象。
- 大型Region(Large Region) : 容量不固定, 可以动态变化, 但必须为2MB的整数倍, 用于放置4MB或以上的大对象。 每个大型Region中只会存放一个大对象, 这也预示着虽然名字叫作“大型Region”, 但它的实际容量完全有可能小于中型Region, 最小容量可低至4MB。 大型Region在ZGC的实现中是不会被重分配(重分配是ZGC的一种处理动作, 用于复制对象的收集器阶段, 稍后会介绍到)的, 因为复制一个大对象的代价非常高昂。
1.6.2 NUMA-aware
NUMA对应的有UMA,UMA即Uniform Memory Access Architecture。UMA表示内存只有一块,所有CPU都去访问这一块内存,那么就会存在竞争问题(争夺内存总线访问权),有竞争就会有锁,有锁效率就会受到影响,而且CPU核心数越多,竞争就越激烈。NUMA的话每个CPU对应有一块内存,且这块内存在主板上离这个CPU是最近的,每个CPU优先访问这块内存,那效率自然就提高了:
服务器的NUMA架构在中大型系统上一直非常盛行,也是高性能的解决方案,尤其在系统延迟方面表现都很优秀。ZGC是能自动感知NUMA架构并充分利用NUMA架构特性的。
1.6.4 ZGC运作过程
ZGC的运作过程大致可划分为以下四个大的阶段:
-
并发标记(Concurrent Mark):与G1一样,并发标记是遍历对象图做可达性分析的阶段,它的初始标记和最终标记Mark End也会出现短暂的停顿,与G1不同的是, ZGC的标记是在指针上而不是在对象上进行的, 标记阶段会更新颜色指针(见下面详解)中的Marked 0、 Marked 1标志位。
-
并发预备重分配(Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成重分配集(Relocation Set)。ZGC每次回收都会扫描所有的Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。
-
并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一转发表(Forward Table),记录从旧对象到新对象的转向关系。ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障(读屏障(见下面详解))所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。ZGC的颜色指针因为“自愈”(Self-Healing)能力,所以只有第一次访问旧对象会变慢, 一旦重分配集中某个Region的存活对象都复制完毕后, 这个Region就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉, 因为可能还有访问在使用这个转发表。
-
并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,但是ZGC中对象引用存在“自愈”功能,所以这个重映射操作并不是很迫切。ZGC很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节省了一次遍历对象图的开销。一旦所有指针都被修正之后, 原来记录新旧对象关系的转发表就可以释放掉了
1.6.5 颜色指针
Colored Pointers,即颜色指针,如下图所示,ZGC的核心设计之一。以前的垃圾回收器的GC信息都保存在对象头中,而ZGC的GC信息保存在指针中。
- 18位:预留给以后使用;
- 1位:Finalizable标识,此位与并发引用处理有关,它表示这个对象只能通过finalizer才能访问;
- 1位:Remapped标识,设置此位的值后,对象未指向relocation set中(relocation set表示需要GC的Region集合);
- 1位:Marked1标识;
- 1位:Marked0标识,和上面的Marked1都是标记对象用于辅助GC;
- 42位:对象的地址(所以它可以支持2^42=4T内存)
为什么有2个mark标记?
每一个GC周期开始时,会交换使用的标记位,使上次GC周期中修正的已标记状态失效,所有引用都变成未标记。
-
GC周期1:使用mark0, 则周期结束所有引用mark标记都会成为01。
-
GC周期2:使用mark1, 则期待的mark标记10,所有引用都能被重新标记。
通过对配置ZGC后对象指针分析我们可知,对象指针必须是64位,那么ZGC就无法支持32位操作系统,同样的也就无法支持压缩指针了(CompressedOops,压缩指针也是32位)。
颜色指针的三大优势:
- 一旦某个Region的存活对象被移走之后,这个Region立即就能够被释放和重用掉,而不必等待整个堆中所有指向该Region的引用都被修正后才能清理,这使得理论上只要还有一个空闲Region,ZGC就能完成收集。
- 颜色指针可以大幅减少在垃圾收集过程中内存屏障的使用数量,ZGC只使用了读屏障。
- 颜色指针具备强大的扩展性,它可以作为一种可扩展的存储结构用来记录更多与对象标记、重定位过程相关的数据,以便日后进一步提高性能。
1.6.6 读屏障
之前的GC都是采用Write Barrier,这次ZGC采用了完全不同的方案读屏障,这个是ZGC一个非常重要的特性。
在标记和移动对象的阶段,每次「从堆里对象的引用类型中读取一个指针」的时候,都需要加上一个Load Barriers。
那么我们该如何理解它呢?看下面的代码,第一行代码我们尝试读取堆中的一个对象引用obj.fieldA并赋给引用o(fieldA也是一个对象时才会加上读屏障)。如果这时候对象在GC时被移动了,接下来JVM就会加上一个读屏障,这个屏障会把读出的指针更新到对象的新地址上,并且把堆里的这个指针“修正”到原本的字段里。这样就算GC把对象移动了,读屏障也会发现并修正指针,于是应用代码就永远都会持有更新后的有效指针,而且不需要STW。
那么,JVM是如何判断对象被移动过呢?就是利用上面提到的颜色指针,如果指针是Bad Color,那么程序还不能往下执行,需要「slow path」,修正指针;如果指针是Good Color,那么正常往下执行即可:
这个动作是不是非常像JDK并发中用到的CAS自旋?读取的值发现已经失效了,需要重新读取。而ZGC这里是之前持有的指针由于GC后失效了,需要通过读屏障修正指针。
后面3行代码都不需要加读屏障:Object p = o这行代码并没有从堆中读取数据;o.doSomething()也没有从堆中读取数据;obj.fieldB不是对象引用,而是原子类型。
正是因为Load Barriers的存在,所以会导致配置ZGC的应用的吞吐量会变低。官方的测试数据是需要多出额外4%的开销:
那么,判断对象是Bad Color还是Good Color的依据是什么呢?就是根据上一段提到的Colored Pointers的4个颜色位。当加上读屏障时,根据对象指针中这4位的信息,就能知道当前对象是Bad/Good Color了。
既然低42位指针可以支持4T内存,那么能否通过预约更多位给对象地址来达到支持更大内存的目的呢?答案肯定是不可以。因为目前主板地址总线最宽只有48bit,4位是颜色位,就只剩44位了,所以受限于目前的硬件,ZGC最大只能支持16T的内存,JDK13就把最大支持堆内存从4T扩大到了16T。
1.6.7 ZGC存在的问题
ZGC最大的问题是浮动垃圾。ZGC的停顿时间是在10ms以下,但是ZGC的执行时间还是远远大于这个时间的。假如ZGC全过程需要执行10分钟,在这个期间由于对象分配速率很高,将创建大量的新对象,这些对象很难进入当次GC,所以只能在下次GC的时候进行回收,这些只能等到下次GC才能回收的对象就是浮动垃圾。
目前唯一的解决办法是增大堆的容量,使得程序得到更多的喘息时间,但是这个也是一个治标不治本的方案。如果需要从根本上解决这个问题,还是需要引入分代收集,让新生对象都在一个专门的区域中创建,然后专门针对这个区域进行更频繁、更快的收集。
1.6.8 ZGC参数设置
启用ZGC比较简单,设置JVM参数即可:-XX:+UnlockExperimentalVMOptions 「-XX:+UseZGC」
。调优也并不难,因为ZGC调优参数并不多,远不像CMS那么复杂。它和G1一样,可以调优的参数都比较少,大部分工作JVM能很好的自动完成。
通用参数:
-XX:MinHeapSize, -Xms
最小堆大小 (default = 8388608 = 8M)
-XX:InitialHeapSize, -Xms
初始化堆大小 (default = 134217728 = 128M )
-XX:MaxHeapSize, -Xmx
最大堆大小 (default = 2134900736 = 2036M)
-XX:SoftMaxHeapSize
JVM堆的最大软限制 (default = 2134900736 = 2036M)
-XX:ConcGCThreads
并发GC的线程数量(default -XX:+ConcGCThreads=1 )
-XX:ParallelGCThreads
设置垃圾回收时的并行GC线程数量 (default = 4 )
-XX:UseLargePages
使用大页面内存 (dafault false)
-XX:UseTransparentHugePages
使用Transparent大页面内存
-XX:UseNUMA
使用UNMA内存分配,可以获得更好的性能
-XX:SoftRefLRUPolicyMSPerMB
每MB的空闲内存空间允许软引用对象存活时间(default = 1000)
-XX:AllocateHeapAt
堆分配参数,可以使用非DRAM 内存,这个参数将指向文件系统的文件并使用内存映射来达到在备用存储设备上进行堆分配。
特有的参数:
-XX:ZAllocationSpikeTolerance
修正系数,数值越大,越早触发GC (default = 2.000000)
-XX:ZCollectionInterval
ZGC发生的最小时间间隔 ,秒 (default = 0.000000)
-XX:ZFragmentationLimit relocation
时,当前region碎片化大于此值,则回收region (default = 25.000000)
-XX:ZMarkStackSpaceLimit
指定为标记堆栈分配的最大字节数 (default = 8589934592 = 8096M)
-XX:ZProactive
是否启用主动回收 (default true)
-XX:ZUncommit
是否归还不使用的内存给OS(default true)
-XX:ZUncommitDelay
不再使用的内存最多延迟多久会归还给OS (default = 300 s)
诊断参数:
-XX:+UnlockDiagnosticVMOptions
使用诊断模式,下面的参数才会起作用
-XX:ZStatisticsInterval
指定统计数据输出之间的时间间隔(秒)。
-XX:ZVerifyForwarding
检验转发表
-XX:ZVerifyMarking
检验标记集
-XX:ZVerifyObjects
检验对象
-XX:ZVerifyRoots
检验根节点
-XX:ZVerifyViews
检验堆视图访问
ZGC触发时机
ZGC目前有4中机制触发GC:
- 定时触发,默认为不使用,可通过ZCollectionInterval参数配置。
- 预热触发,最多三次,在堆内存达到10%、20%、30%时触发,主要时统计GC时间,为其他GC机制使用。
- 分配速率,基于正态分布统计,计算内存99.9%可能的最大分配速率,以及此速率下内存将要耗尽的时间点,在耗尽之前触发GC(耗尽时间 - 一次GC最大持续时间 - 一次GC检测周期时间)。
- 主动触发,(默认开启,可通过ZProactive参数配置) 距上次GC堆内存增长10%,或超过5分钟时,对比距上次GC的间隔时间跟(49 * 一次GC的最大持续时间),超过则触发。
1.7 安全点与安全区域(补充)
①,安全点就是指代码中一些特定的位置,当线程运行到这些位置时它的状态是确定的,这样JVM就可以安全的进行一些操作,比如GC等,所以GC不是想什么时候做就立即触发的,是需要等待所有线程运行到安全点后才能触发。
这些特定的安全点位置主要有以下几种:
- 方法返回之前
- 调用某个方法之后
- 抛出异常的位置
- 循环的末尾
大体实现思想是当垃圾收集需要中断线程的时候, 不直接对线程操作, 仅仅简单地设置一个标志位, 各个线程执行过程时会不停地主动去轮询这个标志, 一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。 轮询标志的地方和安全点是重合的。
②,安全区域:Safe Point 是对正在执行的线程设定的。如果一个线程处于 Sleep 或中断状态,它就不能响应 JVM 的中断请求,再运行到 Safe Point 上。因此 JVM 引入了 Safe Region。Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的。