一、程序添加JVM参数的方式
-
命令行添加:java [JVM参数] -jar [jar包名]
- java -XX:+PrintGC -jar jvm-project.jar
- java -XX:+PrintGC -jar jvm-project.jar
-
idea添加(后续例子用此方式,idea版本2021.2.2、jdk1.8.0_152)
- Run -> Edit Configurations -> modify options -> java -> Add VM options
- 打印效果
- Run -> Edit Configurations -> modify options -> java -> Add VM options
一、GC跟踪参数
-
开启GC日志,打印简要GC信息
- -verbose:gc(-XX:PrintGC):程序执行GC的时候打印GC信息
- GC:执行一次Young GC(YGC、MinorGC);
- Allocation Failure:出发GC的原因(分配内存失败);
- 64289K->1008K:年轻代通过一次YGC,内存占用从64289K降为1008k;
- 247296K:整堆大小为247296K,此处整堆大小是变化的是由于idea默认设置的堆最大值和最小值不一致(见最大/最小堆参数设置);
- 0.0013379 secs:此次YGC耗时;
- -verbose:gc(-XX:PrintGC):程序执行GC的时候打印GC信息
-
打印GC详细信息
- -XX:+PrintGCDetails:程序结束时打印堆信息
- GC/Full GC:GC类型;
- Allocation Failure、Ergonomics:触发GC的原因;Ergonomics是由于虚拟通过算法估算出GC后年轻代所需空间大于当前剩余空间,所以提前进行一次Full GC;
- PSYoungGen、ParOldGen、Metaspace:年轻代、老年代、元空间(jdk1.8之前为永久代);
- 1054K->0K:回收前所占空间->回收后所占空间;
- (2560K):当前区域(PSYoungGen、ParOldGen、Metaspace)可用总大小(年轻代to survivor区永远为空,不可用);
- 0.0002245 secs:当前GC所花时间;
- Times:用户时间、系统时间、实际时间;
- heap:堆信息
- total:当前区域总大小;
- used :当前区域使用大小;
- [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000):当前内存所在地址,最低边界,当前所在边界(使用到哪),最高边界;
- eden:年轻代eden区;
- form:年轻代form surivor区;
- to:年轻代to surivor区;
- -XX:+PrintGCDetails:程序结束时打印堆信息
-
以文件形式输出GC日志
- -Xloggc:C:\temp\gc.log(路径自行调整)
- -Xloggc:C:\temp\gc.log(路径自行调整)
-
每次GC之后打印堆信息
- -XX:+PrintHeapAtGC
- Heap before GC :GC前堆信息;
- invocations=21 (full 7):调用GC次数(其中Full GC次数);
-
跟踪类加载
- -XX:+TraceClassLoading;
- Object为所有类的基类,最先加载;
二、堆分配参数
-
最大堆内存、最小堆内存
- -Xmx20m -Xms12m
- 如下代码中JVMObject中每次分配1M空间,JVMTest通过main方法实例化JVMObject,然后分别打印最大内存、空闲内存、总内存,GC日志如下图;
- 内存总大小分别在第三次GC和第五次GC之后进行了提升,这是由于分配的初始内存为12m,进行三次GC之后,空间不足,JVM对堆内存进行了提升;
- JVM会将堆内存尽量维持在最小值;
/**
* @author xiaomu
* @title: JVMObject
* @description: JVM测试实体对象
* @date 2022/8/1110:28
*/
public class JVMObject {
private byte[] a = null;
public JVMObject() {
this.a = new byte[1 * 1024 * 1024];
}
}
/**
* @author xiaomu
* @title: JVMTest
* @description: JVM测试类
* @date 2022/8/1014:15
*/
public class JVMTest {
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
JVMObject jvmObject = new JVMObject();
}
// 获取最大内存
System.out.print("Xmx:");
System.out.println(Runtime.getRuntime().maxMemory()/1024/1024 + "M");
// 获取空闲内存
System.out.print("free:");
System.out.println(Runtime.getRuntime().freeMemory()/1024/1024 + "M");
// 获取最小内存
System.out.print("tot:");
System.out.println(Runtime.getRuntime().totalMemory()/1024/1024 + "M");
}
}
-
分配新生代大小
- -Xmn1m:给新生代分配5m内存;
- 4608(新生代可用大小)+ 512(to survivor) = 5120 = 5m;
- 如下代码中,每次分配3m内存,在main方法中循环分配两次;
- jvm参数:-Xmx20m -Xms20m -Xmn5m -XX:+PrintGCDetails;
- 未进行GC且from区占用0老年代占用40%:第一次分配3m直接存入Eden区,第二次分配3m时,年轻代内存不足,将大对象直接放入了老年代;(将 -Xmn设置改小时比如-Xmn1m出现了GC现象且年轻代占比升高,from区保持在90%以上,说明对象未直接进入老年代,原因未知,网上资料也未找到,待查)
/**
* @author xiaomu
* @title: JVMObject
* @description: JVM测试实体对象
* @date 2022/8/1110:28
*/
public class JVMObject {
public JVMObject() {
byte[] a = new byte[3 * 1024 * 1024];
}
}
/**
* @author xiaomu
* @title: JVMTest
* @description: JVM测试类
* @date 2022/8/1014:15
*/
public class JVMTest {
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
JVMObject jvmObject = new JVMObject();
}
}
}
-
设置老年代与新生代比值
- -XX:NewRatio=3:老年代 : 新生代 = 3 : 1(默认 老年代 :新生代 = 2 :1);
- PSYoungGen:2560 + 512 = 3072 = 3m;ParOldGen:9216 = 9m;
- -XX:MaxTenuringThreshold=15:设置年轻代进入老年代阈值(15);
-
设置两个survivor和Eden的比例
- -XX:SurvivorRatio=8(默认值):两个survivor与Eden比为2:8,即一个Eden占总新生代的8/10;
- 测试发现,垃圾回收器会优先保证单个survivor内存 >= 512k;如下图:新生代大小设为16m = 16384k,SurvivorRatio=14,GC日志中,Eden区占比为14336 / 16384 = 0.8750 = 14 / 16;改变SurvivorRatio=15,此时Eden区占比为15360 / 16384 = 0.9375 > 15 / 17 = 0.8824,继续增大SurvivorRatio的值之后,GC日志不再变化始终将survivor大小维持在512k;
- jdk1.8默认垃圾回收器为UseParallelGC,网上说默认开启了自适应大小策略(-XX:+UseAdaptiveSizePolicy),在默认参数中并没有发现开启该参数,且实际测试中发现是否加该参数并不影响结果;
- 测试发现from survivor和to survivor区并不总是相等;原因在于Minor GC之后会交换from和to区,此时会根据空间使用情况和GC周期时间开销计算出Eden和servivor的期望大小To_Size = MaxHeapSize - PSOldGen_Size - Eden_Size - From_Size;
/**
* @author xiaomu
* @title: JVMObject
* @description: JVM测试实体对象
* @date 2022/8/1110:28
*/
public class JVMObject {
public JVMObject() {
byte[] a = new byte[1 * 1024 * 1024];
}
}
/**
* @author xiaomu
* @title: JVMTest
* @description: JVM测试类
* @date 2022/8/1014:15
*/
public class JVMTest {
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
JVMObject jvmObject = new JVMObject();
}
}
}
-
OOM时导出堆文件,并指定路径
- -XX:+HeapDumpOnOutOfMemoryError:OOM时导出堆文件;
- -XX:HeadDumpPath=c:\temp\oom.dump;
- oom的复现困难,所以一般设置程序发生oom时将堆文件导出;
/**
* @author xiaomu
* @title: JVMTest
* @description: JVM测试类
* @date 2022/8/1014:15
*/
public class JVMTest {
public static void main(String[] args) {
Vector v=new Vector();
for (int i = 0; i < 20; i++) {
v.add(new byte[1 * 1024 * 1024]);
}
}
}
三、元空间(永久区)分配参数
- jdk1.8以前,元空间称为永久区,其内存在虚拟中,1.8之后改为元空间,内存改到本地,默认初始21807104,最大值不设置,直到将本地内存占满;(查看所有jvm默认参数命令:java -XX:+PrintFlagsFinal,查看经过覆盖的:java -XX:+PrintCommandLineFlags -version)
- -XX:MetaspaceSize=10m:设置元空间初始10m;
- -XX:MaxMetaspaceSize=15m:设置元空间最大15m;
- 元空间大小决定能存放多少类行;
- Full GC会回收metaspace,当所需内存超过MaxMetaspaceSize时,报oom:Metaspace;
- used:加载的类的空间量;
- capacity:当前分配块的元数据的空间;
- committed:空间块的数量;
- reserved:元数据的空间保留(但不一定提交)的量;
四、栈分配参数
- -Xss=10m:设置栈大小为10m;
- 栈大小决定了函数调用的深度,已经可容纳的局部变量;
- 栈大小一般为几百k,由于线程独享,栈大小也同时一定程度上决定了程序可分配的线程大小;
/**
* @author xiaomu
* @title: JVMTest
* @description: JVM测试类
* @date 2022/8/1014:15
*/
public class JVMTest extends ClassLoader{
private static int deep = 0;
public static void main(String[] args) {
try{
testStackDeep();
}catch(Throwable e){
System.out.println("deep of calling = " + deep);
e.printStackTrace();
}
}
public static void testStackDeep(){
deep++;
testStackDeep();
}
}