1、什么是垃圾回收
Garbage Collector简称GC,垃圾回收线程是守护线程,一直在运行,但是根据条件触发FGC
垃圾是指在 运行程序中没有任何指针指向的对象, 这个对象就是需要被回收的垃圾。
如果不及时对内存中的垃圾进行清理,那么这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出。
2、垃圾回收器的种类
jdk1.8默认为Parallel Scavenge和Parallel Old
jdk1.9之后默认为G1
1、Serial:单线程的垃圾回收器,采用复制算法
2、Serial Old:跟serial一起使用,用于回收old代,采用标记整理算法
3、ParNew:多线程收集器,ParNew说白了和Parallel Scavenge一样的,区别组了做了增强,以便能让它和CMS配合使用。采用标记-整理算法
4、CMS:CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,采用标记—清除算法
CMS运行分为4个过程:
- 初始标记(CMS initial mark)
- 并发标记(CMS concurrent mark)
- 重新标记(CMS remark)
- 并发清除(CMS concurrent sweep)
1、初始标记(触发STW):初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快。 2、并发标记(不触发STW):并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程, 这个过程耗时较长但是不需要停顿用户线程, 可以与垃圾收集线程一起并发运行。因为用户程序继续运行,可能会有导致已经标记过的对象状态发生改变。所以要进行一次重新标记。 3、重新标记(触发STW):重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,就是并发标记过程中产生的新垃圾,进行标记,或者到重新标记这一阶段,又要用到并发标记已经标记的垃圾,取消垃圾标记。 这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。 4、并发清理(不触发STW) :开启用户线程,同时GC线程开始对未标记的区域做清扫。这个阶段如果有新增对象会被标记为黑色不做任何处理,等下次重新触发在进行处理。 |
5、Parallel Scavenge:并行垃圾回收,处理新生代垃圾,采用复制算法
6、Parallel Old:和Parallel Scavenge一起使用,处理Old的垃圾,采用标记整理算法
7、G1:多线程收集器,采用标记-整理算法
3、如何找到垃圾
使用Root Searching根可达
4、回收算法
新生代大量死去,少量存活,采用复制算法
老年代存活率高,回收较少,采用MC或MS
1、Mark-Sweep(标记清除)
特点是碎片化
2、Copying(拷贝)
每次只允许用内存的一半,将有用的数据放入另一半,然后将这一半整体清楚
优点是效率高
缺点是内存浪费
3、Mark-Compact(标记压缩)
将无用的数据全部清掉,然后将有用的数据全部放到前面
特点是效率比copy略低
5、垃圾回收器的演进
6、堆内存的逻辑分区
将内存分为新生代new和老年代old,默认比例为1:2,这个比例可以通过调优去修改
优先把新创建的对象放到new
把一些长期有用的放入old,
1、ps+po age=15
2、cms age=6
3、G1 不分代
15或者6次没有被回收就会转到old
eden(伊甸园)和survivor(幸存者)比例是8:1:1,可以通过jvm调优去更改
7、何时进入old老年代
8、三色标记算法
第一种情况A->B消失
当引用断开后会变成浮动垃圾(floating garbage)
第二种情况:B->D消失,A->D增加
可能会造成D漏标,可以通过写屏障,在A.x = D的时候,如果A是黑色,D是白色,标A为灰
就是只要黑色指向白色,就把黑色标为灰色,这种方案为CMS方案:Incremental Update
但是这种方法有个隐秘的bug
并发标记,产生漏标
举例:假如垃圾回收线程标记完A的属性1之后,正在标记A的属性2,在这个时候业务线程把A的属性1又指向了D,那么当垃圾回收线程标记完属性2之后,就会把A标记为黑色,这样D就会被漏掉
CMS的解决这个bug的方法是:在remark阶段,必须从头扫描一遍,在这个扫描的阶段必须STW(stop the world),在业务很大的时候STW时间会很长
CMS的两大弊端:1、floating garbage
2、Remark阶段必须STW
9、G1的解决方案
额外新增加一个记录
当任何灰色对象指向白色 对象的引用消失的时候,会把这根引用记录到堆栈里,当垃圾回收线程再回来时需要看一下有没有新的记录,如果有记录,就去扫描一下被取消指向的白色对象,看看有没有新的引用指向它,如果没有新的引用,那么这个白色对象就是垃圾
10、G1详解
10.1 G1的内存模型
G1 收集器不采用传统的新生代和老年代物理隔离的布局方式,仅在逻辑上划分新生代和老年代,将整个堆内存划分为2048个大小相等的独立内存块Region,每个Region是逻辑连续的一段内存,具体大小根据堆的实际大小而定,整体被控制在 1M - 32M 之间,且为2的N次幂(1M、2M、4M、8M、16M和32M),并使用不同的Region来表示新生代和老年代,G1不再要求相同类型的 Region 在物理内存上相邻,而是通过Region的动态分配方式实现逻辑上的连续。
G1收集器通过跟踪Region中的垃圾堆积情况,每次根据设置的垃圾回收时间,回收优先级最高的区域,避免整个新生代或整个老年代的垃圾回收,使得stop the world的时间更短、更可控,同时在有限的时间内可以获得最高的回收效率。
通过区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。
G1的内存图如下,G1是把内存分为许多个Region(区域)
10.2 分区 Region介绍
G1 垃圾收集器将堆内存划分为若干个 Region,每个 Region 分区只能是一种角色,Eden区、S区、老年代区的其中一个,空白区域代表的是未分配的内存,最后还有个特殊的区域H(Humongous),专门用于存放巨型对象,如果一个对象的大小超过Region容量的50%以上,G1 就认为这是个巨型对象。在其他垃圾收集器中,这些巨型对象默认会被分配在老年代,但如果它是一个短期存活的巨型对象,放入老年代就会对垃圾收集器造成负面影响,触发老年代频繁GC。
为了解决这个问题,G1划分了一个H区专门存放巨型对象,如果一个H区装不下巨型对象,那么G1会寻找连续的H分区来存储,如果寻找不到连续的H区的话,就不得不启动 Full GC 了。
10.3 G1的优点
特点:
并发收集效率高
压缩空闲空间不会演唱GC的暂停时间
更易预测的GC暂停时间
适用不需要实现很高的吞吐量的场景
10.4 G1的一些参数
1、每个Region的大小
headpRegion.cpp
取值:1 2 4 8 16 32
手工指定:XX:G1HeadpRegionSize
11、如何进行jvm调优
11.1 什么是调优
1、根据需求进行JVM规划和预调优
2、优化运行JVM运行环境(慢、卡顿)
3、解决JVM运行过程中出现的各种问题(OOM)
11.2 调优从规划开始
调优,从业务场景开始,没有业务场景的调优都是耍流氓
无监控(压力测试,能看到结果),不调优
步骤:
1、熟悉业务场景(没有最好的垃圾回收器,只有最适合的)
- 响应时间、停顿时间[ CMS G1 ZGC ](需要给用户作响应)
- 吞吐量 = 用户时间/(用户时间+GC时间)[PS]
2、选择回收器组合
3、计算内存需求
4、选定CPU(越高越好)
5、设定年代大小、升级年龄
6、设定日志参数
1、-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=20M
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-XX:+PrintGCCause
2、或者每天生成一个日志文件
7、观察日志情况
11.3 常用工具
1、linux自带的命令、JDK自带命令
linux:jps、top、jstack
2、arthas:阿里的工具
3、使用图形界面远程连接
JVM
JConsole
4、离线工具
MAT
12、GC常用的参数
12.1 GC常用参数
- -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间
- -XX:+UseTLAB 使用TLAB,默认打开
- -XX:+PrintTLAB 打印TLAB的使用情况
- -XX:TLABSize 设置TLAB大小
- -XX:+DisableExplictGC System.gc()不管用 ,FGC
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintHeapAtGC
- -XX:+PrintGCTimeStamps
- -XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间
- -XX:+PrintGCApplicationStoppedTime (低) 打印暂停时长
- -XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用
- -verbose:class 类加载详细过程
- -XX:+PrintVMOptions
- -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial 必须会用
- -Xloggc:opt/log/gc.log
- -XX:MaxTenuringThreshold 升代年龄,最大值15
- 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 … 这些不建议设置
12.2 Parallel常用参数
- -XX:SurvivorRatio
- -XX:PreTenureSizeThreshold 大对象到底多大
- -XX:MaxTenuringThreshold
- -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
- -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例
12.3 CMS常用参数
- -XX:+UseConcMarkSweepGC
- -XX:ParallelCMSThreads CMS线程数量
- -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
- -XX:+UseCMSCompactAtFullCollection 在FGC时进行压缩
- -XX:CMSFullGCsBeforeCompaction 多少次FGC之后进行压缩
- -XX:+CMSClassUnloadingEnabled
- -XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm回收
- GCTimeRatio 设置GC时间占用程序运行时间的百分比
- -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代
12.4 G1常用参数
- -XX:+UseG1GC
- -XX:MaxGCPauseMillis 建议值,G1会尝试调整Young区的块数来达到这个值
- -XX:GCPauseIntervalMillis ?GC的间隔时间
- -XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。 随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 ZGC做了改进(动态区块大小)
- G1NewSizePercent 新生代最小比例,默认为5%
- G1MaxNewSizePercent 新生代最大比例,默认为60%
- GCTimeRatio GC时间建议比例,G1会根据这个值调整堆空间
- ConcGCThreads 线程数量
- InitiatingHeapOccupancyPercent 启动G1的堆空间占用比例