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)扩展堆内存大小,或者从代码角度去优化

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • java内存溢出示例(堆溢出、栈溢出)

    堆溢出: 复制代码 代码如下: /** * @author LXA * 堆溢出 */ public class Heap { public static void main(String[] args) { ArrayList list=new ArrayList(); while(true) { list.add(new Heap()); } } } 报错: java.lang.OutOfMemoryError: Java heap space 栈溢出: 复制代码 代码如下: /** * @a

  • Java 堆内存溢出原因分析

    前言 任何使用过基于 Java 的企业级后端应用的软件开发者都会遇到过这种低劣.奇怪的报错,这些报错来自于用户或是测试工程师: java.lang.OutOfMemoryError:Java heap space. 为了弄清楚问题,我们必须返回到算法复杂性的计算机科学基础,尤其是"空间"复杂性.如果我们回忆,每一个应用都有一个最坏情况特征.具体来说,在存储维度方面,超过推荐的存储将会被分配到应用程序上,这是不可预测但尖锐的问题.这导致了堆内存的过度使用,因此出现了"内存不够&

  • 完美解决java读取大文件内存溢出的问题

    1. 传统方式:在内存中读取文件内容 读取文件行的标准方式是在内存中读取,Guava 和Apache Commons IO都提供了如下所示快速读取文件行的方法: Files.readLines(new File(path), Charsets.UTF_8); FileUtils.readLines(new File(path)); 实际上是使用BufferedReader或者其子类LineNumberReader来读取的. 传统方式的问题: 是文件的所有行都被存放在内存中,当文件足够大时很快就会

  • JAVA内存溢出解决方案图解

    这篇文章主要介绍了JAVA内存溢出解决方案图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 1.在apache-tomcat-7.0.70\bin\catalina.bat(Linux 系统则在catalina.sh) 文件下的 echo Using CATALINA_BASE: "%CATALINA_BASE%" 上面插入以下代码 set JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=256

  • java内存泄漏与内存溢出关系解析

    这篇文章主要介绍了java内存泄漏与内存溢出关系解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory: 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. memory leak会最终会导致out of memory!

  • Java堆内存又溢出了!教你一招必杀技(推荐)

    JAVA堆内存管理是影响性能主要因素之一. 堆内存溢出是JAVA项目非常常见的故障,在解决该问题之前,必须先了解下JAVA堆内存是怎么工作的. 先看下JAVA堆内存是如何划分的,如图: 1.JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation).老年代(Old Generation),非堆内存就一个永久代(Permanent Generation). 2.年轻代又分为Eden和Survivor区.Survivor区由FromSpace和ToSpace组成.Eden

  • 编写Java代码制造一个内存溢出的情况

    这将会是一篇比较邪恶的文章,当你想在某个人的生活中制造悲剧时你可能会去google搜索它.在Java的世界里,内存溢出仅仅只是你在这种情况下可能会引入的一种bug.你的受害者会在办公室里度过几天甚至是几周的不眠之夜. 在这篇文章中我将会介绍两种溢出方式,它们都是比较容易理解和重现的.并且它们都是来源现实项目的案例研究,但是为了让你清晰地掌握,我把它们简化了. 不过放心,在我们遇到和解决了很过溢出bug之后,类似的案例将会比你想象得更加普遍. 先来一个进入状态的,在使用HashSet/HashMa

  • 解决Java导入excel大量数据出现内存溢出的问题

    问题:系统要求导入40万条excel数据,采用poi方式,服务器出现内存溢出情况. 解决方法:由于HSSFWorkbook workbook = new HSSFWorkbook(path)一次性将excel load到内存中导致内存不够. 故采用读取csv格式.由于csv的数据以x1,x2,x3形成,类似读取txt文档. private BufferedReader bReader; /** * 执行文件入口 */ public void execute() { try { if(!path.

  • Java内存溢出案例模拟和原理分析过程

    在JVM虚拟机规范中,Java虚拟机运行时数据区域除了程序计数器(Program Counter Register)外都有可能出现OutOfMemoryError的情况,使用Hotspot虚拟机简单的模拟堆栈内存溢出的场景,方便快速定位是什么区域的内存溢出. 堆 通过VM参数设置Java堆的大小,避免堆可扩展内存(设定-Xms和Xmx一样可避免堆自动扩展): 通过设定-XX:+HeapDumpOnOutOf-MemoryError可以让虚拟机在出现内存溢出异常的时候Dump出当前的内存堆转储快照

  • JAVA 内存溢出案例汇总

    写在前面 作为程序员,多多少少都会遇到一些内存溢出的场景,如果你还没遇到,说明你工作的年限可能比较短,或者你根本就是个假程序员!哈哈,开个玩笑.今天,我们就以Java代码的方式来列举几个典型的内存溢出案例,希望大家在日常工作中,尽量避免写这些low水平的代码. 定义主类结构 首先,我们创建一个名称为BlowUpJVM的类,之后所有的案例实验都是基于这个类进行.如下所示. public class BlowUpJVM { } 栈深度溢出 public static void testStackOv

  • 以Java代码的方式总结几个典型的内存溢出案例

    一.图示 我们先来看看今天要介绍哪些内存溢出案例,这里总结了一张图,如下所示. 二.定义主类结构 首先,我们创建一个名称为BlowUpJVM的类,之后所有的案例实验都是基于这个类进行.如下所示. public class BlowUpJVM { } 三.栈深度溢出 public static void testStackOverFlow(){ BlowUpJVM.testStackOverFlow(); } 栈不断递归,而且没有处理,所以虚拟机栈就不断深入不断深入,栈深度就这样溢出了. 四.永久

  • 详解Java内存溢出的几种情况

    JVM(Java虚拟机)是一个抽象的计算模型.就如同一台真实的机器,它有自己的指令集和执行引擎,可以在运行时操控内存区域.目的是为构建在其上运行的应用程序提供一个运行环境.JVM可以解读指令代码并与底层进行交互:包括操作系统平台和执行指令并管理资源的硬件体系结构. 1. 前言 JVM提供的内存管理机制和自动垃圾回收极大的解放了用户对于内存的管理,大部分情况下不会出现内存泄漏和内存溢出问题.但是基本不会出现并不等于不会出现,所以掌握Java内存模型原理和学会分析出现的内存溢出或内存泄漏,对于使用J

  • Java 内存溢出的原因和解决方法

    你是否遇到过Java应用程序卡顿或突然崩溃的情况?您可能遇到过Java内存泄漏.在本文中,我们将深入研究Java内存泄漏的确切原因,并推荐一些最好的工具来防止内存泄漏发生. 什么是JAVA内存泄漏? 简单地说,Java内存泄漏是指对象不再被应用程序使用,而是在工作内存中处于活动状态. 在Java和大多数其他编程语言中,垃圾收集器的任务是删除不再被应用程序引用的对象.如果不选中,这些对象将继续消耗系统内存,并最终导致崩溃.有时java内存泄漏崩溃不会输出错误,但通常错误会以java.lang.Ou

  • Java内存溢出和内存泄露

    虽然jvm可以通过GC自动回收无用的内存,但是代码不好的话仍然存在内存溢出的风险. 一.为什么要了解内存泄露和内存溢出? 1.内存泄露一般是代码设计存在缺陷导致的,通过了解内存泄露的场景,可以避免不必要的内存溢出和提高自己的代码编写水平: 2.通过了解内存溢出的几种常见情况,可以在出现内存溢出的时候快速的定位问题的位置,缩短解决故障的时间.  二.基本概念  理解这两个概念非常重要. 内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存.即被分配的对象可达但已无

  • 基于Java内存溢出的解决方法详解

    一.内存溢出类型1.java.lang.OutOfMemoryError: PermGen spaceJVM管理两种类型的内存,堆和非堆.堆是给开发人员用的上面说的就是,是在JVM启动时创建:非堆是留给JVM自己用的,用来存放类的信息的.它和堆不同,运行期内GC不会释放空间.如果web app用了大量的第三方jar或者应用有太多的class文件而恰好MaxPermSize设置较小,超出了也会导致这块内存的占用过多造成溢出,或者tomcat热部署时侯不会清理前面加载的环境,只会将context更改

  • Java内存溢出实现原因及解决方案

    1.JVM Heap(堆)溢出:java.lang.OutOfMemoryError: Java heap space JVM在启动的时候会自动设置JVM Heap的值, 可以利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置.Heap的大小是Young Generation 和Tenured Generaion 之和.在JVM中如果98%的时间是用于GC,且可用的Heap size 不足2%的时候将抛出此异常信息. 解决方法:手动设置JVM Heap(堆)的大小. Java堆用于储存

  • Java内存模型(JMM)及happens-before原理

    我们知道java程序是运行在JVM中的,而JVM就是构建在内存上的虚拟机,那么内存模型JMM是做什么用的呢? 我们考虑一个简单的赋值问题: int a=100; JMM考虑的就是什么情况下读取变量a的线程可以看到值为100.看起来这是一个很简单的问题,赋值之后不就可以读到值了吗? 但是上面的只是我们源码的编写顺序,当把源码编译之后,在编译器中生成的指令的顺序跟源码的顺序并不是完全一致的.处理器可能采用乱序或者并行的方式来执行指令(在JVM中只要程序的最终执行结果和在严格串行环境中执行结果一致,这

随机推荐