掌握JVM参数优化配置与常见JVM异常分析

JVM参数类型与设置

安装JDK,配置系统环境参数后,输入java -help,红框标记的就是我们设置 JVM参数的区域。
在这里插入图片描述
[-options]意思是不强制要求设置,java命令支持各种参数设置,这些参数可以分为以下类别

  • 标准参数:JVM的所有实现所支持的最常用的参数 (在jdk各版本之间稳定,不会有较大变化)。
  • 非标准参数(-X):是特定于Java HotSpot虚拟机的通用选项(了解就行)。
  • 高级运行时参数(-XX):控制Java HotSpot VM的运行时行为(重点)。

这里我们重点讲高级运行时参数,JVM参数主要有boolean值类型与key-value值类型。我们写两个case应用一下,Test是我们的资源类。用boolean值设置打印出GC的详情,用key-value值设置MetaspaceSize内存大小。

  • +表示boolean值true,-表示boolean值false。
public class Test {
    public static void main(String[] args) throws Exception {
        System.out.println("hello world");
    }
}

在这里插入图片描述

  • -XX:属性(key)=属性值(value)
    在这里插入图片描述
JVM参数查看
查看运行时JVM参数配置信息
//我们把资源类Test添加sleep用于测试
public class Test extends ClassLoader {
    public static void main(String[] args) throws Exception {
        System.out.println("hello world");
        Thread.sleep(Integer.MAX_VALUE);
    }
}

在这里插入图片描述
此时Test进程阻塞中,我们看看我们的设置的元空间大小是否成功,重新打开一个窗口
在这里插入图片描述

  • jps:相当于linux的ps -ef|grep java,主要查询出JVM进程的进程编号
  • jinfo:根据JVM进程编号查看该进程的信息
查看出厂的JVM的参数默认配置信息
  • -XX:+PrintFlagsInitial:查看默认值
    在这里插入图片描述
  • -XX:+PrintFlagsFinal:查看最终值,就是JVM运行时的值
    在这里插入图片描述
    其中带 : 的就是修改过的值,就是JVM修改或者人为的修改过的值。
  • -XX:+PrintCommandLineFlags:查看当前常用参数默认值
    在这里插入图片描述
JVM常用参数
  • -Xms(最小堆内存)、-Xmx(最大堆内存)
    在这里插入图片描述
public class Test {

    public static void main(String[] args) throws Exception {
        long totalMemory = Runtime.getRuntime().totalMemory();
        long maxMemory = Runtime.getRuntime().maxMemory();
        System.out.println("totalMemory(-Xms) = " + totalMemory / (double) 1024 / 1024 + "MB");
        System.out.println("maxMemory(-Xmx) = " + maxMemory / (double) 1024 / 1024 + "MB");
    }
}

我的电脑内存为8G,JVM初始内存一般为物理内存的1/64,最大内存为物理内存的1/4。(拓展:-Xms=-XX:InitialHeapSize,-Xmx=-XX:MaxHeapSize)

totalMemory(-Xms) = 123.0MB
maxMemory(-Xmx) = 1803.0MB
  • -Xss(单个线程栈的内存大小)
    一般默认值为512KB~1024KB,我们可以按上面的查看方式确认一下默认值
    在这里插入图片描述
    发现竟然是0,怎么可能,我们去官网看看为什么,发现0表示使用的是默认值,这个默认值又依赖于平台,不同平台就不一样。
    在这里插入图片描述(拓展:-Xss=-XX:ThreadStackSize)
  • -Xmn(新生代内存大小)
    一般不去设置,默认新生代大小堆内存的1/3,老年代堆内存的2/3
  • -XX:MetaspaceSize
    在再JDK1.8后,永久代被元空间所取代,它们之间最大的区别是永久代使用的内存是JVM的堆内存,而元空间使用的是本地内存。本地内存(Native memory),也称为C-Heap,是供JVM自身进程使用的。当Java Heap空间不足时会触发GC,但Native memory空间不够却不会触发GC。默认情况下元空间的大小只受本地物理内存限制。初始默认大小为21M左右。
  • -XX:+PrintGCDetails
    打印垃圾回收信息,设置一个需要GC的场景
public class Test {

    public static void main(String[] args) throws Exception {
        System.out.println("hello world");
        //创建一个大对象
        byte[] bytes = new byte[1024 * 1024 * 50];
    }
}

执行时设置最大堆内存小于50m,即可出现GC的动作。
在这里插入图片描述
结合上图的GC信息,分析当前堆内存使用情况
在这里插入图片描述

  • -XX:SurvivorRatio
    作用是设置新生代的Eden区与Survivor-From和Survivor-To的比例,默认为8,就是8:1:1,如果修改为2,则2:1:1,Survivor-From和Survivor-To是相同的。
    新生代
    使用-XX:PrintGCDetails,查看发现eden为33280K,from为5120K,to为5120K
    在这里插入图片描述

  • -XX:NewRatio
    设置新生代老年代的比例,默认为2,就是新生代1/3,老年代2/3。如果修改5,则新生代为1/6,老年代就是5/6。

  • -XX:MaxTenuringThreshold
    设置用于自适应GC大小调整的最大使用期限阈值。最大值为15。并行(吞吐量)收集器的默认值为15,而CMS收集器的默认值为6。理解它之前我们先了解一下MinorGC执行的流程。
    1)首先当Eden区满时,第一次GC,把还活着的对象拷贝到Survivor-From区,
    2)当Eden区再次出发GC的时候,会扫描Eden区和Survivor-From区,对这俩区进行垃圾回收。
    3)经过垃圾回收后,还存活的对象直接复制到Survivor-To区,同时把复制到Survivor-To区的对象年龄+1。
    4)然后清空Eden和Survivor-From区,
    5)最后将Survivor-From区改为Survivor-To区,Survivor-To区改为Survivor-From区,谁空谁就是Survivor-To区。
    MinorGC执行时,部分对象在Survivor-From和Survivor-To区复制来复制区,来回交换15次最终还是存活则存入到老年代。改参数就是新生代对象转为老年代对象的难易程度,最大GC后存活15次即可移入老年代。

JVM常见异常
java.lang.StackOverflowError

原因:线程请求的栈深度 超过了虚拟机允许的深度

public class Test {
	//代码例子
    public static void main(String[] args) throws Exception {
        stackOverflowError();
    }
    private static void stackOverflowError(){
        stackOverflowError();
    }
}

线程栈帧结构图:
在这里插入图片描述

java.lang.OutOfMemoryError:Java heap space

原因:表示的是新对象不能在java heap中分配。

public class Test {
  	//代码例子 启动参数添加 -Xms20m -Xmx20m
    public static void main(String[] args) throws Exception {
        byte[] bytes = new byte[1024 * 1024 * 50];
    }
}
java.lang.OutOfMemoryError:GC overhead limit exceeded

原因:GC回收时间过长时会抛出OutOfMemoryError,过长的定义是超过98%的时间在GC并且回收不到2%的堆内存。如果不抛出GC overhead limit,将会频繁GC,造成恶性循环,造成CPU高使用率。

public class Test {
    //代码例子 启动参数添加 -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
    public static void main(String[] args) throws Exception {
        int i = 0;
        List<String> list = new ArrayList<>();

        try {
            while (true) {
                list.add(String.valueOf(++i).intern());
            }
        } catch (Throwable t) {
            System.out.println("i = " + i);
            t.printStackTrace();
            throw t;
        }
    }
}

GC详情,可以看出GC效果差,GC前后内存变化小

[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7048K->7048K(7168K)] 9096K->9096K(9728K), [Metaspace: 2841K->2841K(1056768K)], 0.0388129 secs] [Times: user=0.25 sys=0.00, real=0.04 secs] 
java.lang.OutOfMemoryError:Direct buffer Memory

NIO:1.8的新特性,NIO三大组件Selector、Channel、Buffer。它可以通过Native函数库直接分配堆外内存,然后通过在JVM堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样可以在某些场景中显著提高性能,因为避免了数据在Java堆和Native堆来回复制。

原因:NIO代码造成操作系统本地内存不足。

public class Test {
    //查看最大的本地直接内存,默认为物理内存1/4
    public static void main(String[] args) throws Exception {
        System.out.println("maxDirectMemory = " + (sun.misc.VM.maxDirectMemory()/(double)1024/1024+"MB"));
    }
}
public class Test {
    //代码例子 启动参数添加 -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m
    public static void main(String[] args) throws Exception {
        //在Java堆中分配内存,属于GC管辖范围,由于需要拷贝,速度相对较慢
//       ByteBuffer.allocate(1);
        //在本地内存中分配内存,不属于GC管辖范围,不需要拷贝,速度相对较快
        ByteBuffer.allocateDirect(1024 * 1024 * 10);
    }
}
java.lang.OutOfMemoryError:unable to create new native thread

原因:程序创建的线程太多,超过了平台系统的极限(Linux系统单个进程默认可以创建的线程最大数为1024个)

public class Test {
    //代码例子 请在Linux上执行
    public static void main(String[] args) throws Exception {
        for (int i = 0; ; i++) {
            System.out.println("i = " + i);
            new Thread(() -> {
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
ulimit -u #查看当前用户的最大线程数
vim /etc/security/limits.d/90-nproc.conf #修改创建线程上线
java.lang.OutOfMemoryError:Metaspace

1.8的新特性:Metaspace代替了永久代,是方法区在HotSpot中的实现,并不占用虚拟机内存,而是使用本地内存,也可以称它为native memory。主要存放:虚拟机加载的类信息、常量、静态变量、即时编译后的代码。

原因:Metaspace内存不足

public class Test {
    //资源类
    static class Obj {
    }

    //代码例子 启动参数添加-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
    public static void main(String[] args) throws Exception {
        int i = 0;
        try {
            while (true) {
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(Obj.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                        return proxy.invoke(obj, args);
                    }
                });
                enhancer.create();
            }
        } catch (Throwable t) {
            System.out.println("i = " + i);
            t.printStackTrace();
        }
    }
}

记得引包

<!-- https://mvnrepository.com/artifact/cglib/cglib -->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值