JVM--内存调优

什么是虚拟机参数配置

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

常见的堆的参数配置
  • -XX:+PrintGC
    每次触发GC的时候打印相关的日志
  • -XX:-UseSerialGC
    串行回收(规定的垃圾收集器的策略)
  • -XX:+PrintGCDetails
    更详细的GC日志
  • -Xms
    堆的初始值
  • -Xmx
    堆的最大的可用值
  • -Xmn
    新生代堆最大可用值
  • -XX:SurvivorRatio
    用来设置新生代中eden空间和from/to空间的比例

含以-XX:SurviorRatio=eden/(from+to)
总结:
在实际的工作种,我们可以直接将初始的堆大小与最大堆大小相等,这样的好处是可以减少程序运行时候的垃圾回收次数,从而提高效率

举例说明,下面是我们没有设置JVM相关参数的代码,以及输出后的结果
package com.xiyou.jvm;

import java.text.DecimalFormat;

public class Demo1 {

    /**
     * 该函数的作用是格式化处理内存大小,将kb转换成M
     * @param maxMemory
     * @return
     */
    static private String toM(long maxMemory){
        float num = (float) maxMemory / (1024 * 1024);
        // 格式化小数
        DecimalFormat df = new DecimalFormat("0.00");
        // 返回String类型
        String s = df.format(num);
        return s;
    }

    public static void main(String[] args) {
        byte[] bytes01 = new byte[1*1024*1024];
        System.out.println("分配了1M内存");
        jvmInfo();
        byte[] bytes02 = new byte[4*1024*1024];
        System.out.println("分配了4M内存");
        jvmInfo();
    }

    public static void jvmInfo(){
        // 最大内存配置信息
        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");
    }
}

如上面的代码所示,我们在没有规定堆内存的时候,打印了最大内存,空闲内存,已经使用的内存

  • 最大内存:
Runtime.getRuntime().maxMemory();
  • 空闲内存:
Runtime.getRuntime().freeMemory();
  • 已经使用的内存:
Runtime.getRuntime().totalMemory();

上面代码的打印结果如下:

Connected to the target VM, address: '127.0.0.1:12163', transport: 'socket'
分配了1M内存
maxMemory: 1881145344, 1794.00M
freeMemory: 123128344, 117.42M
totalMemory: 128974848, 123.00M
分配了4M内存
maxMemory: 1881145344, 1794.00M
freeMemory: 118934024, 113.42M
totalMemory: 128974848, 123.00M
Disconnected from the target VM, address: '127.0.0.1:12163', transport: 'socket'

Process finished with exit code 0

通过上面的结果可以看到
(1)不去配置堆内存的时候本机默认是1784M堆内存
(2)已经使用的内存totalMemory无论分配多少M结果都是123M,原因是打印的太快了,其实内存已经改变了,只不过没有记录出来。

下面是我们设置了虚拟机参数后的代码和输出结果
  • 我们设置以下参数:
    (1)-Xms5m:堆的初始内存大小是5M
    (2)-Xmx20m:堆的最大可用值是20m
    (3)-XX:PrintGCDetails:打印更详细的GC日志
    (4)-XX:+UserSerialGC:串行回收
    (5)-XX:+PrintCommandLineFlags:打印出已经被用户或者JVM设置过的详细的XX参数的名称和值
  • 将这些参数进行设置
    在这里插入图片描述
  • 代码不做改变,和上面的代码一致
  • 结果如下:
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
[GC (Allocation Failure) [DefNew: 1653K->192K(1856K), 0.0011658 secs] 1653K->619K(5952K), 0.0011940 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
分配了1M内存
[GC (Allocation Failure) [DefNew: 1856K->108K(1856K), 0.0015049 secs] 2283K->1745K(5952K), 0.0015222 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
maxMemory: 20316160, 19.38M
freeMemory: 3955424, 3.77M
totalMemory: 6094848, 5.81M
[GC (Allocation Failure) [DefNew: 452K->128K(1856K), 0.0004677 secs][Tenured: 1743K->1871K(4096K), 0.0015799 secs] 2089K->1871K(5952K), [Metaspace: 4003K->4003K(1056768K)], 0.0020839 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
分配了4M内存
maxMemory: 20316160, 19.38M
freeMemory: 4215384, 4.02M
totalMemory: 10358784, 9.88M
Heap
 def new generation   total 1920K, used 49K [0x00000000fec00000, 0x00000000fee10000, 0x00000000ff2a0000)
  eden space 1728K,   2% used [0x00000000fec00000, 0x00000000fec0c670, 0x00000000fedb0000)
  from space 192K,   0% used [0x00000000fedb0000, 0x00000000fedb0000, 0x00000000fede0000)
  to   space 192K,   0% used [0x00000000fede0000, 0x00000000fede0000, 0x00000000fee10000)
 tenured generation   total 8196K, used 5967K [0x00000000ff2a0000, 0x00000000ffaa1000, 0x0000000100000000)
   the space 8196K,  72% used [0x00000000ff2a0000, 0x00000000ff873dc8, 0x00000000ff873e00, 0x00000000ffaa1000)
 Metaspace       used 4009K, capacity 4646K, committed 4864K, reserved 1056768K
  class space    used 449K, capacity 462K, committed 512K, reserved 1048576K

Process finished with exit code 0

通过上面我们可以看到GC进行了三次,当我们设置将Xmx和Xms相等的时候,就是最大的堆内存和初始的堆内存大小一样的时候

-XX:InitialHeapSize=20971520 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 
分配了1M内存
maxMemory: 20316160, 19.38M
freeMemory: 16475232, 15.71M
totalMemory: 20316160, 19.38M
[GC (Allocation Failure) [DefNew: 3750K->640K(6144K), 0.0020843 secs] 3750K->1871K(19840K), 0.0025898 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
分配了4M内存
maxMemory: 20316160, 19.38M
freeMemory: 14101752, 13.45M
totalMemory: 20316160, 19.38M
Heap
 def new generation   total 6144K, used 4892K [0x00000000fec00000, 0x00000000ff2a0000, 0x00000000ff2a0000)
  eden space 5504K,  77% used [0x00000000fec00000, 0x00000000ff0273b0, 0x00000000ff160000)
  from space 640K, 100% used [0x00000000ff200000, 0x00000000ff2a0000, 0x00000000ff2a0000)
  to   space 640K,   0% used [0x00000000ff160000, 0x00000000ff160000, 0x00000000ff200000)
 tenured generation   total 13696K, used 1231K [0x00000000ff2a0000, 0x0000000100000000, 0x0000000100000000)
   the space 13696K,   8% used [0x00000000ff2a0000, 0x00000000ff3d3cf8, 0x00000000ff3d3e00, 0x0000000100000000)
 Metaspace       used 4010K, capacity 4646K, committed 4864K, reserved 1056768K
  class space    used 449K, capacity 462K, committed 512K, reserved 1048576K

Process finished with exit code 0

可以看到只进行了一次GC操作。这就是验证了我们上面所说:

重要
  • 当设置最大堆内存和初始化堆内存一致的时候,垃圾回收阶段最少。
  • 堆的初始值越小,垃圾回收的次数越多。为了调优应该让Xmx和Xms的大小设置的一致。

设置新生代与老年代的回收比例进行调优

上面我们讲到了减少垃圾回收次数进行调优,这里我们再讲解一个调优策略------设置新生代与老年代的回收比例进行调优。使得垃圾回收机制经常去新生代进行回收

  • 新生代与老年代的比例是1/3或者1/4,新生代较小的话,垃圾回收机制就会经常回收新生代,因为新生代内存不足,需要GC。
  • 这里我们接收几个比较重要的参数:
    (1)-XX:SurvivorRatio 用来设置新生代中eden和from+to空间的比例(可以理解为eden/(to+from))
    (2)-Xmn:设置新生代的大小,一般设为整个堆的1/3或者1/4左右
    (3)-XX:NewRatio:就是老年代和新生代的比例,该参数=老年代/新生代(尽量让垃圾回收发生在新生代而不是老年代,所以一般新生代都要比老年代小)

内存溢出

我们内存的溢出经常指的是栈内存溢出和堆内存溢出

  • 栈内存溢出:
    (1)栈内存溢出指的是无限递归调用
    (2)注意一点,无限循环调用不是栈溢出。
    (3)错误:java.lang.StackOverflowError
    (4)解决方法:-Xss5m设置最大的调用深度
    (5)栈内存例子:
package com.xiyou.jvm;

/**
 * 栈内存溢出的例子
 */
public class Demo2 {
    private static int count;
    public static void count(){
        try {
            count++;
            // 无限递归,没有终止条件
            count();
        } catch (Throwable e) {
            System.out.println("最大深度:"+count);
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        count();
    }

}

输出结果

最大深度:12298
java.lang.StackOverflowError
	at sun.misc.Unsafe.compareAndSwapLong(Native Method)
	at java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2259)
	at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1070)
	at java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1535)
	at java.lang.ClassLoader.getClassLoadingLock(ClassLoader.java:463)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:411)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	....
  • 堆内存溢出:
    (1)错误:java.lang.OutOfMemoryError: Java heap space
    (2)解决方案:设置堆内存大小 -Xms1m -Xmx10m(设置初始的堆内存大小和最大的可用堆内存大小,这里只是举个例子,并不是一定要设置这么大)
    (3)堆内存溢出例子:
package com.xiyou.jvm;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

public class Demo3
{
    public static void main(String[] args) throws InterruptedException {
        List<Object> list = new ArrayList<>();
        Thread.sleep(3000);
        jvmInfo();
        for (int i = 0; i < 1000000000; i++) {
            System.out.println("i:"+i);
            Byte [] bytes=	new Byte[1*1024*1024];
            list.add(bytes);
            jvmInfo();
        }
        System.out.println("添加成功...");
    }
    /**
     * 该函数的作用是格式化处理内存大小,将kb转换成M
     * @param maxMemory
     * @return
     */
    static private String toM(long maxMemory){
        float num = (float) maxMemory / (1024 * 1024);
        // 格式化小数
        DecimalFormat df = new DecimalFormat("0.00");
        // 返回String类型
        String s = df.format(num);
        return s;
    }
    public static void jvmInfo(){
        // 最大内存配置信息
        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");
    }
}

结果:

(输出堆未溢出的时候的内存)
.....
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.xiyou.jvm.Demo3.main(Demo3.java:15)

总结:

  1. 减少垃圾回收次数可以进行JVM调优
    (1)设置最大堆内存大小(Xmx)和堆内存初始大小一致(Xms),Xms越小,垃圾回收次数越多。
  2. 尽量让GC发生在新生代
    (1)-Xmn:设置新生代的大小,一般为整个堆的1/3 1/4
    (2)-XX:SurvivorRatio=eden/(to+from)
    (3)-XX:NewRatio = 老年代/新生代
    总而言之我们希望通过设置达到以下要求:
    (1)GC的时间足够小
    (2)GC的次数足够少
    (3)发生Full GC(新生代和老年代)的周期足够长
    但是我们(2)和(1)是不能同时成立的。要想GC时间少,就必须有个更小的堆,要想GC次数足够少,就需要一个更大的堆,并且(3)我们无法给出明确的指标,所以我们通常在生产环境上按照上面说明的几点设计就好。

猜你喜欢

转载自blog.csdn.net/u014437791/article/details/89297760