JVM调优
1. 常用工具
| 名称 | 作用 | 应用 |
|---|---|---|
| jps | 显示指定系统内所有的hotspot虚拟机进程 | |
| jstat | 收集Hotspot虚拟机各方面运行时数据 | |
| jinfo | 显示JVM配置信息 | |
| jmap | 生成指定进程的堆转储快照(heapdump) | |
| jhat | 分析heapdump快照 | |
| jstack | 虚拟机线程快照(stack trace):即jvm中每一条编程正在执行的方法堆栈集合。 | 定位线程出现长时间停顿的原因:线程间死锁、死循环、请求外部资源时间过长等 |
| Jconsole | ||
| VisualVM |
2.收集器选择及JVM参数配置
-
垃圾收集器选择 :Serial可以直接排除掉,现在最普通的服务器也有双核64位\8G内存,默认的收集器是PS Scavenge和PS MarkSweep。所以在并行(Parallel)和并发(Concurrent)两者之间选择。如果系统对峰值处理要求不高,而对一两秒的停顿可以接受,则使用(-XX:+UseParallelGC);如果应用对响应有更高的要求,停顿最好小于一秒,则使用(-XX:+UseConcMarkSweepGC)。
-
JVM配置方面,一般情况可以先用默认配置(基本的一些初始参数可以保证一般的应用跑的比较稳定了),在测试中根据系统运行状况(会话并发情况、会话时间等),结合gc日志、内存监控、使用的垃圾收集器等进行合理的调整,当老年代内存过小时可能引起频繁Full GC,当内存过大时Full GC时间会特别长。
那么JVM的配置比如新生代、老年代应该配置多大最合适呢?答案是不一定,调优就是找答案的过程,物理内存一定的情况下,新生代设置越大,老年代就越小,Full GC频率就越高,但Full GC时间越短;相反新生代设置越小,老年代就越大,Full GC频率就越低,但每次Full GC消耗的时间越大。建议如下:
- -Xms和-Xmx的值设置成相等,堆大小默认为-Xms指定的大小,默认空闲堆内存小于40%时,JVM会扩大堆到-Xmx指定的大小;空闲堆内存大于70%时,JVM会减小堆到-Xms指定的大小。如果在Full GC后满足不了内存需求会动态调整,这个阶段比较耗费资源。
- 新生代尽量设置大一些,让对象在新生代多存活一段时间,每次Minor GC 都要尽可能多的收集垃圾对象,防止或延迟对象进入老年代的机会,以减少应用程序发生Full GC的频率。 老年代如果使用CMS收集器,新生代可以不用太大,因为CMS的并行收集速度也很快,收集过程比较耗时的并发标记和并发清除阶段都可以与用户线程并发执行。
- 方法区大小的设置,1.6之前的需要考虑系统运行时动态增加的常量、静态变量等,1.7只要差不多能装下启动时和后期动态加载的类信息就行。
-
代码实现方面,性能出现问题比如程序等待、内存泄漏除了JVM配置可能存在问题,代码实现上也有很大关系:
- 避免创建过大的对象及数组:过大的对象或数组在新生代没有足够空间容纳时会直接进入老年代,如果是短命的大对象,会提前出发Full GC。
- 避免同时加载大量数据,如一次从数据库中取出大量数据,或者一次从Excel中读取大量记录,可以分批读取,用完尽快清空引用。
- 当集合中有对象的引用,这些对象使用完之后要尽快把集合中的引用清空,这些无用对象尽快回收避免进入老年代。
- 可以在合适的场景(如实现缓存)采用软引用、弱引用,比如用软引用来为ObjectA分配实例:SoftReference objectA=new SoftReference(); 在发生内存溢出前,会将objectA列入回收范围进行二次回收,如果这次回收还没有足够内存,才会抛出内存溢出的异常。 避免产生死循环,产生死循环后,循环体内可能重复产生大量实例,导致内存空间被迅速占满。
- 尽量避免长时间等待外部资源(数据库、网络、设备资源等)的情况,缩小对象的生命周期,避免进入老年代,如果不能及时返回结果可以适当采用异步处理的方式等。
3.JVM问题排查记录案例
JVM服务问题排查 https://blog.csdn.net/jacin1/article/details/44837595
次让人难以忘怀的排查频繁Full GC过程 http://caogen81.iteye.com/blog/1513345
线上FullGC频繁的排查 https://blog.csdn.net/wilsonpeng3/article/details/70064336/
【JVM】线上应用故障排查 https://www.cnblogs.com/Dhouse/p/7839810.html
一次JVM中FullGC问题排查过程 http://iamzhongyong.iteye.com/blog/1830265
JVM内存溢出导致的CPU过高问题排查案例 https://blog.csdn.net/nielinqi520/article/details/78455614
一个java内存泄漏的排查案例 https://blog.csdn.net/aasgis6u/article/details/54928744
4. 常用JVM参数参考:
| 参数 | 说明 | 实例 | | ———————– | ———————————————————— | ———————— | | -Xms | 初始堆大小,默认物理内存的1/64 | -Xms512M | | -Xmx | 最大堆大小,默认物理内存的1/4 | -Xms2G | | -Xmn | 新生代内存大小,官方推荐为整个堆的3/8 | -Xmn512M | | -Xss | 线程堆栈大小,jdk1.5及之后默认1M,之前默认256k | -Xss512k | | -XX:NewRatio=n | 设置新生代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4 | -XX:NewRatio=3 | | -XX:SurvivorRatio=n | 年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:8,表示Eden:Survivor=8:1:1,一个Survivor区占整个年轻代的1/8 | -XX:SurvivorRatio=8 | | -XX:PermSize=n | 永久代初始值,默认为物理内存的1/64 | -XX:PermSize=128M | | -XX:MaxPermSize=n | 永久代最大值,默认为物理内存的1/4 | -XX:MaxPermSize=256M | | -verbose:class | 在控制台打印类加载信息 | | | -verbose:gc | 在控制台打印垃圾回收日志 | | | -XX:+PrintGC | 打印GC日志,内容简单 | | | -XX:+PrintGCDetails | 打印GC日志,内容详细 | | | -XX:+PrintGCDateStamps | 在GC日志中添加时间戳 | | | -Xloggc:filename | 指定gc日志路径 | -Xloggc:/data/jvm/gc.log | | -XX:+UseSerialGC | 年轻代设置串行收集器Serial | | | -XX:+UseParallelGC | 年轻代设置并行收集器Parallel Scavenge | | | -XX:ParallelGCThreads=n | 设置Parallel Scavenge收集时使用的CPU数。并行收集线程数。 | -XX:ParallelGCThreads=4 | | -XX:MaxGCPauseMillis=n | 设置Parallel Scavenge回收的最大时间(毫秒) | -XX:MaxGCPauseMillis=100 | | -XX:GCTimeRatio=n | 设置Parallel Scavenge垃圾回收时间占程序运行时间的百分比。公式为1/(1+n) | -XX:GCTimeRatio=19 | | -XX:+UseParallelOldGC | 设置老年代为并行收集器ParallelOld收集器 | | | -XX:+UseConcMarkSweepGC | 设置老年代并发收集器CMS | | | -XX:+CMSIncrementalMode | 设置CMS收集器为增量模式,适用于单CPU情况。 | |