基本概念
JVM把内存区分为堆区(heap)、栈区(stack)和方法区(method)。由于本文主要讲解JVM调优,因此我们可以简单的理解为,JVM中的堆区中存放的是实际的对象,是需要被GC的。其他的都无需GC。
下图文JVM的内存模型
从图中我们可以看到,
- JVM实质上分为三大块,年轻代(YoungGen),年老代(Old Memory),及持久代(Perm,在Java8中被取消,不做深入介绍)。
- 垃圾回收GC,分为2种,一是Minor GC,可以可以称为YGC,即年轻代GC,当Eden区,还有一种称为Major GC,又称为FullGC。
- GC原理:我们可以看到年轻代包括Eden区(对象刚被new出来的时候,放到该区),S0和S1,是幸存者1区和幸存者2区,从名字可以看出,是当发生YGC,没有被任何其他对象所引用的对象将会从内存中被清除,还被其他对象引用的则放到幸存者区。当发生多次YGC,在S0、S1区多次没有被清楚的对象,则会被移到老年代区域。当老年代区域被占满的时候,则会发送FullGC。无论是YGC或是FullGC,都会导致stop-the-world,即整个程序停止一些事务的处理,只有GC进程允许以进行垃圾回收,因此如果垃圾回收时间较长,部分web或socket程序,当终端连接的时候会报connetTimeOut或readTimeOut异常,
- 从JVM调优的角度来看,我们应该尽量避免发生YGC或FullGC,或者使得YGC和FullGC的时间足够的短。
调优思路
确定是否有频繁Full GC现象:
- 如果Full GC频繁,那么考虑内存泄漏的情况
内存泄漏角度:
- 使用jps -l命令获取虚拟机的LVMID
- 使用jstat -gc lvmid命令获取虚拟机的执行状态,判断full GC次数
- 使用jmap -histo:live 分析当前堆中存活对象数量
- 如果还不能定位到关键信息,使用jmap =dump:format打印当前堆映象dump文件
- 使用MAT等工具分析dump文件,一般使用的参数是Histogram或者Dominator Tree,分析出各对象的内存占用率,并根据对象的引用情况找到泄漏点
2. 如果Full GC并不频繁,各个区域内存占用也很正常,那么考虑线程阻塞,死锁,死循环等情况
线程角度:
- 使用jps -l命令获取虚拟机的LVMID
- 使用jstack分析各个线程的堆栈内存使用情况,如果说系统慢,那么要特别关注Blocked,Waiting on condition,如果说系统的CPU消耗高,那么肯定是线程执行又死循环,那么此时要关注下Runable状态
- 如果还不能定位到关键信息,使用jmap -dump打印出当前堆映像dump文件
- 使用MAT等工具分析dump文件,一般使用的参数是Histogram或者Dominator Tree,分析出各对象的内存占用率,并根据对象的引用情况找到泄漏点
3. 如果都不是,考虑堆外存溢出,或者外部命令等情况
Runtime.getRuntime.exec();
常见参数
- Xms
- Xmx
- Xmn
- Xss
- -XX:SurvivorRatio
- -XX:NewRatio
- -XX:+PrintGCDetails
- -XX:ParalleGCThreads
- -XX:+HeapDumpOnOutOfMemoryError
- -XX:+UseG1GC
- -XX:MaxGCPauseMillis