一、JVM参数调优配置

    xiaoxiao2025-04-27  25

    一、背景

    1.Java虚拟机原理:所谓虚拟机,就是一台虚拟的机器,它是一款软件,用来执行一系列虚拟计算指令,大体上虚拟机可以分为系统虚拟机和程序虚拟机,大名鼎鼎的Visual Box、Vmware就属于系统虚拟机,他们完全是对物理计算的仿真,提供了一个可以运行完整操作系统的软件平台。程序虚拟机典型代表就是java虚拟机,它专门为执行单个计算程序而计算,在java虚拟机中执行的指令使我们程序成为java字节码,无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。

    2.首先我们要对JVM参数进行调优,那么我们就得明白java内存结构,这样我们才能更进一步的去了解JVM参数进行调优(主要调优堆和垃圾回收机制,见下图)。

    二、Java内存结构(JVM虚拟机存储空间)

    1.首先我们要清楚java内存模型(多线程JMM)与Java内存结构的不同(JVM虚拟机存储空间)。

    2.图解(自己画的)

    2.1.class文件被类加载器classloader(采用的双亲委派模式)进行加载到内存空间中去。

    2.2.什么是方法区:方法区又叫永久区,存放static关键字修饰的,常量信息当然包括字符串常量信息,方法区是什么时候被执行的?当class文件被加载的时候,就会初始化,所有线程会被共享(static修饰的线程会共享的)。

    2.3.调优问题:web开发的时候,定义静态常量太多好不好,不好,因为定义太多的常量它是放在方法区的,垃圾回收机制对方法区不进行回收,所以内存会越来越大,很消耗内存。

    2.4.堆:创建对象、new创建数组都是存放在堆内存中的,调优主要也是对它进行调优的,所有线程是会被共享的。

    2.5.栈:定义基本局部变量,也存类的方法,栈代码运行完毕,自动释放内存,每个线程私有,互不共享,栈不会产生线程安全问题。

    2.6.本地方法栈:主要调用底层C语言的(了解)。

    三、详细讲堆内存

    1.图解

    1.1.什么是堆内存:创建对象、new创建数组都是存放在堆内存中的。

    1.2.堆内存中又分为两个区:新生代、老年代,为啥这么分,主要是为了垃圾回收机制。

    1.3.新生代又分为三个区:eden、s0、s1。

    1.4.新生代:刚创建的对象,先放在新生代的eden区,比如user对象新建,先放在eden区,如果这个对象被频繁的使用,就会晋升到s0或者是s1区(s0、s1目的是为了垃圾回收机制的复制算法),如果在s0或者s1区中还是在频繁的使用,这时候就会晋升到老年代中。

    1.5.老年代:如果对象在频繁的使用,对象放入到老年代。

    1.6.分代的目的就是为了垃圾回收机制,垃圾回收机制主要回收新生代的对象,老年代也会回收不过次数很少的,还有一点就是老年代的对象不会逆流到新生代中去。

    1.7.调优:在web系统中,尽量减少垃圾回收机制的次数,因为如果频繁的话,服务的其他线程都会被卡死,很影响效率的,所以不要频繁回收垃圾回收。

    还有一个web开发的时候,定义静态常量太多好不好,不好,因为定义太多的常量它是放在方法区的,垃圾回收机制对方法区不进行回收,所以内存会越来越大,很消耗内存。

    新生代回收次数要比老年代次数多。

    四、堆内存参数配置

    1.什么是虚拟机参数配置

    在虚拟机运行的过程中,如果可以跟踪系统的运行状态,那么对于问题的故障排查会有一定的帮助,为此,在虚拟机提供了一些跟踪系统状态的参数,使用给定的参数执行Java虚拟机,就可以在系统运行时打印相关日志,用于分析实际问题。我们进行虚拟机参数配置,其实就是围绕着堆、栈、方法区、进行配置。你说下 你熟悉那些jvm参数调优。

    2.堆的参数配置

    -XX:+PrintGC      每次触发GC的时候打印相关日志

    -XX:+UseSerialGC      串行回收

    -XX:+PrintGCDetails  更详细的GC日志

    -Xms               堆初始值

    -Xmx               堆最大可用值

    -Xmn               新生代堆最大可用值

    -XX:SurvivorRatio     用来设置新代中eden空间和from/to空间的比例.

    含以-XX:SurvivorRatio=eden/from=den/to

    总结:在实际工作中,我们可以直接将初始的堆大小与最大堆大小相等,

    这样的好处是可以减少程序运行时垃圾回收次数,从而提高效率。

    -XX:SurvivorRatio     用来设置新代中eden空间和from/to空间的比例.

    五、设置最大堆内存

    1.代码栗子:

    /** * Created by ChenMingXu on 2019/5/26. */ public class demo001 { public static void main(String[] args) throws InterruptedException { byte[] bytes01 = new byte[1 * 1024 * 1024]; System.out.println("分配了1M内存"); jvmInfo(); Thread.sleep(3000); byte[] bytes02 = new byte[4 * 1024 * 1024]; System.out.println("分配了4M内存"); jvmInfo(); } static private String toM(long maxMemory) { float num = (float) maxMemory / (1024 * 1024); DecimalFormat df = new DecimalFormat("0.00");// 格式化小数 String s = df.format(num);// 返回的是String类型 return s; } public static void jvmInfo() { // 最大内存配置信息kb long maxMemory = Runtime.getRuntime().maxMemory(); System.out.println("maxMemory:" + maxMemory + "," + toM(maxMemory) + "M"); // 当前空闲内存 long freeMemory = Runtime.getRuntime().freeMemory(); System.out.println("freeMemory:" + freeMemory + "," + toM(freeMemory) + "M"); //已使用内存 long totalMemory = Runtime.getRuntime().totalMemory(); System.out.println("totalMemory:" + totalMemory + "," + toM(totalMemory) + "M"); } }

    2.结果:

    分配了1M内存 maxMemory:1879048192,1792.00M //默认堆内存大小 freeMemory:121798192,116.16M totalMemory:126877696,121.00M 分配了4M内存 maxMemory:1879048192,1792.00M freeMemory:117603872,112.16M totalMemory:126877696,121.00M //可能因为底层打印太快了,具体再加上4M

    3.调优参数

    3.1.参数: -Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags,// 堆初始值、堆最大可用值、串行回收、每次触发GC的时候打印相关日志,目的减少垃圾回收的次数已达到调优的目的。

    3.2.怎么使用呢?

    3.3.日志打印分析

    3.3.1.初始值设置成5M、最大值设置成20M,这时候垃圾回收2次。

    -XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC [GC (Allocation Failure) [DefNew: 1664K->192K(1856K), 0.0389905 secs] 1664K->671K(5952K), 0.1851062 secs] [Times: user=0.00 sys=0.00, real=0.18 secs] 分配了1M内存 [GC (Allocation Failure) [DefNew: 1856K->106K(1856K), 0.0023211 secs] 2335K->1799K(5952K), 0.0023404 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] maxMemory:20316160,19.38M freeMemory:3889352,3.71M totalMemory:6094848,5.81M [GC (Allocation Failure) [DefNew: 491K->134K(1856K), 0.0029878 secs][Tenured: 1797K->1931K(4096K), 0.0073899 secs] 2184K->1931K(5952K), [Metaspace: 3934K->3934K(1056768K)], 0.1023465 secs] [Times: user=0.02 sys=0.00, real=0.10 secs] 分配了4M内存 maxMemory:20316160,19.38M freeMemory:4153352,3.96M totalMemory:10358784,9.88M Heap def new generation total 1920K, used 49K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000) eden space 1728K, 2% used [0x00000000fec00000, 0x00000000fec0c6a8, 0x00000000fedb0000) from space 192K, 0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000) to space 192K, 0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000) tenured generation total 8196K, used 6027K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000) the space 8196K, 73% used [0x00000000ff2a0000, 0x00000000ff882fe0, 0x00000000ff883000, 0x00000000ffaa1000) Metaspace used 3941K, capacity 4646K, committed 4864K, reserved 1056768K class space used 435K, capacity 462K, committed 512K, reserved 1048576K

    3.3.2.初始值设置成20M、最大值设置成20M,这时候垃圾回收1次,这样能达到调优的效果。

    -XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 分配了1M内存 maxMemory:20316160,19.38M freeMemory:16335496,15.58M totalMemory:20316160,19.38M [GC (Allocation Failure) [DefNew: 3887K->640K(6144K), 0.0076749 secs] 3887K->1931K(19840K), 0.0077995 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 分配了4M内存 maxMemory:20316160,19.38M freeMemory:14039648,13.39M totalMemory:20316160,19.38M Heap def new generation total 6144K, used 4893K [0x00000000fec00000, 0x00000000ff2a0000, 0x00000000ff2a0000) eden space 5504K, 77% used [0x00000000fec00000, 0x00000000ff0274f8, 0x00000000ff160000) from space 640K, 100% used [0x00000000ff200000, 0x00000000ff2a0000, 0x00000000ff2a0000) to space 640K, 0% used [0x00000000ff160000, 0x00000000ff160000, 0x00000000ff200000) tenured generation total 13696K, used 1291K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000) the space 13696K, 9% used [0x00000000ff2a0000, 0x00000000ff3e2e48, 0x00000000ff3e3000, 0x0000000100000000) Metaspace used 3942K, capacity 4646K, committed 4864K, reserved 1056768K class space used 435K, capacity 462K, committed 512K, reserved 1048576K

    六、配置新生代与老年代调优参数

    1.设置新生代与老年代回收比例,jvm参数调优,怎么样让垃圾回收机制经常去新生代进行回收,新生代与老年代1:3或者1:4的比例进行分配

    2.代码演示(设置新生代比例参数例子,配置新生代的eden与s0或者s1的比例关系)

    /** * Created by ChenMingXu on 2019/5/27. */ public class demo002 { public static void main(String[] args) { //调优参数 //-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC //配置新生代的eden与s0或者s1的比例关系 byte[] bytes = null; for (int i = 0; i < 10; i++) { System.out.println("i:" + i); bytes = new byte[1 * 1024 * 1024]; } } }

    2.1.调优参数:-Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

    解释:-Xmn  新生代大小值,一般设为整个堆的1/3到1/4左右。

               -XX:SurvivorRatio    设置新生代中eden区和from/to(s0和s1区)空间的比例关系n/1

    2.2.运行结果

    [GC (Allocation Failure) [DefNew: 506K->256K(768K), 0.0008095 secs] 506K->430K(20224K), 0.0008442 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 768K->169K(768K), 0.0010041 secs] 942K->598K(20224K), 0.0010229 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 681K->58K(768K), 0.0005780 secs] 1110K->655K(20224K), 0.0005968 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] i:0 i:1 i:2 i:3 i:4 i:5 i:6 i:7 i:8 i:9 Heap def new generation total 768K, used 514K [0x00000000fec00000, 0x00000000fed00000, 0x00000000fed00000) eden space 512K, 89% used [0x00000000fec00000, 0x00000000fec72008, 0x00000000fec80000) from space 256K, 22% used [0x00000000fecc0000, 0x00000000fecce978, 0x00000000fed00000) to space 256K, 0% used [0x00000000fec80000, 0x00000000fec80000, 0x00000000fecc0000) tenured generation total 19456K, used 10837K [0x00000000fed00000, 0x0000000100000000, 0x0000000100000000) the space 19456K, 55% used [0x00000000fed00000, 0x00000000ff795490, 0x00000000ff795600, 0x0000000100000000) Metaspace used 3443K, capacity 4496K, committed 4864K, reserved 1056768K class space used 374K, capacity 388K, committed 512K, reserved 1048576K

    2.3.运行结果分析

    此时新生代中eden区和from/to(s0和s1区)空间的比例关系2:1,即:(256+256)=512k

    3.代码演示(设置新生代与老年代比例参数例子)

    /** * Created by ChenMingXu on 2019/5/27. */ public class demo002 { public static void main(String[] args) { //调优参数 //-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=2 //配置新生代与老年代的关系 byte[] bytes = null; for (int i = 0; i < 10; i++) { System.out.println("i:" + i); bytes = new byte[1 * 1024 * 1024]; } } }

    3.1.调优参数:-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC -XX:NewRatio=2 //配置新生代与老年代的关系(1:2),在实际中尽量设置(1:3或者1:4).

    总结:不同的堆分布情况,对系统执行会产生一定的影响,在实际工作中,应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,减少老年代的GC次数。除了可以设置新生代的绝对大小(-Xmn),可以使用(-XX:NewRatio)设置新生代和老年代的比例:-XX:NewRatio=老年代/新生代

    3.2.运行结果

    i:0 i:1 [GC (Allocation Failure) [DefNew: 3095K->1664K(5120K), 0.0027022 secs] 3095K->1744K(18816K), 0.0027460 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] i:2 i:3 i:4 [GC (Allocation Failure) [DefNew: 4800K->1024K(5120K), 0.0019211 secs] 4880K->1744K(18816K), 0.0019513 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] i:5 i:6 i:7 [GC (Allocation Failure) [DefNew: 4161K->1024K(5120K), 0.0007788 secs] 4881K->1744K(18816K), 0.0008016 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] i:8 i:9 Heap def new generation total 5120K, used 4231K [0x00000000fec00000, 0x00000000ff2a0000, 0x00000000ff2a0000) eden space 3456K, 92% used [0x00000000fec00000, 0x00000000fef21f98, 0x00000000fef60000) from space 1664K, 61% used [0x00000000ff100000, 0x00000000ff200010, 0x00000000ff2a0000) to space 1664K, 0% used [0x00000000fef60000, 0x00000000fef60000, 0x00000000ff100000) tenured generation total 13696K, used 720K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000) the space 13696K, 5% used [0x00000000ff2a0000, 0x00000000ff354040, 0x00000000ff354200, 0x0000000100000000) Metaspace used 3440K, capacity 4496K, committed 4864K, reserved 1056768K class space used 374K, capacity 388K, committed 512K, reserved 1048576K

    3.3.运行结果分析:新生代:老年代=1:2、(3456+1664+1664)x2=13568k,约等于老年代的内存大小13696k

    七、堆溢出解决方法

    1.代码例子

    /** * Created by ChenMingXu on 2019/5/27. */ public class demo003 { public static void main(String[] args) { List<Object> listObject = new ArrayList<>(); for (int i = 0; i < 10; i++) { System.out.println("i:"+i); listObject.add(new byte[1*1024*1024]); } System.out.println("创建完毕!"); } }

    2.参数设置:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError(因为listObject也要占内存,所以会报堆内存溢出):

    i:0 i:1 i:2 i:3 i:4 i:5 i:6 i:7 java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid14788.hprof ... Heap dump file created [8977694 bytes in 0.045 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at demo003.main(demo003.java:12)

    3.解决办法参数设置:-Xms10m -Xmx40m -XX:+HeapDumpOnOutOfMemoryError(实际也是修改-Xmx的大小一般为30m或者40m)。

    3.1.打印结果

    i:0 i:1 i:2 i:3 i:4 i:5 i:6 i:7 i:8 i:9 创建完毕!

    八、栈溢出解决方法

    1.代码演示

    /** * Created by ChenMingXu on 2019/5/27. */ //什么是栈溢出? 无线递归调用 public class demo004 { private static int count = 0; public static void getCount() { try { count++; getCount(); } catch ( Throwable e) { System.out.println("最大的深度...." + count); e.printStackTrace(); } } //栈溢出 是方法中递归调用 不是循环调用方法 发生栈溢出 public static void main(String[] args) { // for (int i = 0; i < 10000; i++) { getCount(); } }

    2.结果显示(栈溢出了错误)

    java.lang.StackOverflowError 最大的深度....13483 at java.util.HashMap.afterNodeAccess(HashMap.java:1779) at java.util.HashMap.putVal(HashMap.java:657) at java.util.HashMap.put(HashMap.java:612) at java.util.HashSet.add(HashSet.java:220) at java.util.Collections$SynchronizedCollection.add(Collections.java:2035) at java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:508) at demo004.getCount(demo004.java:11) at demo004.getCount(demo004.java:11) *** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 876

    3.解决办法

    3.1.栈溢出产生于递归调用,循环遍历是不会的,但是循环方法里面产生递归调用, 也会发生栈溢出。

    3.2.解决办法:设置线程最大调用深度,-Xss5m 设置最大调用深度

    3.3.打印结果(最大深度比上面的大了很多了)

    java.lang.StackOverflowError 最大的深度....274094 at java.util.HashMap.afterNodeAccess(HashMap.java:1779) at java.util.HashMap.putVal(HashMap.java:657) at java.util.HashMap.put(HashMap.java:612) at java.util.HashSet.add(HashSet.java:220) at java.util.Collections$SynchronizedCollection.add(Collections.java:2035) at java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:508) at demo004.getCount(demo004.java:11) at demo004.getCount(demo004.java:11) *** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with message transform method call failed at JPLISAgent.c line: 876

    九、结束

    以上就是我对jvm调优的学习以及认识,如有哪里做的不好,请批评指正,共勉!!!

    Always keep the faith!!!

    最新回复(0)