你说你做过JVM调优和参数配置,请问如何查看JVM系统默认值
使用jps和jinfo进行查看
-Xms:初始堆空间
-Xmx:堆最大值
-Xss:栈空间
-Xms 和 -Xmx最好调整一致。由于heap内存扩大或缩小导致应用停顿,降低延迟,同时避免每次垃圾回收完成后JVM重新分配内存
JVM参数类型
- 标配参数(从JDK1.0 - Java12都在,很稳定)
- -version
- -help
- java -showversion
- X参数(了解)
- -Xint:解释执行
- -Xcomp:第一次使用就编译成本地代码
- -Xmixed:混合模式
- XX参数(重点)
- Boolean类型:-XX:+ 或者-某个属性 + 表示开启,- 表示关闭
-XX:-PrintGCDetails 表示关闭了GC详情输出
- key-value类型:-XX:属性key=属性value
不满意初始值,可以通过下列命令调整 -XX:MetaspaceSize=21807104
- Boolean类型:-XX:+ 或者-某个属性 + 表示开启,- 表示关闭
查看运行的Java程序,JVM参数是否开启,具体值为多少?
首先我们运行一个HelloGC的java程序
public class HelloGC {
public static void main(String[] args) throws InterruptedException {
System.out.println("hello GC");
Thread.sleep(Integer.MAX_VALUE);
}
}
查看默认参数
jps:查看java的后台进程
jinfo:查看正在运行的java程序
查看进程号:jps -l
22032 org.jetbrains.jps.cmdline.Launcher
25640 ch.HelloGC
73596 org.jetbrains.kotlin.daemon.KotlinCompileDaemon
查看到HelloGC的进程号为:25640
使用jinfo -flag 然后查看是否开启PrintGCDetails这个参数
jinfo -flag PrintGCDetails 25640
得到的内容是
-XX:-PrintGCDetails
-号表示关闭,即没有开启PrintGCDetails这个参数
在启动HelloGC的时候,增加 PrintGCDetails这个参数,需要在运行程序的时候配置JVM参数
然后在VM Options中加入下面的代码,现在+号表示开启
-XX:+PrintGCDetails
然后在使用jinfo查看我们的配置
jps -l
jinfo -flag PrintGCDetails 进程ID
得到的内容是
-XX:+PrintGCDetails
原来的-号变成了+号,说明我们通过 VM Options配置的JVM参数已经生效了
使用下列命令,会把jvm的全部默认参数输出
jinfo -flags 进程ID
得到的内容是
Attaching to process ID 89060, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.151-b12
Non-default VM flags: -XX:CICompilerCount=12 -XX:InitialHeapSize=266338304 -XX:MaxHeapSize=4261412864 -XX:MaxNewSize=1420296192 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=88604672 -XX:OldSize=177733632 -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -XX:+PrintGCDetails -javaagent:D:\dev\IntelliJ IDEA 2020.1\lib\idea_rt.jar=5136:D:\dev\IntelliJ IDEA 2020.1\bin -Dfile.encoding=UTF-8
两个经典参数:-Xms 和 -Xmx,这两个参数 如何解释
这两个参数,还是属于XX参数,因为取了别名
- -Xms 等价于 -XX:InitialHeapSize :初始化堆内存(默认只会用最大物理内存的1/64)
- -Xmx 等价于 -XX:MaxHeapSize :最大堆内存(默认只会用最大物理内存的1/4)
查看JVM默认参数
查看JVM初始默认参数-XX:+PrintFlagsInitial
java -XX:+PrintFlagsInitial -version
java -XX:+PrintFlagsInitial(重要参数)
查看JVM修改以后,最终的值的参数-XX:+PrintFlagsFinal
java -XX:+PrintFlagsFinal
工作中配置常用的JVM基本参数
查看堆内存
查看JVM的初始化堆内存 -Xms 和最大堆内存 Xmx
public class HelloGC {
public static void main(String[] args) throws InterruptedException {
// 返回Java虚拟机中内存的总量
long totalMemory = Runtime.getRuntime().totalMemory();
// 返回Java虚拟机中试图使用的最大内存量
long maxMemory = Runtime.getRuntime().maxMemory();
System.out.println("TOTAL_MEMORY(-Xms) = " + totalMemory + "(字节)、" + (totalMemory / (double)1024 / 1024) + "MB");
System.out.println("MAX_MEMORY(-Xmx) = " + maxMemory + "(字节)、" + (maxMemory / (double)1024 / 1024) + "MB");
}
}
运行结果:
TOTAL_MEMORY(-Xms) = 255328256(字节)、243.5MB
MAX_MEMORY(-Xmx) = 3787980800(字节)、3612.5MB
-Xms 初始堆内存为:系统物理内存的1/64
-Xmx 最大堆内存为:系统物理内存的 1/4
打印JVM默认参数
使用 -XX:+PrintCommandLineFlags
打印出JVM的默认的简单初始化参数
-XX:InitialHeapSize=266253056 -XX:MaxHeapSize=4260048896 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
自己格式化后方便查看如下:
-XX:InitialHeapSize=266253056
-XX:MaxHeapSize=4260048896
-XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
常用调优参数
- -Xms:初始化堆内存,默认为物理内存的1/64,等价于 -XX:initialHeapSize
- -Xmx:最大堆内存,默认为物理内存的1/4,等价于-XX:MaxHeapSize
- -Xss:设计单个线程栈的大小,一般默认为512K~1024K,等价于 -XX:ThreadStackSize
- 使用 jinfo -flag ThreadStackSize 会发现 -XX:ThreadStackSize = 0
- 这个值的大小是取决于平台的
- Linux/x64:1024KB
- OS X:1024KB
- Oracle Solaris:1024KB
- Windows:取决于虚拟内存的大小
- -Xmn:设置年轻代大小
- -XX:MetaspaceSize:设置元空间大小
- 元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存,因此,默认情况下,元空间的大小仅受本地内存限制。
- -Xms10m -Xmx10m -XX:MetaspaceSize=1024m -XX:+PrintFlagsFinal
- 但是默认的元空间大小:只有20多M
- 为了防止在频繁的实例化对象的时候,让元空间出现OOM,因此可以把元空间设置的大一些
- -XX:PrintGCDetails:输出详细GC收集日志信息
- GC
- Full GC
GC日志收集流程图
我们使用一段代码,制造出垃圾回收的过程
首先我们设置一下程序的启动配置: 设置初始堆内存为10M,最大堆内存为10M
-Xms10m -Xmx10m -XX:+PrintGCDetails
然后用下列代码,创建一个 非常大空间的byte类型数组
byte [] byteArray = new byte[50 * 1024 * 1024];
运行后,发现会出现下列错误,这就是OOM:java内存溢出,也就是堆空间不足
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at ch.GCDetails.main(GCDetails.java:12)
同时还打印出了GC垃圾回收时候的详情
[GC (Allocation Failure) [PSYoungGen: 1977K->504K(2560K)] 1977K->712K(9728K), 0.0005634 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 504K->504K(2560K)] 712K->816K(9728K), 0.0003513 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 312K->650K(7168K)] 816K->650K(9728K), [Metaspace: 3462K->3462K(1056768K)], 0.0041352 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 650K->650K(9728K), 0.0002124 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 650K->632K(7168K)] 650K->632K(9728K), [Metaspace: 3462K->3462K(1056768K)], 0.0038281 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
问题发生的原因:
因为们通过 -Xms10m 和 -Xmx10m 只给Java堆栈设置了10M的空间,但是创建了50M的对象,因此就会出现空间不足,而导致出错
同时在垃圾收集的时候,我们看到有两个对象:GC 和 Full GC
GC垃圾收集
GC在新生区
[GC (Allocation Failure) [PSYoungGen: 1977K->504K(2560K)] 1977K->724K(9728K), 0.0011719 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
GC (Allocation Failure):表示分配失败,那么就需要触发年轻代空间中的内容被回收
[PSYoungGen: 1977K->504K(2560K)] 1977K->724K(9728K), 0.0011719 secs]
参数解析图
Full GC垃圾回收
Full GC大部分发生在养老区
[Full GC (Allocation Failure) [PSYoungGen: 512K->0K(2560K)] [ParOldGen: 372K->649K(7168K)] 884K->649K(9728K), [Metaspace: 3453K->3453K(1056768K)], 0.0042719 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
规律:
[名称: GC前内存占用 -> GC后内存占用 (该区内存总大小)]
当出现了老年代都扛不住的时候,就会出现OOM异常
-XX:SurvivorRatio
调整新生代中 eden 和 S0、S1的空间比例,
默认为 -XX:SuriviorRatio=8,Eden:S0:S1 = 8:1:1
如果设置成 -XX:SurvivorRatio=4,则为 Eden:S0:S1 = 4:1:1
SurvivorRatio值就是设置eden区的比例占多少,S0和S1相同
Java堆从GC的角度还可以细分为:
新生代(Eden区,From Survivor区合To Survivor区)和老年代
- eden、SurvivorFrom复制到SurvivorTo,年龄+1
首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom去,当Eden区再次触发GC的时候会扫描Eden区和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果对象的年龄已经到达老年的标准,则赋值到老年代区),通知把这些对象的年龄+1 - 清空eden、SurvivorFrom
然后,清空eden,SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to - SurvivorTo和SurvivorFrom互换
最后,SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区,部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认为15),最终如果还是存活,就存入老年代
-XX:MaxTenuringThreshold
设置垃圾最大年龄,SurvivorTo和SurvivorFrom互换,原SurvivorTo成为下一次GC时的SurvivorFrom区,部分对象会在From和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认为15),最终如果还是存活,就存入老年代 这里就是调整这个次数的,默认是15,并且设置的值 在 0~15之间
查看默认进入老年代年龄:jinfo -flag MaxTenuringThreshold 17344
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻对象不经过Survivor区,直接进入老年代。对于年老代比较多的应用,可以提高效率。如果将此值设置为一个较大的值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活时间,增加在年轻代即被回收的概念
-XX:NewRatio(了解)
配置年轻代new 和老年代old 在堆结构的占比, 默认: -XX:NewRatio=2 新生代占1,老年代2,年轻代占整个堆的1/3, -XX:NewRatio=4:新生代占1,老年代占4,年轻代占整个堆的1/5,NewRadio值就是设置老年代的占比,剩下的1个新生代, 新生代特别小,会造成频繁的进行GC收集