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++ ...
随机推荐
- goland 注册
注意:本教程已过期 请使用其他人教程激活最新版 https://www.789zhao.com/blog/JC08EIFBS9TM.html https://shimo.im/docs/dKYC ...
- Java进阶专题(十五) 从电商系统角度研究多线程(下)
前言 本章节继上章节继续梳理:线程相关的基础理论和工具.多线程程序下的性能调优和电商场景下多线程的使用. 多线程J·U·C ThreadLocal 概念 ThreadLocal类并不是用来解决 ...
- pc和移动与ipad自适应布局的相关问题和解决
一.通过CSS检测本机设备屏幕大小分配样式 1.最小尺寸分辨率1024*768(传统17寸显示器),则可以采用940px.960px.或者常用的980px作为最小宽度. ---- 在可视区域的宽度小于 ...
- Java9系列第九篇-对HTTP2协议的支持与非阻塞HTTP-API
在HTTP/1.1 发布了16 年之后,IETF在2015年终于通过了HTTP/2 协议.HTTP/2协议旨在降低延迟,满足当今时代对于信息响应时间的要求.在这篇文章中,我会简要的对HTTP/2协议进 ...
- B. Once Again... 解析(思維、DP、LIS、矩陣冪)
Codeforce 582 B. Once Again... 解析(思維.DP.LIS.矩陣冪) 今天我們來看看CF582B 題目連結 題目 給你一個長度為\(n\)的數列\(a\),求\(a\)循環 ...
- vue自定义指令长按事件
Vue.directive('longpress', { bind: function (el, binding, vNode) { // Make sure expressi ...
- vue路由传参及组件传参和组件方法调用
VUE路由和组件传参 第一种vue自带的路由传参的三种基本方式 1.通过name :id传参 子组件通过$route.name接收参数 { path: '/particulars/:id', name ...
- LuoguP3615 如厕计划
题面 现有两个厕所,一个女士专用,一个通用,给出\(2*n\)个排成一列的人的性别 每人如厕需要一分钟,假如女厕是空的,女生中最靠前的可以直接进入. 需要通过调换顺序使得所有人都上完厕所最后的时间为n ...
- yython爬虫基础知识入门
Python爬虫 关注公众号"轻松学编程"了解更多. 大纲: 1.获取响应 urllib(python3)/urllib2-urllib(python2) requests(url ...
- P1098 字符串的展开
P1098 字符串的展开 刷新三观的模拟题 题意描述 太长了自己去看吧. 算法分析 模拟题分析你*呀! 写这篇题解的唯一原因是:三目运算符用的好的话,可以让百行大模拟变成30行水题. 代码实现 #in ...