JVM调优思路
监控工具:
- Arthas(线上检测工具)
- Visual VM(官方提供GUI工具)
- jmeter(压测工具)
- jps(查看当前运行的java进程信息)
- jstat(查看jvm统计信息)
- jmap(查看/导出内存占用情况,会触发fullgc)
- jinfo(实时查看和修改jvm配置参数)
- jhat(jdk自带的堆分析工具)
- jstack(打印jvm线程快照)
- jstatd(远程主机信息收集)
调优依据:
- 运行日志
- 异常堆栈
- GC日志
- 线程快照
- 堆转储快照
常见参数
- -XX:+PrintGC 开启gc日志
- -verbose:gc 输出gc日志信息,默认输出到标准输出
- 打印gc详细信息参数:-XX:+PrintGCDetails
- 设置gc日志位置 -Xloggc:目录位置
- 设置栈空间大小 -Xss(默认1M大小)
- -XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
- -XX:+PrintGCTimeStamps 输出GC的时间戳(以JVM启动到当期的总时长的时间戳形式)
- -XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
- -XX:+PrintReferenceGC 打印年轻代各个引用的数量以及时长
- -XX:SurvivorRatio=8(设置新生代伊甸园与幸存区大小比,默认是8,也就是8:1:1)
- -XX:MetaspaceSize 设置元空间大小
垃圾回收器参数
- -XX:+UseSerialGC
- 使用串行回收器进行回收,这个参数会使新生代和老年代都使用串行回收器,新生代使用复制算法,老年代使用标记-整理算法。Serial收集器是最基本、历史最悠久的收集器,它是一个单线程收集器。一旦回收器开始运行时,整个系统都要停止。Client模式下默认开启,其他模式默认关闭。
- -XX:+UseParNewGC
- Parallel是并行的意思,ParNew收集器是Serial收集器的多线程版本,使用这个参数后会在新生代进行并行回收,老年代仍旧使用串行回收。新生代S区任然使用复制算法。操作系统是多核CPU上效果明显,单核CPU建议使用串行回收器。打印GC详情时ParNew标识着使用了ParNewGC回收器。默认关闭。
- -XX:+UseParallelGC
- 代表新生代使用Parallel收集器,老年代使用串行收集器。Parallel Scavenge收集器在各个方面都很类似ParNew收集器,它的目的是达到一个可以控制的吞吐量。吞吐量为CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机运行100分钟,垃圾收集花费1分钟,那吞吐量就99%。Server模式默认开启,其他模式默认关闭。
- Parallel Scavenge提供了两个参数用于控制吞吐量,-XX:MaxGCPauseMillis参数用于设置最大停顿时间,它的参数运行值是一个大于0的毫秒数,收集器将尽力保证垃圾回收时间不超过设定值,系统运行的需要回收的垃圾总量是固定的,缩短停顿时间的同时会增大回收频度。-XX:GCTimeRatio参数用户控制垃圾回收时间占比,它运行的参数值是0-100的整数,如果参数设置为19,代表最大GC时间占总时间的5%(1/(1+19))。
- Parallel收集器还提供了自适应的调节策略-XX:UseAdaptiveSizePolicy,即JVM会根据实际运行情况动态调整新生代大小、新生代和s区比例、晋升老年代对象大小等细节参数。
- -XX:+UseParallelOldGC
- 新生代和老年代都使用并行收集器。打印出的GC会带PSYoungGen、ParOldGen关键字。
- -XX:+UseConcMarkSweepGC
- Concurrent Mark Sweep 并发标记清除,即使用CMS收集器。它是和应用程序线程一起执行,相对于Stop The World来说虚拟机停顿时间较少。停顿减少,吞吐量会降低。它使用的是 标记清除算法,运作过程为四个步骤,分别是 初始标记—并发标识—重新标记—并发清除。它是老年代的收集算法,新生代使用ParNew收集算法。默认关闭
- CMS收集器的缺点是对服务器CPU资源较为敏感,在并发标记时会降低吞吐量。它使用的标记清除算法也会产生大量空间碎片,空间碎片的存在会加大Full GC的频率,虽然老年代还有足够的内存,但是因为内存空间连续,不得不进行Full GC。
- -XX:+ UseCMSCompactAtFullCollection
- Full GC后,进行一次整理,整理过程是独占的,会引起停顿时间变长。仅在使用CMS收集器时生效。
- -XX:ParallelCMSThreads
- 设置并行GC时进行内存回收的线程数量
- -XX:PreternureSizeThreshold
- 直接晋升老年代的对象大小,设置了这个参数后,大于这个参数的对象直接在老年代进行分配。
- -XX:MaxTenuringThreshold
- 晋升老年代的对象年龄,对象在每一次Minor GC后年龄增加一岁,超过这个值后进入到老年代。默认值为15。
- -XX:NativeMemoryTracking=detail
- 使用命令jcmd pid VM.native_memory detail,配合查看JVM相关情况
调优思路:
- 调整堆的大小提供服务的吞吐量(-Xms / Xmx设置堆空间起始大小和最大大小)
- 堆空间默认最大值计算公式:若物理内存如果小于192MB则heap最大值为物理内存的一半,若物理内存大于等于1G则heap最大值为物理内存1/4
- 堆空间默认最小值计算公式:最少不小于8M,物理内存大于等于1G则heap最小值为物理内存1/64
- 将-Xms 和 -Xmx设置为一样的值,让java垃圾回收机制清理完堆区后不需要重新分隔计算堆区大小,提高性能
- JIT即时编译器优化
- 栈上分配:如果对象未发生逃逸,则栈上分配(需打开逃逸分析,jdk6以后hotspot默认开启,+XX:+DoEscapeAnalysis显示开启逃逸分析, +XX:PrintEscapeAnalysis查看逃逸分析筛选结果)
- 标量替换:对象栈上分配时打撒分配,-XX:+EliminateAllocations(开启标量替换,默认开启)
- 同步消除:一个方法内创建的对象加锁,此时线程安全则会把锁消除
- 合理配置堆内存
- 一次fullgc以后统计当前老年代的内存占用情况,记录该值的平均值为x
- 设置堆内存大小 -Xms = -Xmx = 3x - 4x
- 方法区大小 -XX:PermSize = -XX:MaxPermSize = 1.2x - 1.5x
- Young Generation新生代大小 -Xmn = 1x - 1.5x
- Old Generation老年代大小 2x - 3x
- CPU突然占用很高
- ps aux | grep java查看当前java进程使用情况
- top -Hp 进程号 查看进程的各线程对cpu等硬件占用情况
- jstack 进程号 | grep -A20 线程号 获取线程内执行详细信息
- G1垃圾回收器并发执行线程数对性能的影响
- -XX:+UseG1GC 手动指定使用G1收集器执行内存回收任务。
- -XX:G1HeapRegionSize 设置每个Region的大小。值是2的幂,范围是1MB到32MB之间,目标是根据最小的Java堆大小划分出约2048个区域。默认是堆内存的1/2000。
- -XX:MaxGCPauseMillis 设置期望达到的最大GC停顿时间指标(JVM会尽力实现,但不保证达到)。默认值是200ms
- -XX:ParallelGCThread 设置STW时GC线程数的值。最多设置为8
- -XX:ConcGCThreads 设置并发标记的线程数。将n设置为并行垃圾回收线程数(ParallelGCThreads)的1/4左右。
- -XX:InitiatingHeapOccupancyPercent 设置触发并发GC周期的Java堆占用率阈值。超过此值,就触发GC。默认值是45。
- 调整垃圾回收器提高服务的吞吐量
- 在单核服务器下使用Serial串行收集器,在多核下使用并发收集器(parallel GC;ParNew + CMS;G1吞吐量)
- 高并发业务下分析调优思路
- 分析业务的并发量和产生的对象的占用内存大小(假设一个业务下一次请求产生多个对象,其中对象a一个1Kb,对象b一个2Kb…,每秒钟100个请求,这样分析出一秒钟产生的对象占用的内存,假设此时为2M/秒,这些对象一般分配在Eden区)
- 此时模拟分配堆内存大小,判断MinorGC发生频率
- 使用压测工具求每次MinorGC的时间,可以得出吞吐量和响应时间(在用户看来发生的延迟与响应时间有关)
- 当并发量增大时判断每秒增加的请求量从而判断每秒增加的对象内存占用,继续刚才的计算分析,合理分配堆内存大小或者水平扩容机器数量
- 结合前面的知识来看,我们还可以尽量让对象不发生逃逸,从而让它栈上分配,这样它会和该方法栈帧一起释放