Java虚拟机在运行的过程中,虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行java虚拟机,就可以在系统运行时打印相关日志,用于分析实际问题。我们进行虚拟机参数配置,其实主要就是围绕着堆、栈进行配置。
1.虚拟机参数-Heap内存
• -XX:+PrintGC 使用这个参数,虚拟机启动后,只要遇到GC就会打印日志。
• -XX:+UseSerialGC 配置串行回收器
• -XX:+PrintGCDetails 可以查看详细信息,包括各个区的情况
• -Xms:设置java程序启动时初始堆大小
• -Xmx:设置java程序能获得的最大堆大小
• -XX:+PrintCommandLineFlags: 可以将隐式或者显示传给虚拟机的参数输出
【注意:在实际工作中,直接将初始的堆大小与最大堆大小设置相等,这样的好处是可以减少程序运行时的垃圾回收次数,从而提高性能。】
非常多的开源框架都采用Lock Memory的机制,将初始化堆大小与最大堆大小固定设置为一致,并独占固定内存大小,强制占用固定内存,不被其他应用使用。
2.Heap内存结构
Young:年轻代中的数据频繁的创建销毁,所以采用复制算法;
Eden区:所有new出来的对象都存在此区域;
from(S0)/to(S1):YGC执行会将Eden区中一直引用(存活)的对象放入S0或S1其中一个区域;发生GC的时候S0或S1同时只有一个区域启用(复制算法);
复制算法:Survivor区,一块叫From,一块叫To,对象存在Eden和From块;当进行GC时,Eden存活的对象全移到To块,而From中,存活的对象按年龄值确定去向,当达到一定值(年龄阈值,通过-XX:MaxTenuringThreshold可设置,默认15次)的对象会移到年老代中,没有达到值的复制到To区,经过GC后,Eden和From被清空。之后,From和To交换角色,新的From即为原来的To块,新的To块即为原来的From块,且新的To块中对象年龄加1。
Old:Young区中默认经历过15次(可设置)GC的对象会进入Old区,相对而言Old区中的数据非常庞大,那么Old区执行GC会非常消耗性能,其采用标记压缩算法;
标记压缩算法:标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象压缩到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题;优缺点同标记清除算法,解决了标记清除算法的碎片化的问题,标记压缩算法多了一步,对象移动内存位置的步骤,其效率也有一定的影响。
3.内存溢出
通过微缩方法来模拟内存溢出,并导出文件排查;
step1:配置参数运行应用
-Xms5m (初始化化5M)
-Xmx20m (最大20M)
-XX:+PrintGCDetails (打印GC详细信息)
-XX:+UseSerialGC (应用串行化垃圾回收器)
-XX:+PrintCommandLineFlags(打印命令行参数)
/**
* 01 堆内存分配查看
*/
public class Test_001 {
public static void main(String[] args) {
//-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
//查看GC信息
System.out.println("max memory:" + Runtime.getRuntime().maxMemory());
System.out.println("free memory:" + Runtime.getRuntime().freeMemory());
System.out.println("total memory:" + Runtime.getRuntime().totalMemory());
byte[] b1 = new byte[1*1024*1024];
System.out.println("分配了1M");
System.out.println("max memory:" + Runtime.getRuntime().maxMemory());
System.out.println("free memory:" + Runtime.getRuntime().freeMemory());
System.out.println("total memory:" + Runtime.getRuntime().totalMemory());
byte[] b2 = new byte[4*1024*1024];
System.out.println("分配了4M");
System.out.println("max memory:" + Runtime.getRuntime().maxMemory());
System.out.println("free memory:" + Runtime.getRuntime().freeMemory());
System.out.println("total memory:" + Runtime.getRuntime().totalMemory());
}
}
输出:
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
max memory:20316160
free memory:5200384
total memory:6094848
[GC (Allocation Failure) [DefNew: 873K->192K(1856K), 0.0009620 secs] 873K->576K(5952K), 0.0009875 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配了1M
max memory:20316160
free memory:4423312
total memory:6094848
[GC (Allocation Failure) [DefNew: 1248K->0K(1856K), 0.0215669 secs][Tenured: 1600K->1600K(4096K), 0.0015010 secs] 1632K->1600K(5952K), [Metaspace: 2635K->2635K(1056768K)], 0.0344427 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
分配了4M
max memory:20316160
free memory:4491456
total memory:10358784
Heap
def new generation total 1920K, used 68K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
eden space 1728K, 3% used [0x00000000fec00000, 0x00000000fec11158, 0x00000000fedb0000)
from space 192K, 0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
to space 192K, 0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
tenured generation total 8196K, used 5696K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
the space 8196K, 69% used [0x00000000ff2a0000, 0x00000000ff830198, 0x00000000ff830200, 0x00000000ffaa1000)
Metaspace used 2641K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 280K, capacity 386K, committed 512K, reserved 1048576K
step2模拟内存溢出,并导出文件;(查看文件需要安装Memory Analyzer)
安装Memory Analyzer
/**
* 02 堆内存溢出
*/
public class Test_002 {
static List<byte[]> list = new ArrayList<>();
public static void main(String[] args) {
/**
* yum install lrzsz
* 1 设置初始化内存与最大内存一致,并使用串行化垃圾收集器
* -Xms20m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
*
* //Full GC
* //Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
*/
/**
* 2 内存溢出时导出dump快照文件:
* -Xms20m -Xmx20m
* -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/
*
*/
/**
* 3 使用Memory Analyser dump内存进行分析
* <1> Leak Suspects 泄露疑点 overview
* <2> Histogram 图表查看具体内存数量
* <3> Merge GC root 查看对象引用个数
* <4> entrie Heap 查看对象引用百分比与大小
*/
/**
* 4 使用jmap实时查看内存情况/导出dump文件:
* <1> jmap -heap 5236
* <2> jmap -dump:format=b,file=app.hprof 5236
*/
/**
* 5 设置新生代与老年代内存比例:
* <1> 这个参数对系统性能以及GC行为有很大的影响, 新生代大小一般会设置整个堆空间的1/3 ~ 1/4左右。
*
* -Xms40m -Xmx40m -Xmn10m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
*
* <2> -XX:SurvivorRatio:用来设置新生代中eden空间和from/to空间的比例。
*
* 含义:-XX:SurvivorRatio=eden/from=eden/to
*
* 基本策略:尽可能将对象预留在新生代,减少老年代的GC次数
*
* <3> -XX:NewRatio=老年代/新生代 :用来设置新生代和老年代的比例
*
* <4> Q:如果仅仅给出 eden区40M,那么比较合理的整个堆设置为多少?
* A: eden/8 = 5M = S0 = S1 新生代 = 50M * 3 OR * 4= 150M ~ 200M
*
*/
while(true) {
byte[] b = new byte[1*1024];
list.add(b);
}
}
}
输出:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/ -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC
[GC (Allocation Failure) [DefNew: 5504K->639K(6144K), 0.0028424 secs] 5504K->5094K(19840K), 0.0028726 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 6143K->639K(6144K), 0.0028299 secs] 10598K->10530K(19840K), 0.0028536 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 6143K->6143K(6144K), 0.0000090 secs][Tenured: 9891K->13695K(13696K), 0.0052929 secs] 16034K->15952K(19840K), [Metaspace: 2634K->2634K(1056768K)], 0.0053436 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [Tenured: 13695K->13695K(13696K), 0.0036933 secs] 19839K->19820K(19840K), [Metaspace: 2634K->2634K(1056768K)], 0.0037170 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 13695K->13695K(13696K), 0.0028377 secs] 19838K->19838K(19840K), [Metaspace: 2634K->2634K(1056768K)], 0.0028741 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [Tenured: 13695K->13695K(13696K), 0.0040750 secs] 19838K->19824K(19840K), [Metaspace: 2634K->2634K(1056768K)], 0.0040955 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 13695K->13695K(13696K), 0.0030613 secs] 19838K->19838K(19840K), [Metaspace: 2634K->2634K(1056768K)], 0.0030813 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [Tenured: 13695K->13695K(13696K), 0.0026850 secs] 19838K->19838K(19840K), [Metaspace: 2634K->2634K(1056768K)], 0.0027027 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to d:/\java_pid23584.hprof ...
Heap dump file created [21140604 bytes in 0.015 secs]
Exception in thread "main" [Full GC (Allocation Failure) [Tenured: 13695K->13695K(13696K), 0.0030278 secs] 19839K->19839K(19840K), [Metaspace: 2659K->2659K(1056768K)], 0.0030517 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
java.lang.OutOfMemoryError: Java heap space
[Full GC (Allocation Failure) [Tenured: 13695K->13695K(13696K), 0.0043163 secs] 19839K->19838K(19840K), [Metaspace: 2659K->2659K(1056768K)], 0.0043393 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
at com.bfxy.jvm.Test_002.main(Test_002.java:65)
[Full GC (Allocation Failure) [Tenured: 13695K->13695K(13696K), 0.0036342 secs] 19839K->19838K(19840K), [Metaspace: 2660K->2660K(1056768K)], 0.0036576 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation total 6144K, used 6143K [0x00000000fec00000, 0x00000000ff2a0000, 0x00000000ff2a0000)
eden space 5504K, 100% used [0x00000000fec00000, 0x00000000ff160000, 0x00000000ff160000)
from space 640K, 99% used [0x00000000ff160000, 0x00000000ff1ffe50, 0x00000000ff200000)
to space 640K, 0% used [0x00000000ff200000, 0x00000000ff200000, 0x00000000ff2a0000)
tenured generation total 13696K, used 13695K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000)
the space 13696K, 99% used [0x00000000ff2a0000, 0x00000000fffffe90, 0x0000000100000000, 0x0000000100000000)
Metaspace used 2666K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 283K, capacity 386K, committed 512K, reserved 1048576K
将D:/java_pid23854.hprof拷贝到工程中查看:
查看直方图:
【jvm中只有强引用才会导致内存溢出,弱引用、软引用经过GC都会被释放,所有定位只排查强引用】
4.定时巡检
实际工作过程中,针对生产、预节点、测试、组单元等环境,不会等到内存溢出之后,在去分析内存溢出的原因和问题,都是定时的巡检,在不应组件的情况下,就需要人工使用jmap命令进行导出了。(定时巡检是RD的日常值例工作内容)
step1:查heap实时情况 jmap -heap pid
step2:通过jmap导出应用情况 jmap -dump:format=b,file=app.hprof pid
step3:从虚拟机导出(通过 lrzsz)并查看:
5.合理划分新生代老年代
• Java虚拟机,新生代的配置
• -Xmn:可以设置新生代的大小,设置一个比较大的新生代会减少老年代的大小,这个参数对系统性能以及GC行为有很大的影响,新生代大小一般会设置整个堆空间的1/3到1/4左右。
• -XX:SurvivorRatio:用来设置新生代中eden空间和from/to空间的比例。
• 含义:-XX:SurvivorRatio=eden/from=eden/to
【基本策略:尽可能将对象预留在新生代,减少老年代的GC次数】