Java8虚拟机(JVM)内存溢出实战
前言
相信很多JAVA中高级的同学在面试的时候会经常碰到一个面试题
你是如何在工作中对JVM调优和排查定位问题的?
事实上,如果用户量不大的情况下,在你的代码还算正常的情况下,在工作中除非真正碰到与JVM相关的问题是少之又少,就算碰到了也是由公司的一些大牛去排查解决,那么我们又如何积累这方面的经验呢?下面由冲锅带大家一起来实践JVM的调优吧
注意我们平常所说的JVM调优一般指Java堆,Java虚拟机栈参数调优
Java堆溢出
先来一段代码示例,注意笔者用的是IDEA工具,需要配置一下VM options 为-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError,如果不清楚的百度一下如何配置idea的JVM运行参数
package com.example.demo.jvm;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 9:37
* @Version: V1.0
*/
public class HeapOutMemoryTest {
static class ChongGuo {
}
public static void main(String[] args) {
List<ChongGuo> chongGuos = new ArrayList<>();
while (true) {
chongGuos.add(new ChongGuo());
}
}
}
运行结果如下:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9352.hprof ...
Heap dump file created [28701160 bytes in 0.122 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at com.example.demo.jvm.HeapOutMemoryTest.main(HeapOutMemoryTest.java:18)
Disconnected from the target VM, 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为啥会报堆内存溢出呢?

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

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

如果我启动VM参数加上:-XX:MaxMetaspaceSize=1m,重新运行一下上面的程序,
Connected to the target VM, address: '127.0.0.1:56433', transport: 'socket'
java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid9232.hprof ...
Heap dump file created [1604635 bytes in 0.024 secs]
FATAL ERROR in native method: processing of -javaagent failed
Exception in thread "main" Disconnected from the target VM, address: '127.0.0.1:56433', transport: 'socket'
Process finished with exit code 1
可以发现报错信息变成了java.lang.OutOfMemoryError: Metaspace,说明元空间不够,我改成到大概4m左右才能满足启动条件。
虚拟机栈和本地方法栈栈溢出
在Java虚拟机规范中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
- 如果虚拟机在扩展栈无法申请到足够的内存空间,则抛出OutOfMemoryError异常
StackOverflowError比较好测试,测试代码如下:
package com.example.demo.jvm;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 19:09
* @Version: V1.0
*/
public class StackOverflowTest {
/**
* 栈大小
*/
private int stackLength = 1;
/**
* 递归压栈
*/
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
StackOverflowTest stackOverflowTest = new StackOverflowTest();
try {
stackOverflowTest.stackLeak();
} catch (Throwable e) {
System.out.println("stack length is :" + stackOverflowTest.stackLength);
throw e;
}
}
}
运行结果如下:
Exception in thread "main" stack length is :20739
java.lang.StackOverflowError
at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)
at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)
在VM参数-Xss参数未设置的情况下,该线程的内存支持的栈深度为20739,该测试结果与机器的内存大小有关,不过上面的第二点如何测试呢?正常来说如果是单线程,则难以测试内存泄露的情况,那么多线程呢?我们看一下以下测试代码:
package com.example.demo.jvm;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 19:09
* @Version: V1.0
*/
public class StackOOMTest implements Runnable{
/**
* 栈大小
*/
private int stackLength = 1;
/**
* 递归压栈
*/
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
while (true){
StackOOMTest stackOverflowTest = new StackOOMTest();
new Thread(stackOverflowTest).start();
}
}
@Override
public void run() {
stackLeak();
}
}
如果系统不假死的情况下,会出现Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread
运行时常量池溢出
- 字符型常量池溢出,在JAVA8中也是堆溢出,测试代码如下:
package com.example.demo.jvm;
import java.util.ArrayList;
import java.util.List;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 19:44
* @Version: V1.0
*/
public class RuntimePoolOOMTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i).intern());
}
}
}
结果如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.add(ArrayList.java:458)
at com.example.demo.jvm.RuntimePoolOOMTest.main(RuntimePoolOOMTest.java:17)
Disconnected from the target VM, address: '127.0.0.1:50253', transport: 'socket'
证明字符常量池已经在Java8中是在堆中分配的。
方法区溢出
在Java7之前,方法区位于永久代(PermGen),永久代和堆相互隔离,永久代的大小在启动JVM时可以设置一个固定值,不可变;Java8仍然保留方法区的概念,只不过实现方式不同。取消永久代,方法存放于元空间(Metaspace),元空间仍然与堆不相连,但与堆共享物理内存,逻辑上可认为在堆中
测试代码如下,为快速看出结果,请加入VM参数-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=10m:
package com.example.demo.jvm;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
/**
* @Author: Wang Chong
* @Date: 2019/9/22 19:56
* @Version: V1.0
*/
public class MethodAreaOOMTest {
public static void main(String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o,
objects));
enhancer.create();
}
}
static class OOMObject {
}
}
运行结果如下:
java.lang.OutOfMemoryError: Metaspace
Dumping heap to java_pid8816.hprof ...
Heap dump file created [6445908 bytes in 0.039 secs]
Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:492)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:305)
at com.example.demo.jvm.MethodAreaOOMTest.main(MethodAreaOOMTest.java:19)
Caused by: java.lang.reflect.InvocationTargetException
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459)
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336)
... 6 more
Caused by: java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
... 11 more
Process finished with exit code 1
元空间内存报错,证明方法区的溢出与元空间相关。
总结如下:
- 正常JVM调优都是针对堆内存和栈内存、元空间的参数做相应的改变
- 元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:
- -XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
- -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。
- 字符串池常量池在每个VM中只有一份,存放的是字符串常量的引用值,存放在堆中
有更多的文章,请关注查看,更有面试宝典相送

Java8虚拟机(JVM)内存溢出实战的更多相关文章
- JVM 内存溢出 实战 (史上最全)
文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...
- jvm内存溢出分析
概述 jvm中除了程序计数器,其他的区域都有可能会发生内存溢出 内存溢出是什么? 当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出 内存溢出和 ...
- 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码
程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...
- Tomcat中JVM内存溢出及合理配置及maxThreads如何配置(转)
来源:http://www.tot.name/html/20150530/20150530102930.htm Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个Java虚 ...
- Tomcat中JVM内存溢出及合理配置
Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个Java虚拟机.Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识 ...
- JVM内存溢出及合理配置
Tomcat本身不能直接在计算机上运行,需要依赖于硬件基础之上的操作系统和一个Java虚拟机.Tomcat的内存溢出本质就是JVM内存溢出,所以在本文开始时,应该先对Java JVM有关内存方面的知识 ...
- jvm内存溢出问题
Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出 J ...
- 定位JVM内存溢出问题思路总结
JVM的内存溢出问题,是个常见而有时候有非常难以定位的问题.定位内存溢出问题常见方法有很多,但是其实很多情况下可供你选择的有效手段非常有限.很多方法在一些实际场景下没有实用价值.这里总结下我的一些定位 ...
- jvm 内存溢出问题排查方法
如果你做TCP通讯或者map集合操作,并发处理等功能时,很容易出现 Java 内存溢出的问题.本篇文章,带领大家深入jvm,分析并找出jvm内存溢出的代码. jvm中除了程序计数器,其他的区域都有可能 ...
- 5种JVM垃圾收集器特点和8种JVM内存溢出原因
先来看看5种JVM垃圾收集器特点 一.常见垃圾收集器 现在常见的垃圾收集器有如下几种: 新生代收集器: Serial ParNew Parallel Scavenge 老年代收集器: Serial O ...
随机推荐
- laravel为模型中所有查询统一添加WHERE条件
在使用laravel开发web系统的过程,需要在model处为该模型统一添加一个条件或者多个条件,研究了一个laravel的模型类,发现model中有个方法是构建查询的,方法如下: /** * Reg ...
- mybatis基础简介
1.mybatis的加载过程? 程序首先加载mybatis-config.xml文件,根据配置文件创建SQLSessionFactory对象: 然后通过SQLSessionFactory对象创建 ...
- 100天搞定机器学习|day45-53 推荐一本豆瓣评分9.3的书:《Python数据科学手册》
<Python数据科学手册>共五章,每章介绍一到两个Python数据科学中的重点工具包.首先从IPython和Jupyter开始,它们提供了数据科学家需要的计算环境:第2章讲解能提供nda ...
- 玩转 SpringBoot 2 快速搭建 | Spring Tool Suite篇
Spring Tool Suite (STS) 工具介绍 我个人比较推荐使用 Spring Tool Suite(STS),之所以推荐使用 Spring Tool Suite(STS) ,是因为它是 ...
- Selenium3 + Python3自动化测试系列十——调用JavaScript代码
调用JavaScript代码 一.调用JavaScript代码方法 Selenium在对浏览器操作时会有自动化代码中不稳定的部分,经常出错的部分,可以将这部分对网页元素进行操作的代码换成对应的Java ...
- .NET平台下,钉钉微应用开发之:获取userid
工作需求,开发钉钉微应用和小程序,之前有接触过支付宝小程序和生活号的开发,流程没有很大的差别,这里记录下我用ASP.NET MVC实现钉钉微应用的开发,并实现获取用户的userid.小弟我技术有限,本 ...
- Java线程之线程简介
Java线程之线程简介 一.何谓线程 明为跟踪处理流程,实为跟踪线程 阅读程序时,我们会按处理流程来阅读. 首先执行这条语句 ↓ 然后执行这条语句 ↓ 接着再执行这条语句…… 我们就是按照上面这样的流 ...
- 深度解密Go语言之 scheduler
目录 前置知识 os scheduler 线程切换 函数调用过程分析 goroutine 是怎么工作的 什么是 goroutine goroutine 和 thread 的区别 M:N 模型 什么是 ...
- Nginx 实用配置
1 防盗链 相关配置: valid_referers location ~* \.(gif|jpg|png)$ { # 只允许 192.168.0.1 请求资源 valid_referers none ...
- 【selenium】- webdriver常见元素定位(上)
本文由小编根据慕课网视频亲自整理,转载请注明出处和作者. 1. 元素的定位 2.By.id 打开Firefox,打开百度首页,右键点击选择“使用Firebug”查看元素. 点击红框内的按钮,将鼠标指针 ...