在Java虚拟机规范描述中,除程序计数器外,其他几个运行时区域都有可能发生OutOfMemoryError异常。接下来将对各区域分别进行分析介绍,内容包括触发各区域OutOfMemoryError异常的代码,以及对其进行排查判定的过程。还会初步涉及几个与内存相关的最基本虚拟机参数。

1、Java堆溢出

Java堆用于存储对象实例,只要不断创建对象并保证对象不会被回收,那么当对象数量到达最大堆的容量限制后就会产生堆内存溢出异常。

测试代码如下:

/**
* 探究Java堆溢出
* VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* 将堆最小值(-Xms)与堆最大值(-Xmx)参数设置为一样(20MB),可避免堆自动扩展
*
* -XX:+HeapDumpOnOutOfMemoryError :让虚拟机在出现内存溢出异常时可Dump出
* 当前的内存堆转储快照以便时候进行分析
* @author yangtf
*
*/
public class HeapOOM {
static class OOMObject{}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}

运行结果:

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid18152.hprof ...
Heap dump file created [28008150 bytes in 0.132 secs]

产生上述异常后,异常信息:java.lang.OutOfMemoryError: Java heap space 。

并且会在代码根路径下生成一个 java_pid18152.hprof 文件,此文件的生成是由于-XX:+HeapDumpOnOutOfMemoryError 配置的结果。

要解决这个区域的异常,一般是通过内存映像分析工具(如 Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认出现了内存泄漏还是内存溢出。

  内存泄漏:对象已经死了,无法通过垃圾收集器进行自动回收,通过找出泄露的代码位置和原因,才好确定解决方案

  内存溢出:内存中的对象都还必须存活着,这说明Java堆分配空间不足,检查堆设置大小(-Xmx与-Xms),检查代码是否存在对象生命周期太长、持有状态时间过长的情况

下面介绍用  Memory Analyzer (以下简称MAT ~Tool)进行分析的过程

  1)下载与安装:下载链接点击上面 Memory Analyzer。

  2)打开分析文件:在根目录下查找 java_pid18152.hprof 文件,并打开。

  3)打开后见下图:

从上图可以看到它的大部分功能。
     1. Histogram可以列出内存中的对象,对象的个数以及大小。
     2. Dominator Tree可以列出那个线程,以及线程下面的那些对象占用的空间。
     3.Top consumers通过图形列出最大的object。
     4.Leak Suspects通过MA自动分析泄漏的原因

Histogram如下图:
     Objects:类的对象的数量。
     Shallow size:就是对象本身占用内存的大小,不包含对其他对象的引用,也就是对象头加成员变量(不是成员变量的值)的总和。

Retained size:是该对象自己的shallow size,加上从该对象能直接或间接访问到对象的shallow size之和。换句话说,retained size是该对象被GC之后所能回收到内存的总和。

  可以看出Object类、Thread类、HeapOOM类占用了绝大部分空间

Dominator Tree如下图:

可以看出 Thread 线程占用了很大空间

 Top consumers如下图:
     这里显示了内存中最大的对象有哪些,他们对应的类是哪些,类加载器classloader是哪些。
     有些时候,我们在这里就可以看到代码泄露的位置。
  图中每个选项均可展开,显示详细图标信息。
  Leak Suspects如下图:
  深色区域被怀疑出现问题,深色区域占用内存量过大。
  后面的描述告诉我们线程 Java.lang.Thread 占用的大量空间,并指出 system class loader 加载的 Java.lang.Object[] 实例在内存中聚集。
  MAT通过简单的报告就分析出了问题所在。

点击上图中 【Details】

  可以看出积累了过多的 OOMObject ,选择一个左击==>List objects==>with outgoing rederences,查看Thread都应用了什么对象。

  现在看到引用的对象:

  可以看到对象是引起问题的关键,在对象上右击==>Path to GC Roots==>exclude weak reference

    从下图看出在 java.lang.Thread 中保存了 OOMObject的引用。所以可以得出结论是由于运行时持有大量的 OOMObject 引起了内存泄漏。

  以上是通过MAT分析测试程序查找内存溢出的原因。

堆溢出解决方式:

  内存泄漏:掌握内存泄露对象信息及GC Roots引用链信息,就可以比较准确地定位出泄漏代码的位置。

  内存溢出:检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过程、持有状态时间过长的情况,尝试减少程序运行期间的内存消耗。

 2、虚拟机栈和本地方法栈

  此次测试用的为HotSpot虚拟机,在此虚拟机中不区分虚拟机栈和本地方法栈,因此虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由-Xss参数设定。

  查看虚拟机版本的方式为:

  这两个栈中均有两种异常:StackOverflowError 和 OutOfMemoryError 在此分别进行介绍:

  在单线程下,无论由于栈帧太大还是虚拟机容量太小,当内存无法分配的时候,抛出的都是 StackOverflowError 异常。

  StackOverflowError 代码:

/**
* VM Args: -Xss128k
* @author yangtf
*
*/
public class JavaVMStackOF {
private int stackLength = 1; // 不断调用,不断压栈
public void stackLeak(){
// 记录调用数量
stackLength++;
stackLeak();
} public static void main(String[] args) {
JavaVMStackOF oom = new JavaVMStackOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length: " + oom.stackLength);
throw e;
}
}
}

输出结果:

  此处限制了栈内存的大小,不能满足栈请求的深度,弹出了 StackOverflowError  异常

  解决方法:增加栈深度,或尽量减少不必要的调用。

  OutOfMemoryError 异常:动态扩展申请不到足够的内存。

  此异常并不容易实现,如果在单线程下引起的为 StackOverflowError  异常,若是在多线程下虽然能引起 OutOfMemoryError 异常,但是这与栈空间是否足够大并不存在联系(此处应该是能否动态扩展申请到足够的内存,栈空间不够大,所以申请不到足够的内存)。多线程情况下,为每个线程的栈分配的内存越大,反而越容易引起内存溢出。如果是由于建减立过多线程导致内存溢出,只能通过少减少最大堆容量和减少栈容量来换取更多线程。

  此处给出代码:

/**
* VM Args:-Xss2m
* 在Windows平台的虚拟机中,Java线程映射到操作系统内核线程上,
* 代码执行时可能会导致操作系统假死
*
* 通过不断地创建运行着的线程,引起异常
* @author yangtf
*
*/
public class JavaVMStackOOM {
private void dontStop(){
while (true) {
}
} public void stackLeakByThread(){
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
} public static void main(String[] args) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}

3、运行时常量池和方法区

  运行时常量池属于方法区,所以此处一起测试。

  常量池溢出代码,由于JDK1.7开始逐步“去永久代”,所以此处测试代码应在JDK1.7以下的版本中运行:

/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
* -XX:PermSize 和 -XX:MaxPermSize 限制方法区大小,也就限制了常量池的大小
* @author yangtf
*
*/
public class RunConstantPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}

JVM内存区域异常分析的更多相关文章

  1. Java虚拟机------JVM内存区域

    JVM内存区域运行时数据区域分为两种: JVM内存区域 运行时数据区域分为两种: 线程隔离的数据区: 程序计数器 Java虚拟机栈 本地方法栈 所有线程程共享的数据区: Java堆 方法区 JVM 内 ...

  2. JVM Java 内存区域透彻分析(转)

    出处:  Java 内存区域透彻分析  Java8内存模型—永久代(PermGen)和元空间(Metaspace) 这篇文章主要介绍Java内存区域,也是作为Java虚拟机的一些最基本的知识,理解了这 ...

  3. BAT面试必问题系列:深入详解JVM 内存区域及内存溢出分析

    前言 在JVM的管控下,Java程序员不再需要管理内存的分配与释放,这和在C和C++的世界是完全不一样的.所以,在JVM的帮助下,Java程序员很少会关注内存泄露和内存溢出的问题.但是,一旦JVM发生 ...

  4. JVM基础知识(1)-JVM内存区域与内存溢出

    JVM基础知识(1)-JVM内存区域与内存溢出 0. 目录 什么是JVM 运行时数据区域 HotSpot虚拟机对象探秘 OutOfMemoryError异常 1. 什么是JVM 1.1. 什么是JVM ...

  5. JVM内存区域详解

    本文分为两部分:一是JVM内存区域的讲解:二是常见的内存溢出异常分析. 1.JVM内存区域 java虚拟机在执行java程序的过程中会把它管理的内存划分为若干个不同的数据区域,这些区域都有各自的用途, ...

  6. JVM 内存区域 (运行时数据区域)

    JVM 内存区域 (运行时数据区域) 链接:https://www.jianshu.com/p/ec479baf4d06 运行时数据区域 Java 虚拟机在执行 Java 程序的过程中会把它所管理的内 ...

  7. Java内存区域与模拟内存区域异常

    我把Java的内存区域画了一张思维导图,以及各区域的主要功能. 模拟Java堆溢出 Java堆用于存储对象实例.仅仅要不断地创建对象而且保证GC ROOTS到对象之间有可达路径避免被回收机制清除.就能 ...

  8. 谈谈JVM内存区域的划分

    我们知道,计算机CPU和内存的交互是最频繁的,内存是我们的高速缓存区,用户磁盘和CPU的交互,而CPU运转速度越来越快,磁盘远远跟不上CPU的读写速度,才设计了内存,用户缓冲用户IO等待导致CPU的等 ...

  9. 深入JVM内存区域管理,值得你收藏

    JDK和JRE和JVM的关系 JDK(Java Development Kit)是程序开发者用来来编译.调试java程序用的开发工具包 JRE(JavaRuntimeEnvironment,Java运 ...

随机推荐

  1. javascript数据结构与算法--散列

    一:javascript数据结构与算法--散列  一:什么是哈希表? 哈希表也叫散列表,是根据关键码值(key,value)而直接进行访问的数据结构,它是通过键码值映射到表中一个位置来访问记录的,散列 ...

  2. 【译文】 GC 安全点 和安全区域

    原文链接 : here 根引用  Root references  一个实例死了,意味着它变得无用.只用程序员知道一个实例是否已经无用.为了让程序知道一个实例是否已经无用,我们可以使用编译器分析,引用 ...

  3. 【Bootstrap】Bootstrap-select多选下拉框实现

    目录 前言 需要引用的它们 核心选项 核心方法 实例应用 回到顶部 前言 项目中要实现多选,就想到用插件,选择了bootstrap-select. 附上官网api链接,http://silviomor ...

  4. tyvj1102 单词的划分

    描述 有一个很长的由小写字母组成字符串.为了便于对这个字符串进行分析,需要将它划分成若干个部分,每个部分称为一个单词.出于减少分析量的目的,我们希望划分出的单词数越少越好.你就是来完成这一划分工作的. ...

  5. hive单机安装(实战)

    hive使用与注意事项:http://blog.csdn.net/stark_summer/article/details/44222089 连接命令:beeline -n root -u jdbc: ...

  6. TypePerf.exe使用命令查找计数器

    TypePerf.exe是一个命令行工具,包括把Windows操作系统的性能计数器数据输出到命令窗口或写入到支持该功能的日志文件格式中. 通过以下两个参数获取计数器信息: -q [object] 列出 ...

  7. SQL 批量删除表

    /*-------------------------------- 功能说明: 批量DropTable 使用说明: 使用时一定要小心,因为删选表的where条件是like所有必须保证where 后的 ...

  8. PAT Mooc datastructure 6-1

    Saving James Bond - Hard Version This time let us consider the situation in the movie "Live and ...

  9. oracle分区表知识

    在F5中查看执行计划的时候总是看到很多信息: range分区 执行计划中出现的: 分区表,按 n1 ,n2 分区 partition range single:访问单个分区 partition ran ...

  10. Eclipse快捷键与使用技巧总结

    快捷键 功能 Ctrl + / 双斜杠注释 Ctrl + Shilft + / 块注释 Alt + / 代码提示 Ctrl + I (或 Ctrl + Shift + F) 格式化代码 Ctrl + ...