G1摘要

G1

启动参数示例

-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+PrintTenuringDistribution -XX:+UseG1GC -XX:+DisableExplicitGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:ParallelGCThreads=4 -Xloggc:/export/Logs/gclogs

  • 打印GC日志是很重要的,对性能基本没有影响
  • 设置一个合理的GC线程数是很有必要的,特别是当前容器环境,jvm可能获取到的是物理机的处理器个数来作为gc线程个数的基准,而实际上容器只是一个2核4G内存的虚拟机,无疑,GC线程数过多,反而对cpu的利用率不高,并且多线程回收时造成无谓的cpu竞争、切换上下文。

特点

G1采用分区的思路,用内存分为若干个大小相等的区域,每一块区域都可以为年轻代、老年代服务

Region

Old\Eden\Survivor\Humongous(H直接分配到Old,防止反复拷贝移动,大小大于等于region一半的对象)
一个Region的大小可以通过参数-XX:G1HeapRegionSize设定

参数说明

-XX:MaxGCPauseMillis:每次年轻代垃圾回收的最长时间(最大暂停时间), 如果无法满足此时间,JVM会自动调整年轻代大小,以满足此值.
-XX:GCTimeRatio:设置垃圾回收时间占程序运行时间的百分比,设置吞吐量大小,它的值是一个 0-100 之间的整数。假设 GCTimeRatio 的值为 n,那么系统将花费不超过 1/(1+n) 的时间用于垃圾收集。

RSet

已记忆集合 Remember Set (RSet):一个谁引用了我的机制,记录引用了当前region的分区内对象的卡片索引,当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,来确定本分区内的对象存活情况,如被引用的分区内对象全是垃圾了,则当前分区内的对象可能也是垃圾了。
Remembered Set是辅助GC过程的一种结构,典型的空间换时间工具,和Card Table有些类似。还有一种数据结构也是辅助GC的:Collection Set(CSet),它记录了GC要收集的Region集合,集合里的Region可以是任意年代的。在GC的时候,对于old->young和old->old的跨代对象引用,只要扫描对应的CSet中的RSet即可。
逻辑上说每个Region都有一个RSet,RSet记录了其他Region中的对象引用本Region中对象的关系,属于points-into结构(谁引用了我的对象)。而Card Table则是一种points-out(我引用了谁的对象)的结构,每个Card 覆盖一定范围的Heap(一般为512Bytes)。G1的RSet是在Card Table的基础上实现的:每个Region会记录下别的Region有指向自己的指针,并标记这些指针分别在哪些Card的范围内。 这个RSet其实是一个Hash Table,Key是别的Region的起始地址,Value是一个集合,里面的元素是Card Table的Index。

RSet究竟是怎么辅助GC的呢?在做YGC的时候,只需要选定young generation region的RSet作为根集,这些RSet记录了old->young的跨代引用,避免了扫描整个old generation。 而mixed gc的时候,old generation中记录了old->old的RSet,young->old的引用由扫描全部young generation region得到,这样也不用扫描全部old generation region。所以RSet的引入大大减少了GC的工作量。

RSET数据伪结构

[
{"region:[region`s card index]}...]

rset记录了引用了当前region的其他region,并精确到了card
card table记录了当前region引用了哪些region

G1提供了两种GC模式,Young GC和Mixed GC,两种都是完全Stop The World的。

Young GC

选定所有年轻代里的Region。通过控制年轻代的region个数,即年轻代内存大小,来控制young GC的时间开销。当Eden区无法申请新的内存时,开始Young GC

Mixed GC

选定所有年轻代里的Region,外加根据global concurrentt marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。
当old区Heap的对象占总Heap(整个堆,包括young)的比例超过InitiatingHeapOccupancyPercent,默认45之后,就会开始ConcurentMarking, 完成了Concurrent Marking后,G1会从Young GC切换到Mixed GC, 在Mixed GC中,G1可以增加若干个Old区域的Region到CSet中

Full GC

由上面的描述可知,Mixed GC不是full GC,它只能回收部分老年代的Region,如果mixed GC在并发阶段,无法跟上程序分配内存的速度,导致新的对象占满了所有空间,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。我们可以知道,G1是不提供单独full GC的,只提供了降级的单线程 old full gc,这次回收可能是很慢的。

  • CMS的Initial Marking和Remarking两个STW阶段在Heap区越来越大的情况下需要的时间越长,并且由于内存碎片,需要压缩的话也会造成较长停顿时间。所以需要一种高吞吐量的短暂停时间的收集器,而不管堆内存多大。
  • TLAB为线程本地分配缓冲区,它的目的为了使对象尽可能快的分配出来。如果对象在一个共享的空间中分配,我们需要采用一些同步机制来管理这些空间内的空闲空间指针。在Eden空间中,每一个线程都有一个固定的分区用于分配对象,即一个TLAB。分配对象时,线程之间不再需要进行任何的同步。
    对TLAB空间中无法分配的对象,JVM会尝试在Eden空间中进行分配。如果Eden空间无法容纳该对象,就只能在老年代中进行分配空间。
  • Snapshot-At-The-Beginning,由字面理解,是GC开始时活着的对象的一个快照。它是通过Root Tracing得到的,作用是维持并发GC的正确性
    一个对象a,如果在gc前,被一个还未标记完成的对象b引用了,gc过程中,并发执行时,如果应用程序将b对a的引用删除,同时,改为另一个已经标记完成的对象c来引用a,garbage collector可能会漏标a,把它当成一个已经没人其他对象引用的白对象,这时严重的错误,为防止这个问题,gc加入了写屏障,将旧的引用关系b <- a记录下来,这样a还是被b引用,就不会被回收了,但是也产生了浮动垃圾

日志

-XX:G1HeapRegionSize=n
2018-07-11T16:01:23.955+0800: 503932.789: [GC pause (G1 Evacuation Pause) (young)
Desired survivor size 62390272 bytes, new threshold 15 (max 15)

  • age 1: 1253680 bytes, 1253680 total
  • age 2: 54312 bytes, 1307992 total
  • age 3: 9440 bytes, 1317432 total
  • age 4: 73416 bytes, 1390848 total
  • age 5: 160368 bytes, 1551216 total
  • age 6: 8976 bytes, 1560192 total
  • age 7: 2336 bytes, 1562528 total
  • age 8: 13272 bytes, 1575800 total
  • age 9: 2984 bytes, 1578784 total
  • age 10: 64840 bytes, 1643624 total
  • age 11: 4928 bytes, 1648552 total
  • age 12: 48872 bytes, 1697424 total
  • age 13: 67664 bytes, 1765088 total
  • age 14: 3280 bytes, 1768368 total
  • age 15: 5632 bytes, 1774000 total
    , 0.0138689 secs]
    [Parallel Time: 10.0 ms, GC Workers: 4]
    [GC Worker Start (ms): Min: 503932789.7, Avg: 503932789.7, Max: 503932789.7, Diff: 0.1]
    [Ext Root Scanning (ms): Min: 1.5, Avg: 2.3, Max: 4.6, Diff: 3.1, Sum: 9.3] //扫描非堆root用的时间,如classloaders,jni引用,jvm system roots....
    [Update RS (ms): Min: 3.4, Avg: 5.7, Max: 6.4, Diff: 3.1, Sum: 22.7]
    [Processed Buffers: Min: 39, Avg: 49.0, Max: 55, Diff: 16, Sum: 196]
    [Scan RS (ms): Min: 0.4, Avg: 0.4, Max: 0.4, Diff: 0.1, Sum: 1.6]
    [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1] //从code调用来的root扫描时间 How long it took to scan the roots that came from the actual code: local vars, etc.
    [Object Copy (ms): Min: 1.4, Avg: 1.4, Max: 1.5, Diff: 0.1, Sum: 5.8] //把存活的对象copy出被收集的regions的时间
    [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 判断是否可以安全停止,没有更多的工作需要做的时间
    [GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.0, Sum: 0.2]
    [GC Worker Total (ms): Min: 9.9, Avg: 9.9, Max: 10.0, Diff: 0.1, Sum: 39.6] 工作线程一共工作了多次时间
    [GC Worker End (ms): Min: 503932799.6, Avg: 503932799.6, Max: 503932799.6, Diff: 0.0]
    [Code Root Fixup: 0.2 ms]
    [Code Root Migration: 0.1 ms]
    [Code Root Purge: 0.0 ms]
    [Clear CT: 0.3 ms]
    [Other: 3.3 ms]
    [Choose CSet: 0.0 ms]
    [Ref Proc: 0.9 ms] 执行非强引用:清理他们或者不需要清理
    [Ref Enq: 0.0 ms] 非强引用入队时间
    [Redirty Cards: 0.0 ms]
    [Free CSet: 1.7 ms]
    [Eden: 946.0M(946.0M)->0.0B(948.0M) Survivors: 4096.0K->3072.0K Heap: 1522.8M(2048.0M)->576.3M(2048.0M)]
    [Times: user=0.01 sys=0.03, real=0.02 secs]

https://liuzhengyang.github.io/2017/06/07/garbage-first-collector/

-XX:MetaspaceSize=2m -XX:MaxMetaspaceSize=256m -XX:+PrintTenuringDistribution -XX:+UseG1GC -XX:+DisableExplicitGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:ParallelGCThreads=4 -XX:+PrintHeapAtGC

-XX:MaxMetaspaceSize=256m -XX:+PrintTenuringDistribution -XX:+UseG1GC -XX:+DisableExplicitGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:ParallelGCThreads=4 -XX:+PrintHeapAtGC -Xloggc:/export/Logs/gclogs

2018-07-05T20:02:34.115+0800: 2.949: [GC pause (G1 Evacuation Pause) (young) 表示从eden copy到survior的停顿

-XX:MetaspaceSize=2m时,会有很多mixed gc;
[GC pause (G1 Evacuation Pause) (young)
[GC pause (G1 Evacuation Pause) (mixed)

-XX:MaxMetaspaceSize=16m 2018-07-16T11:43:08.780+0800: [GC pause (Metadata GC Threshold) (young) (initial-mark)

设置-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m 8m是一个不足的内存
2018-07-16T15:55:09.036+0800: [Full GC (Last ditch collection) 1718K->1718K(8192K), 0.0078003 secs]
[Eden: 0.0B(4096.0K)->0.0B(4096.0K) Survivors: 0.0B->0.0B Heap: 1718.9K(8192.0K)->1718.9K(8192.0K)], [Metaspace: 7989K->7989K(1056768K)]
Heap after GC invocations=1132 (full 809):
garbage-first heap total 8192K, used 1718K [0x0000000088e00000, 0x0000000088f00040, 0x0000000100000000)
region size 1024K, 0 young (0K), 0 survivors (0K)
Metaspace used 7989K, capacity 8134K, committed 8192K, reserved 1056768K
class space used 926K, capacity 990K, committed 1024K, reserved 1048576K
}
[Times: user=0.02 sys=0.00, real=0.01 secs]

  • metaspace回收:classloader无效时才会回收,相比于jdk7的permgen空间,metaspace不是一块连续的内存空间,full gc时,会根据classloader是否能回收,来确定相关联的class能否回收,即使相关联的class由于重复创建导致的错误,并未正常给用户使用,并且已满足class回收条件(此时错误class并未引用classloader,在创建class后进行的约束检查中会失败),仍然不会被回收掉。而permgen空间在full gc时,会和老年代一起做可用性分析,如果无引用,会被回收掉,这也是很多应用升级成jdk8产生metaspace oom的原因:一些框架会由于weakhashmap等缓存结构在某些情况下key被回收,导致判断class是否已存在时失败,重复创建class,但又无法通过jvm的约束校验,此时生成了重复的class,在jdk7时会在full gc时被回收,在jdk8中metaspace在full gc时,却没有回收

猜你喜欢

转载自www.cnblogs.com/windliu/p/9317277.html
G1