对于Java应用的调优,主要就集中于这两个指标
为了很好的满足这两个指标,G1垃圾收集器应运而生
G1收集器是一个面向服务端的非实时垃圾收集器,适用于多核处理器、大内存容量的服务端系统,它满足短时间GC停顿的同时达到一个较高的吞吐量,JDK 7以上版本适用
JVM的性能主要由Heap、JIT Compiler、Garbage Collector控制,堆是对象存储的位置,这个区域主要由JVM启动时所选择的垃圾收集器所管理。大多数调优操作都是和调整堆的大小有关并且选择最适合当前形势的垃圾收集器。JIT Compiler对性能也有很大的影响,但是在JVM的新版本中很少需要对JIT Compiler进行调优
注:永久代在JDK 8后进行了移除,并用元空间进行替换
G1提供了两种GC模式——Young GC和Mixed GC,两种都是完全Stop The World的
Mixed GC不是Full GC,它只能回收部分老年代的region,如果Mixed GC实在无法跟上程序分配内存的速度,导致老年代填满,无法继续进行Mixed GC,就会使用serial old GC(Full GC)来收集整个GC heap,所以本质上G1是不提供Full GC的
global concurrent marking的执行过程类似于CMS,但是不同的是,在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节
global concurrent marking的执行过程分为四个步骤:
初始标记是共用了Young GC的暂停,只是因为它们可以复用root scan操作,所以说global concurrent marking是伴随Young GC而发生的,清理只是回收了没有存活对象的region,所以它不需要STW
由一些参数控制,另外也控制着哪些老年代region会被纳入到CSet(收集集合)
Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行(前面整个过程都是STW的)
这时,我们需要考虑一个问题,如果仅仅GC新生代对象,我们如何找到所有的根对象呢? 老年代的所有对象都是根么?那这样扫描下来会耗费大量的时间。于是,G1引进了RSet的概念,它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用
在CMS中,也有RSet的概念,在老年代中有一块区域用来记录指向新生代的引用。这是一种point-out,在进行Young GC时,扫描根时,仅仅需要扫描这一块区域,而不需要扫描整个老年代
但在G1中,并没有使用point-out,这是由于一个分区太小,分区数量太多,如果是用point-out的话,会造成大量的扫描浪费,有些根本不需要GC的分区引用也扫描了
于是G1中使用point-into来解决。point-into的意思是哪些分区引用了当前分区中的对象。这样,仅仅将这些对象当做根来扫描就避免了无效的扫描
由于新生代有多个,那么我们需要在新生代之间记录引用吗?这是不必要的,原因在于每次GC时,所有新生代都会被扫描,所以只需要记录老年代到新生代之间的引用即可
需要注意的是,如果引用的对象很多,赋值器需要对每个引用做处理,赋值器开销会很大,为了解决赋值器开销这个问题,在G1 中又引入了另外一个概念,卡表(Card Table)。一个Card Table将一个分区在逻辑上划分为固定大小的连续区域,每个区域称之为卡。卡通常较小,介于128到512字节之间。Card Table通常为字节数组,由Card的索引(即数组下标)来标识每个分区的空间地址
默认情况下,每个卡都未被引用。当一个地址空间被引用时,这个地址空间对应的数组索引的值被标记为”0″,即标记为脏被引用,此外RSet也将这个数组下标记录下来。一般情况下,这个RSet其实是一个Hash Table,Key是别的Region(即引用当前region的region)的起始地址,Value是一个集合,里面的元素是Card Table的Index
Young GC 可以分为如下5个阶段:
阶段1:根扫描——静态和本地对象被扫描
阶段2:更新RS——处理dirty card队列更新RS
阶段3:处理RS——检测从年轻代指向年老代的对象
阶段4:对象拷贝——拷贝存活的对象到survivor/old区域
阶段5:处理引用队列——软引用,弱引用,虚引用处理
Mixed GC不仅进行正常的新生代垃圾收集,同时也回收部分后台扫描线程标记的老年代分区
它的GC步骤分为两步:
在G1 GC中,它主要是为Mixed GC提供标记服务的,并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为四个步骤,具体步骤见上文
提到并发标记,我们不得不了解并发标记的三色标记算法。它是描述追踪式回收器的一种有用的方法,利用它可以推演回收器的正确性。
我们将对象分成三种类型的:
上面的过程看起来没有什么问题,但是如果在标记过程中,应用程序也在运行,那么对象的指针就有可能改变。这样的话,我们就会遇到一个问题:对象丢失问题
A.c = C;
B.c = null;
如何找到在GC过程中分配的对象呢?
解决了对象在GC过程中分配的问题,那么在GC过程中引用发生变化的问题怎么解决呢?
三色标记算法可能造成漏标的几种情形:
对于三色算法在concurrent的时候可能产生的漏标记问题,SATB在marking阶段中,对于从gray对象移除的目标引用对象标记为gray,对于black引用的新产生的对象标记为black;由于是在开始的时候进行snapshot,因而可能存在Floating Garbage
漏标与误标:误标没什么关系,顶多造成浮动垃圾,在下次GC还是可以回收的,但是漏标的后果是致命的,把本应该存活的对象给回收了,从而影响的程序的正确性
分代GC模式下选择CSet有两种子模式,分别对应Young GC和Mixed GC
VM Option:-verbose:gc -Xms10m -Xmx10m -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:MaxGCPauseMillis=200m
public class MyTest1 {
public static void main(String[] args) {
int size = 1024 * 1024;
byte[] myAlloc1 = new byte[size];
byte[] myAlloc2 = new byte[size];
byte[] myAlloc3 = new byte[size];
byte[] myAlloc4 = new byte[size];
System.out.println("hello world");
}
}
输出结果:
2020-03-26T10:10:49.680+0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0016222 secs]
[Parallel Time: 1.1 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 120.5, Avg: 120.5, Max: 120.7, Diff: 0.2]
[Ext Root Scanning (ms): Min: 0.4, Avg: 0.6, Max: 0.7, Diff: 0.2, Sum: 4.5]
[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.0]
[Object Copy (ms): Min: 0.4, Avg: 0.4, Max: 0.4, Diff: 0.1, Sum: 3.0]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[Termination Attempts: Min: 3, Avg: 7.8, Max: 14, Diff: 11, Sum: 62]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[GC Worker Total (ms): Min: 0.8, Avg: 1.0, Max: 1.1, Diff: 0.2, Sum: 7.9]
[GC Worker End (ms): Min: 121.5, Avg: 121.5, Max: 121.5, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
[Other: 0.4 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 2048.0K(4096.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 3946.2K(10.0M)->2816.1K(10.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
2020-03-26T10:10:49.682+0800: [GC concurrent-root-region-scan-start]
2020-03-26T10:10:49.682+0800: [GC pause (G1 Humongous Allocation) (young)2020-03-26T10:10:49.683+0800: [GC concurrent-root-region-scan-end, 0.0008804 secs]
2020-03-26T10:10:49.683+0800: [GC concurrent-mark-start]
, 0.0016254 secs]
[Root Region Scan Waiting: 0.3 ms]
[Parallel Time: 0.7 ms, GC Workers: 8]
[GC Worker Start (ms): Min: 123.1, Avg: 123.3, Max: 123.7, Diff: 0.6]
[Ext Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.3, Diff: 0.3, Sum: 0.9]
[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.0]
[Object Copy (ms): Min: 0.0, Avg: 0.3, Max: 0.5, Diff: 0.5, Sum: 2.5]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.2]
[Termination Attempts: Min: 1, Avg: 3.4, Max: 7, Diff: 6, Sum: 27]
[GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[GC Worker Total (ms): Min: 0.1, Avg: 0.5, Max: 0.7, Diff: 0.6, Sum: 3.7]
[GC Worker End (ms): Min: 123.8, Avg: 123.8, Max: 123.8, Diff: 0.0]
[Code Root Fixup: 0.0 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.1 ms]
[Other: 0.4 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 0.1 ms]
[Ref Enq: 0.0 ms]
[Redirty Cards: 0.2 ms]
[Humongous Register: 0.0 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.0 ms]
[Eden: 1024.0K(2048.0K)->0.0B(1024.0K) Survivors: 1024.0K->1024.0K Heap: 3881.1K(10.0M)->3992.5K(10.0M)]
[Times: user=0.00 sys=0.00, real=0.00 secs]
2020-03-26T10:10:49.684+0800: [GC concurrent-mark-end, 0.0014553 secs]
2020-03-26T10:10:49.684+0800: [Full GC (Allocation Failure) 3992K->3722K(10M), 0.0028500 secs]
[Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 1024.0K->0.0B Heap: 3992.5K(10.0M)->3722.0K(10.0M)], [Metaspace: 3355K->3355K(1056768K)]
[Times: user=0.11 sys=0.00, real=0.00 secs]
2020-03-26T10:10:49.687+0800: [GC remark, 0.0000163 secs]
[Times: user=0.00 sys=0.00, real=0.00 secs]
2020-03-26T10:10:49.688+0800: [GC concurrent-mark-abort]
hello world
Heap
garbage-first heap total 10240K, used 4746K [0x00000000ff600000, 0x00000000ff700050, 0x0000000100000000)
region size 1024K, 1 young (1024K), 0 survivors (0K)
Metaspace used 3448K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 376K, capacity 388K, committed 512K, reserved 1048576K
创建的字节数组为大对象:2020-03-26T09:44:27.320+0800: [GC pause (G1 Humongous Allocation) (young) (initial-mark), 0.0048562 secs]
对应Young GC的五个步骤:
[Ext Root Scanning (ms): Min: 0.7, Avg: 2.1, Max: 3.2, Diff: 2.5, Sum: 17.1]
[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]
[Object Copy (ms): Min: 0.0, Avg: 1.0, Max: 2.3, Diff: 2.3, Sum: 8.0]
[Termination (ms): Min: 0.0, Avg: 0.4, Max: 0.7, Diff: 0.7, Sum: 3.1]
处理card table:[Clear CT: 0.1 ms]
回收集合:[Clear CT: 0.1 ms]
执行完Young GC后堆的使用情况:[Eden: 2048.0K(4096.0K)->0.0B(2048.0K) Survivors: 0.0B->1024.0K Heap: 3946.2K(10.0M)->2784.1K(10.0M)]
默认的region大小以及young region个数以及survivor region个数:region size 1024K, 1 young (1024K), 0 survivors (0K)
——正好验证了为什么创建的数组都是Humongous,因为数组是连续占用内存空间,大小肯定已经超过一个region大小的50%