Java中常见内存溢出模拟及错误分析
在JVM虚拟机规范中,Java虚拟机运行时数据区域除了程序计数器(Program Counter Register)外都有可能出现OutOfMemoryError
的情况,使用Hotspot虚拟机简单的模拟堆栈内存溢出的场景,方便快速定位是什么区域的内存溢出。
堆
通过VM参数设置Java堆的大小,避免堆可扩展内存(设定-Xms和Xmx一样可避免堆自动扩展);
通过设定-XX:+HeapDumpOnOutOf-MemoryError
可以让虚拟机在出现内存溢出异常的时候Dump出当前的内存堆转储快照。
/**
* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* @author Vicente
* @version 1.0
* @date 2020/4/5 10:28
*/
public class TestHeapOOM {
public static void main(String[] args) {
List<TestHeapOOM> list = new ArrayList<TestHeapOOM>();
while (true) {
list.add(new TestHeapOOM());
}
}
}
设置启动参数:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
运行结果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3676.hprof ...
Heap dump file created [28279988 bytes in 0.099 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:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at com.oom.TestHeapOOM.main(TestHeapOOM.java:19)
使用IDEA和Eclipse都可以设置启动时的参数。
堆转储快照文件一般生成后位于你的work space,拿到文件后要对快照文件进行分析,可以采用不同的工具来帮助我们分析,这里推荐两种:
- jhat
在IDEA或者Eclispe的终端控制台直接输入命令
jhat java_pid13232.hprof
当dump的文件过大时需要设置jhat参数:jhat -J-Xmx2048m java_pid13232.hprof,默认-Xmx为1024
对堆快照进行分析
Reading from java_pid13232.hprof...
Dump file created Sun Apr 05 10:54:06 CST 2020
Snapshot read, resolving...
Resolving 818818 objects...
Chasing references, expect 163
dots................................................................................................................
Eliminating duplicate
references..........................................................................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
通过访问http://localhost:7000即可查看分析结果,对象内存分配的大小等信息。
- mat
工具下载地址:https://eclipse.org/mat/downloads.php,选择需要下载的版本,windows版本下载后是一个压缩包,直接解压运行即可。
打开需要分析的堆转储文件,分析后会展示一个概要预览
Leak Suspects » Leaks » Problem Suspect 在这里面可以看到对象的个数,对象占用大小等信息,这里包含两个重要信息
Generally speaking, shallow heap of an object is its size in the heap and retained size of the same object is the amount of heap memory that will be freed when the object is garbage collected.
具体解释可以参考:https://help.eclipse.org/2020-03/index.jsp
- Shallow Heap:某个对象自身大小,不包含其引用对象的大小;
- Retained Heap:某个对象在发生GC回收时,如果被释放,其释放内存的大小,这就要包含其引用的对象占用堆内存的大小。
这里具体使用就不再描述,可以参考官方文档。
栈
Java运行时数据区包含虚拟机栈和本地方法栈,在Hotspot虚拟机实现中对于本地方法栈的参数(-Xoss)设定并无实际效果,只通过-Xss参数来模拟栈的内存溢出。《Java虚拟机规范》中指出:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,抛出StackOverflowError异常
- 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,抛出OutOfMemoryError异常
设置运行参数VM Args:-Xss128k
/**
* VM Args:-Xss128k
* @author Vicente
* @version 1.0
* @date 2020/4/5 12:39
*/
public class TestStackOverflow {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) throws Throwable {
TestStackOverflow overflow = new TestStackOverflow();
try {
overflow.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + overflow.stackLength);
throw e;
}
}
}
运行结果:
Exception in thread "main" java.lang.StackOverflowError
at com.oom.TestStackOverflow.stackLeak(TestStackOverflow.java:17)
at com.oom.TestStackOverflow.stackLeak(TestStackOverflow.java:17)
//省略...
stack length:36984
//省略...
根据操作系统的不同和Java虚拟机版本的不同,栈容量的最小值也会有所不同,改变栈容量的大小或者栈帧过大时都会导致StackOverflowError异常。
Hotspot虚拟机不支持扩展栈内存,除非在创建线程申请内存就不足会导致OutOfMemoryError异常,其他情况都是在运行时因为栈容量无法容纳新的栈帧而导致StackOverflowError异常。
不同的虚拟机实现有着不同的细节处理,其他虚拟机实现如果是可扩容栈空间,栈容量不足时会抛出OutOfMemoryError异常,当遇到OutOfMemoryError时要先判断是栈空间还是堆内存的异常。
方法区
在JDK6之前的Hotspot虚拟机中方法区被设置在永久代中,运行时常量池也属于方法区的一部分,可以通过-XX:PermSize和-XX:MaxPermSize限制永久代的大小,在JDK7开始逐步的去永久代,到了JDK8就开始使用元空间(meta-space)来实现方法区,保存程序运行时的数据。
使用JDK6,设定永久代参数-XX:PermSize=2M -XX:MaxPermSize=2M
public class TestConstantPoolOOM {
public static void main(String[] args) throws Throwable {
//使用Set保持着常量池引用,避免Full GC回收常量池行为
Set<String> set = new HashSet<String>();
// 在short范围内足以让6MB的PermSize产生OOM了
short i = 0;
while (true) {
set.add(String.valueOf(i++).intern());
//String.valueOf(i++).intern();
}
}
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.oom.TestConstantPoolOOM.main(TestConstantPoolOOM.java from InputFileObject:21)
可以看到OutOfMemoryError后面指明了内存溢出的位置PermGen space
;在JDK8中使用-XX:MaxMeta-spaceSize参数把方法区容量同样限制,也不会出现异常,因为从JDK7开始常量池已经从永久代移到了Java堆的位置,此时限制Java堆的大小便会抛出异常,定位异常的位置。
设置上面代码的运行参数:-Xms6m -Xmx6m
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:704)
at java.util.HashMap.putVal(HashMap.java:663)
at java.util.HashMap.put(HashMap.java:612)
at java.util.HashSet.add(HashSet.java:220)
at com.oom.TestConstantPoolOOM.main(TestConstantPoolOOM.java:20)
可以看到,程序运行抛出java.lang.OutOfMemoryError异常。
方法区的异常也是一种常见异常,一个类被垃圾回收期回收的条件是比较苛刻的,在经常运行时生成大量动态类的应用场景里,就应该特别关注这些类的回收状况,比如传统项目中大量的jsp文件,jsp会被编译成Java类,容易抛出方法区异常,在JDK8以后永久代不再存在,元空间的出现使得,正常创建对象的过程很难出现方法区的内存溢出,不过Hotspot也提供了一些参数设置保护元空间。
- XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。
- XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集,收集器会对该值进行调整:如果释放大量的空间,适当降低该值;如果释放了很少的空间,在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值。
- XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。
- XX:Max-MetaspaceFreeRatio,用于控制最大的元空间剩余容量的百分比。
直接内存
直接内存可以理解为堆外内存,通过参数-XX:MaxDirectMemorySize来设定,如果不设置默认与Java堆内存的最大值相同。NIO会使用直接内存,使用Unsafe类来模拟直接内存溢出的情况。
public class TestDirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
at com.oom.TestDirectMemoryOOM.main(TestDirectMemoryOOM.java:24)
当直接内存溢出时,Dump文件并没有太多错误信息,要考虑是否间接使用NIO了。
总结
- 当Java虚拟机抛出OutOfMemoryError错误时,判断是Java内存哪一块区域抛出的错误,定位堆,栈
- 使用工具分析堆转储快照,判断是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)
- 当发生内存泄漏可以通过工具查看GC Roots引用链,分析为什么垃圾回收器无法回收,判定对象创建的位置,是否有可回收对象
- 如果是内存溢出,根据硬件性能是否可以(使用-Xms和-Xmx)扩展堆内存大小,或者从代码角度去优化
参考资料:
《深入理解Java虚拟机 第三版》
https://help.eclipse.org/2020-03/index.jsp
Java中常见内存溢出模拟及错误分析的更多相关文章
- java中的内存溢出和内存泄漏
内存溢出:对于整个应用程序来说,JVM内存空间,已经没有多余的空间分配给新的对象.所以就发生内存溢出. 内存泄露:在应用的整个生命周期内,某个对象一直存在,且对象占用的内存空间越来越大,最终导致JVM ...
- Java中OutOfMemoryError(内存溢出)的三种情况及解决办法
转载自:http://blog.sina.com.cn/s/blog_701c951f0100n1sp.html 相信有一定java开发经验的人或多或少都会遇到OutOfMemoryError的问题, ...
- java中之内存溢出说明
java语句是编译型和解释型语言,选通过编译命令javac 把java文件编译为.class字节码文件,然后通过java虚拟机(JVM)加载class文件到内存运行. 而java虚拟机在运行程序时有自 ...
- java中出现内存溢出的几种情况
情况一:java.lang.OutOfMemoryError: Java heap space 原因:java堆内存不足,可能是真的不足,也可能是程序中有死循环 方案:1.调整JVM参数-Xms204 ...
- Java中OutOfMemoryError(内存溢出)的情况及解决办法
java.lang.OutOfMemoryError: Java heap space // TODO Auto-generated method stub Vector v = new Vector ...
- Java架构师中的内存溢出和内存泄露是什么?实际操作案例!
JAVA中的内存溢出和内存泄露分别是什么,有什么联系和区别,让我们来看一看. 01 内存泄漏 & 内存溢出 1.内存泄漏(memory leak ) 申请了内存用完了不释放,比如一共有 102 ...
- java虚拟机涉及内存溢出
Java语言写的代码是.java文件,它会被特定程序编译(javac.exe,它会被Eclipse之类的IDE调用)成字节码(bytecode),字节码不能直接在CPU上运行,需要另一个程序读取并执行 ...
- java常见内存溢出(OOM)
jvm内存区域 程序计数器一块很小的内存空间,作用是当前线程所执行的字节码的行号指示器. java栈与程序计数器一样,java栈(虚拟机栈)也是线程私有的,其生命周期与线程相同.通常存放基本数据类型, ...
- java中常见的内存泄露的例子
JAVA 中的内存泄露 Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露. Java中的内存泄露与C++中的表现有所不同. 在C++ ...
随机推荐
- matplotlib条形图
三个班级平均分 import matplotlib.pyplot as plt import matplotlib as mpl classes = ['class1','class2','class ...
- java 实体对象转Map公共类
java 实体对象转Map公共类 package org.kxtkx.portal.utils; import java.lang.reflect.Field; import java.util.Ha ...
- fish_redux使用详解---看完就会用!
说句心里话,这篇文章,来来回回修改了很多次,如果认真看完这篇文章,还不会写fish_redux,请在评论里喷我. 前言 来学学难搞的fish_redux框架吧,这个框架,官方的文档真是一言难尽,比fl ...
- 【转】Python3 如何优雅地使用正则表达式(完整版)
转载自鱼c论坛 : https://fishc.com.cn/thread-57073-1-1.html 注:本文翻译自 Regular Expression HOWTO,小甲鱼童鞋对此做了一些注释 ...
- python- pyqt5 一个存疑问题
首先 我的问题是 自定义的方法中 无法给窗体中增加控件 我们直接看例子 这是一个图书管理系统的窗口 我们给他加上菜单(menuBar) 加上工具栏(QAction) 程序变成了这样 这个界面是这样的( ...
- 论文解读《Plug-and-Play Priors for Model Based Reconstruction》
这篇论文主要概述了model-baesd的方法在解决图像恢复的逆问题的很好的效果,降噪问题其实就是前向模型的H是一个恒等算子,将state-of-the-art的降噪算法(先验模型)和相对应的逆问题的 ...
- 全球最火的程序员学习路线!没有之一!3天就在Github收获了接近1w点赞
大家好,我是G哥,目前人在荆州办事,但是干货还是要安排上! 国外有一个爆火的开发人员学习路线,目前已经在 Github收获了 131 k+ star,Star 数量在 Github 所有仓库中排名第 ...
- 配置交换机之间直连链路聚合-LACP模式
组网图形 LACP模式链路聚合简介 以太网链路聚合是指将多条以太网物理链路捆绑在一起成为一条逻辑链路,从而实现增加链路带宽的目的.链路聚合分为手工模式和LACP模式. LACP模式需要有链路聚合控制协 ...
- DTU连接经常遇到的问题有哪些
随着物联网的不断推进,工业.环保.能源.共享等领域对于DTU设备的应用也越来越广泛,在应用过程中,DTU经常遇到哪些问题以及解决办法,下面做如下分析. 第一,DTU如何与组态软件连接? 答:二者连接的 ...
- 震惊!你还不知道SpringBoot真正的启动引导类
引言 SpringBoot项目中的启动类,一般都是XXApplication,例如「StatsApplication」,「UnionApplication」. 每个项目的启动类名称都不一样.但是它的启 ...