本部分涉及JVM内存结构及GC算法,java调优等知识。
JVM
- JVM内存结构划分(堆、栈、方法区、程序计数器等)
- 标记清除、标记整理及复制算法。
- 常用GC参数
- 常见垃圾回收器及优缺点
- 程序CPU 100%怎样排查?
- 常见的JVM工具有哪些
JVM优化
- 栈上分配 当开启逃逸分析后,JVM会把确定不会溢出的对象放到栈上分配,是JVM的一项优化技术,基本思想是对线程私有的对象将他们打散分配到栈上,分配到栈上的对象可以在函数结束时自行销毁,不需要垃圾回收器介入。
- TLAB (Thread Local Allocation Buffer) 线程本地分配缓存。为提高分配对象效率,会在Eden区为每个线程单独分配一块空间用于其对象分配。可避免线程分配过程中的同步操作。
注意
TLAB默认是开启的,一般不会很大,且当有大对象时不会再TLAB中分配。
垃圾收集器
串行收集器(Serial、Serial Old)
Serial串行收集器,单线程收集器,使用复制算法,在单核CPU上可以发挥很好。用于新生代(client默认收集器)。
Serial Old 串行收集器的老年代版本。使用标记清除算法。可以和多种新生代收集器工作。
使用-XX:+UseSerialGC
开启
优点:
- 简单高效,没有线程切换开销。
- 目前仍是client模式默认收集器。
serial收集器,在GC日志上为
新生代 [DefNew]
老年代 [Tenured]
ParNew(新生代多线程收集器:多线程,独占)
Serial收集器的多线程版本,其参数和Serial一样,使用算法,回收策略也与Serial一样。只是它使用了多线程进行垃圾回收。
可以总结为,除了使用多线程收集以外,与Serial没有什么很大区别,当在单CPU环境下,由于其线程切换的开销,其性能可能还差于Serial收集器。
使用 -XX:+UseParNewGC
ParNew收集器用于新生代,会使用串行收集器用于老年代(Serial Old)。
使用 -XX:+UseConcMarkSweepGC
,新生代使用ParNew,老年代使用CMS。
ParallelGC(重吞吐,新生代,多线程)
可以用于控制系统吞吐量,收集算法为复制算法。
使用-XX:MaxGCPauseMills
控制收集停顿时间;使用-XX:GCTimeRatio
控制吞吐量大小(即用于业务的时间与垃圾回收时间比值,如19,为99%)。
-XX:+UseParallelGC
使用ParallelGC收集器,并使用Serial Old
作为老年代收集器。
ParallelOldGC(重吞吐,老年代,多线程)
ParallelGC的老年代版本。收集算法为标记-整理算法。
参数-XX:+UseParallelOldGC
使用ParallelOldGC收集器,使用ParallelGC
作为老年代手机器。
CMS(Concurrent Mark Sweep并发标记清除收集器)
老年代收集器,使用标记-清除算法,关注系统停顿时间。多线程并行收集器。
CMS从整体来说,不像其他收集器垃圾收集时是独占式的,CMS的垃圾收集过程是并发的,和用户线程一起工作。由于CMS工作时没有暂停用户线程,内存可能在收集阶段增长,所以CMS会在内存到达一定阈值后触发回收,而不是等待内存即将满时才回收。使用-XX:CMSInitiatingOccupancyFraction=xx
控制回收阈值,xx为百分比。
不过需注意:
- 如果
CMSInitiatingOccupancyFraction
设置过高,当CMS在回收过程中剩余内存不足以容下此段时间新对象的内存,则会发生Concurrent Mode Failure
,会停掉整个用户线程,使用Serial Old
收集,这样反而会更慢。 - CMS使用标记-清除算法,会产生内存碎片。可设置参数
-XX:CMSFullGCsBeforeCompaction
,表示在进行多少次不压缩Full GC时进行压缩FullGC,压缩收集时,无法并发处理)
CMS开启参数为:-XX:+UseConcMarkSweepGC
。线程数默认为:(cpu数+3)/4。
并发收集线程数设置不当,CMS回收垃圾时,可能会导致性能问题。
CMS一共有以下几步:
- 初始标记, 标记GC root根节点直接关联对象,很快
- 并发标记, 并发标记根节点指向节点,并发(进行可达性分析过程)
- 重新标记,修正并发标记期间,因用户线程仍运行导致变动的对象
- 并发清除,并发清除对象
CMS目前可与新生代ParNew、Serial搭配。当CMS回收失败时,可用Serial Old进行回收。
G1(分区算法)
待续
收集日志
GC表示停顿类型,Full GC表示发生了STW,如果出现Full GC(System)
表示使用了显式GC(调用了System.gc())。
[GC (Allocation Failure) [PSYoungGen: 25600K->4077K(29696K)] 25600K->17349K(98304K), 0.0290039 secs] [Times: user=0.09 sys=0.01, real=0.03 secs]
[GC (Allocation Failure) [PSYoungGen: 23877K->4064K(29696K)] 37149K->33804K(98304K), 0.0423202 secs] [Times: user=0.12 sys=0.01, real=0.04 secs]
[GC (Allocation Failure) [PSYoungGen: 25708K->4080K(29696K)] 55448K->55246K(98304K), 0.0461796 secs] [Times: user=0.16 sys=0.02, real=0.04 secs]
[Full GC (Ergonomics) [PSYoungGen: 4080K->0K(29696K)] [ParOldGen: 51166K->50318K(68608K)] 55246K->50318K(98304K), [Metaspace: 3306K->3306K(1056768K)], 0.4442177 secs] [Times: user=0.58 sys=0.01, real=0.45 secs]
方括号数字表示 【GC前该内存区域大小->GC后该内存区域大小(该内存总大小)】
GC (Allocation Failure)
表示此次GC是分配失败造成的(内存分配不足)
Full GC 和 Young GC
Full GC指老年代GC,YoungGC为年轻代GC。一般Full GC比较慢,大概比YoungGC慢10倍。
常用GC参数
-Xloggc 保存GC日志
-XX:+PrintGCTimeStamps 打印GC日志时的时间
-XX:+PrintGC 打印GC日志
-XX:+PrintGCDetails 打印详细GC日志
排查策略
cpu消耗过高排查
- 使用jps查出运行java进程id(可省略)
- 使用top看上一步查到的进程id的cpu消耗(定位进程)
- 使用
top -Hp pid
命令查询该线程下所有进程情况,查看是否有异常cpu的线程(定位线程) - 使用
stack pid(进程id) > stack.log
打印线程堆栈到文件,查看文件。 - 将第3部获取到的进程id转为16进制,在线程堆栈里搜索,确定问题线程。
- 如果问题线程是自己代码起的线程,查看该代码是否有问题,若是
VM Thread
(垃圾回收线程),使用jstat -gcutil pid <interval> <count>
命令查看垃圾回收情况,关注YGC(青年代回收次数)和FGC(老年代回收次数)。 - 使用
jmap -dump:format=b,file=<堆栈名> pid
dump堆栈信息,然后用MAT分析堆栈信息。
参考 系统运行缓慢,CPU 100%,以及Full GC次数过多问题的排查思路