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

tech2022-07-13  176

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>
最新回复(0)