Java8虚拟机内存溢出的示例分析(java,web开发)

时间:2024-05-08 01:54:50 作者 : 石家庄SEO 分类 : web开发
  • TAG :

Java8虚拟机(JVM)内存溢出实战

前言

相信很多JAVA中高级的同学在面试的时候会经常碰到一个面试题
你是如何在工作中对JVM调优和排查定位问题的?

事实上,如果用户量不大的情况下,在你的代码还算正常的情况下,在工作中除非真正碰到与JVM相关的问题是少之又少,就算碰到了也是由公司的一些大牛去排查解决,那么我们又如何积累这方面的经验呢?下面由冲锅带大家一起来实践JVM的调优吧

注意我们平常所说的JVM调优一般指Java堆,Java虚拟机栈参数调优

Java堆溢出

先来一段代码示例,注意笔者用的是IDEA工具,需要配置一下VM options 为-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError,如果不清楚的百度一下如何配置idea的JVM运行参数

packagecom.example.demo.jvm;importjava.util.ArrayList;importjava.util.List;/***@Author:WangChong*@Date:2019/9/229:37*@Version:V1.0*/publicclassHeapOutMemoryTest{staticclassChongGuo{}publicstaticvoidmain(String[]args){List<ChongGuo>chongGuos=newArrayList<>();while(true){chongGuos.add(newChongGuo());}}}

运行结果如下:

java.lang.OutOfMemoryError:JavaheapspaceDumpingheaptojava_pid9352.hprof...Heapdumpfilecreated[28701160bytesin0.122secs]Exceptioninthread"main"java.lang.OutOfMemoryError:Javaheapspaceatjava.util.Arrays.copyOf(Arrays.java:3210)atjava.util.Arrays.copyOf(Arrays.java:3181)atjava.util.ArrayList.grow(ArrayList.java:261)atjava.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)atjava.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)atjava.util.ArrayList.add(ArrayList.java:458)atcom.example.demo.jvm.HeapOutMemoryTest.main(HeapOutMemoryTest.java:18)DisconnectedfromthetargetVM,address:'127.0.0.1:54599',transport:'socket'

可以看到控制台出现java.lang.OutOfMemoryError: Java heap space的错误,这是为什么呢,首先先解释一下上面的运行参数

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError


  • -Xms20m:设置JVM最小内存为20m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存


  • -Xmx20m:设置JVM最大可用内存20M


  • -XX:+HeapDumpOnOutOfMemoryError 表示当JVM发生OOM时,自动生成DUMP文件


下面我们分析一下出错的原因,用JProfiler分析一下,打开刚才生成的名为java_pid9352.hprof的dump文件。可以看到根据(InstanceXcount和Size)基本可以确定哪个类的对象出现问题,在上面示例中,可以是ChongGuo这个实例生在数量的大小已经超过12M,但没有超过20M,那么新问题又来了?没到20M为啥会报堆内存溢出呢?

Java8虚拟机内存溢出的示例分析

答案就是JDK8中堆内存中还包括Metaspace,即元内存空间,在元空间出现前JDK1.7之前在JDK7以及其前期的JDK版本号中。堆内存通常被分为三块区域Nursery内存(young generation)、长时内存(old generation)、永久内存(Permanent Generation for VM Matedata),如下图

Java8虚拟机内存溢出的示例分析

当中最上一层是年轻代,一个对象被创建以后首先被放到年轻代中的Eden内存中,假设存活期超两个Survivor之后就会被转移到长时内存中(Old Generation)中永久内存中存放着对象的方法、变量等元数据信息。通过假设永久内存不够。我们就会得到例如以下错误:java.lang.OutOfMemoryError: PermGen
而在JDK8中情况发生了明显的变化,就是普通情况下你都不会得到这个错误,原因
在于JDK8中把存放元数据中的永久内存从堆内存中移到了本地内存(native memory)
中,JDK8中JVM堆内存结构就变成了例如以下:

Java8虚拟机内存溢出的示例分析

如果我启动VM参数加上:-XX:MaxMetaspaceSize=1m,重新运行一下上面的程序,

ConnectedtothetargetVM,address:'127.0.0.1:56433',transport:'socket'java.lang.OutOfMemoryError:MetaspaceDumpingheaptojava_pid9232.hprof...Heapdumpfilecreated[1604635bytesin0.024secs]FATALERRORinnativemethod:processingof-javaagentfailedExceptioninthread"main"DisconnectedfromthetargetVM,address:'127.0.0.1:56433',transport:'socket'Processfinishedwithexitcode1

可以发现报错信息变成了java.lang.OutOfMemoryError: Metaspace,说明元空间不够,我改成到大概4m左右才能满足启动条件。

虚拟机栈和本地方法栈栈溢出

在Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常

  • 如果虚拟机在扩展栈无法申请到足够的内存空间,则抛出OutOfMemoryError异常

    StackOverflowError比较好测试,测试代码如下:

packagecom.example.demo.jvm;/***@Author:WangChong*@Date:2019/9/2219:09*@Version:V1.0*/publicclassStackOverflowTest{/***栈大小*/privateintstackLength=1;/***递归压栈*/publicvoidstackLeak(){stackLength++;stackLeak();}publicstaticvoidmain(String[]args){StackOverflowTeststackOverflowTest=newStackOverflowTest();try{stackOverflowTest.stackLeak();}catch(Throwablee){System.out.println("stacklengthis:"+stackOverflowTest.stackLength);throwe;}}}

运行结果如下:

Exceptioninthread"main"stacklengthis:20739java.lang.StackOverflowErroratcom.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)atcom.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)

在VM参数-Xss参数未设置的情况下,该线程的内存支持的栈深度为20739,该测试结果与机器的内存大小有关,不过上面的第二点如何测试呢?正常来说如果是单线程,则难以测试内存泄露的情况,那么多线程呢?我们看一下以下测试代码:

packagecom.example.demo.jvm;/***@Author:WangChong*@Date:2019/9/2219:09*@Version:V1.0*/publicclassStackOOMTestimplementsRunnable{/***栈大小*/privateintstackLength=1;/***递归压栈*/publicvoidstackLeak(){stackLength++;stackLeak();}publicstaticvoidmain(String[]args){while(true){StackOOMTeststackOverflowTest=newStackOOMTest();newThread(stackOverflowTest).start();}}@Overridepublicvoidrun(){stackLeak();}}

如果系统不假死的情况下,会出现Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread

运行时常量池溢出
  • 字符型常量池溢出,在JAVA8中也是堆溢出,测试代码如下:

packagecom.example.demo.jvm;importjava.util.ArrayList;importjava.util.List;/***@Author:WangChong*@Date:2019/9/2219:44*@Version:V1.0*/publicclassRuntimePoolOOMTest{publicstaticvoidmain(String[]args){List<String>list=newArrayList<>();inti=0;while(true){list.add(String.valueOf(i).intern());}}}

结果如下:

Exceptioninthread"main"java.lang.OutOfMemoryError:Javaheapspaceatjava.util.Arrays.copyOf(Arrays.java:3210)atjava.util.Arrays.copyOf(Arrays.java:3181)atjava.util.ArrayList.grow(ArrayList.java:261)atjava.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)atjava.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)atjava.util.ArrayList.add(ArrayList.java:458)atcom.example.demo.jvm.RuntimePoolOOMTest.main(RuntimePoolOOMTest.java:17)DisconnectedfromthetargetVM,address:'127.0.0.1:50253',transport:'socket'

证明字符常量池已经在Java8中是在堆中分配的。

方法区溢出

在Java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变;Java8仍然保留方法区的概念,只不过实现方式不同。取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中
测试代码如下,为快速看出结果,请加入VM参数-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=10m:

packagecom.example.demo.jvm;importorg.springframework.cglib.proxy.Enhancer;importorg.springframework.cglib.proxy.MethodInterceptor;/***@Author:WangChong*@Date:2019/9/2219:56*@Version:V1.0*/publicclassMethodAreaOOMTest{publicstaticvoidmain(String[]args){while(true){Enhancerenhancer=newEnhancer();enhancer.setSuperclass(OOMObject.class);enhancer.setUseCache(false);enhancer.setCallback((MethodInterceptor)(o,method,objects,methodProxy)->methodProxy.invokeSuper(o,objects));enhancer.create();}}staticclassOOMObject{}}

运行结果如下:

java.lang.OutOfMemoryError:MetaspaceDumpingheaptojava_pid8816.hprof...Heapdumpfilecreated[6445908bytesin0.039secs]Exceptioninthread"main"org.springframework.cglib.core.CodeGenerationException:java.lang.reflect.InvocationTargetException-->nullatorg.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)atorg.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:492)atorg.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)atorg.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)atorg.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)atorg.springframework.cglib.proxy.Enhancer.create(Enhancer.java:305)atcom.example.demo.jvm.MethodAreaOOMTest.main(MethodAreaOOMTest.java:19)Causedby:java.lang.reflect.InvocationTargetExceptionatsun.reflect.GeneratedMethodAccessor1.invoke(UnknownSource)atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)atjava.lang.reflect.Method.invoke(Method.java:498)atorg.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)atorg.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)...6moreCausedby:java.lang.OutOfMemoryError:Metaspaceatjava.lang.ClassLoader.defineClass1(NativeMethod)atjava.lang.ClassLoader.defineClass(ClassLoader.java:763)...11moreProcessfinishedwithexitcode1

元空间内存报错,证明方法区的溢出与元空间相关。

总结如下:

  • 正常JVM调优都是针对堆内存和栈内存、元空间的参数做相应的改变

  • 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:

  • -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

  • -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

  • 字符串池常量池在每个VM中只有一份,存放的是字符串常量的引用值,存放在堆中

 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:Java8虚拟机内存溢出的示例分析的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:java正则表达式有哪些下一篇:

12 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18