在Java虚拟机运行时数据区中,除了程序计数器之外,虚拟机栈、本地方法栈、方法区和Java堆都有发生OutOfMemoryError(简称OOM)异常的可能。

  一、Java堆溢出

  Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

  VM参数:

  • -Xms20m:设置堆的最小值为20MB
  • -Xmx20m:设置堆的最大值为20MB,两者设置一样是为了避免堆自动扩展
  • --XX:+HeapDumpOnOutOfMemoryError:让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后进行分析(默认存储路径为程序工作目录下)
public class HeapOOM {

    static class OOMObject{
} public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
}
}

  运行结果:当出现Java堆溢出异常时,通常在错误信息后会进一步提示Java heap space

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid14208.hprof ...
Heap dump file created [28125411 bytes in 0.077 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Unknown Source)
at java.util.Arrays.copyOf(Unknown Source)
at java.util.ArrayList.grow(Unknown Source)
at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
at java.util.ArrayList.add(Unknown Source)
at outOfMemoryError.HeapOOM.main(HeapOOM.java:14)

  解决方法:一般的手段是先通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要先分析清楚到底是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。

  •  内存溢出(Memory Overflow):是指程序在申请内存时,没有足够的内存空间供其使用。
  •  内存泄漏(Memory Leak):是指在程序申请内存后,无法释放以申请的内存空间。

  如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法回收它们的。掌握了泄漏对象的类型信息以及GC Roots引用的信息,就可以比较准确地定位出泄漏的位置。

  如果不存在内存泄漏,也就是内存中的对象确实都还必须存活着,那就应该检查虚拟机的堆参数(-Xmx与-Xms),与机器物理内存对比看是否可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期间的内存消耗。

  例如:可以看到占据了main线程创建的对象占据了16MB的内存。

  

  二、虚拟机栈和本地方法栈溢出

  由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此,对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的,栈容量只由-Xss参数设定。对于虚拟机栈和本地方法栈,在Java虚拟机中有两种异常。

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,将抛出OutOfMemoryError异常

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

  如果不断地建立线程,并且通过-Xss参数为每个线程的栈分配的内存越大,越容易产生OutOfMemoryError异常。

  因此,如果建立过多线程导致内存溢出,在不能较少线程数或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。

  例如:1.通过-Xss128k设置单个线程的虚拟机栈容量为128k,单个线程情况下出现了StackOverflowError异常

        2.为通过-Xss2M设置单个线程的虚拟机栈容量为2M,然后不断地创建线程的情况下出现了OutOfMemoryError异常。

  三、包括常量池溢出

  String.intern()方法是一个Native方法,它的作用是:如果字符串常量池中已经包含了一个等于此String对象的字符串,则返回常量池中的这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

  在JDK1.6及之前的版本中,由于常量池分配在永久代,可以通过-XX:PermSize=10M和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池容量。

public class RuntimeConstantPool100M {

    public static void main(String[] args) {
// 使用List保持着常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}

  因此,在JDK1.6及之前的版本中,会报PermGen Space异常,这是因为,通过String的intern()方法连续不断地将不同的字符串都加入到运行时常量池中,然后会填满运行时常量池。这里的常量池属于方法区的一部分,而方法区又属于永久代,因此会报永久代异常。

  但是,上面的示例在JDK1.6之后的版本就不会得到相同的结果,而是会一直循环下去,这是因为字符串常量池实现方式的不同。例如:

public class RuntimeConstantPool100M {

    public static void main(String[] args) {
String str1 = new StringBuilder("Computer").append("Software").toString();
System.out.println(str1.intern() == str1);  // 返回true String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);  // 返回false
}
}

  在JDK1.6中会返回两个false,这是因为JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用,而又StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,因此将返回false。

  而JDK1.7中的intern()不会再复制实例,只是在常量池中记录首次出现的实例的引用并返回这个引用,因此对于str1.intern()方法的执行过程就是,把str1这个引用记录到常量池中,并且返回这个引用。而对于str2来说,由于常量池中已经有“Java”这个字符串的引用(默认就有),因此不是首次出现的,所以不是同一个引用。

  

  四、方法区溢出

  方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。如果运行时产生了大量类填满了方法区,那么方法区就会溢出。方法区溢出也是一种常见的内存溢出异常,一个类要被垃圾收集器回收掉,判定条件是比较苛刻的。在经常动态生成大量Class的应用中,需要特别注意类的回收情况。

  五、本机内存直接溢出

  DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定)一样。由DirectMemory导致的内存溢出,在Heap Dump文件中不会看见明显的异常,如果发现Dump文件很小,而程序中又直接或者间接使用了NIO,可能就是由于DirectMemory溢出导致的。

  

  

JVM(2) Java内存溢出异常的更多相关文章

  1. Java内存溢出异常(上)

    上一篇文章我们讲了JVM运行时数据区域与内存溢出异常,其中对于内存溢出异常这部分将的不够详细,这篇文章将着重讲解Java内存溢出异常的相关知识.如果有没看过上一篇文章的小伙伴们,请点击Java内存区域 ...

  2. Java内存溢出异常(下)

    此篇是上一篇文章Java内存溢出异常(上)的续篇,没有看过的同学,可以先看一下上篇.本篇文章将介绍剩余的两个溢出异常:方法区和运行时常量池溢出. 方法区和运行时常量池溢出 这部分为什么会放在一起呢?在 ...

  3. java内存溢出异常

    名称 特征 作用 配置参数 异常 程序 计数器 占用内存小,线程私有, 生命周期与线程相同 大致为字节码行号指示器 无 无 虚拟机栈 线程私有,生命周期与线程 相同,使用连续的内存空间 Java 方法 ...

  4. 模拟Java内存溢出

    本文通过修改虚拟机启动参数,来剖析常见的java内存溢出异常(基于jdk1.8). 修改虚拟机启动参数Java堆溢出虚拟机栈溢出方法区溢出本机直接内存溢出 修改虚拟机启动参数   这里我们使用的是ID ...

  5. JVM自动内存管理-Java内存区域与内存溢出异常

    摘要: JVM内存的划分,导致内存溢出异常的可能区域. 1. JVM运行时内存区域 JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域: 1.1 程序计数器 程序计数器是一块较小的内 ...

  6. JVM:Java常见内存溢出异常分析

    转载自:http://www.importnew.com/14604.html Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等,而Hotspot jvm的实现中,将堆 ...

  7. JVM高级特性与实践(一):Java内存区域 与 内存溢出异常

    套用<围城>中的一句话,“墙外面的人想进去,墙里面的人想出来”,用此来形容Java与C++之间这堵内存动态分配和垃圾收集技术所围成的“围墙”就再合适不过了. 对于从事C.C++的开发人员而 ...

  8. JVM学习与问题总结——java内存区域与内存溢出异常

    java虚拟机将内存分为哪些区域? 根据Java SE7版本的Java虚拟机规范,虚拟机管理的内存包括5个运行时数据区域: 程序计数器 虚拟机栈 本地方法栈 方法区 堆 运行时数据区各部分的作用? 程 ...

  9. [深入理解JVM虚拟机]第2章-Java内存区域与内存溢出异常

    2.0引-Java内存区域中,栈内存和堆内存分别装什么,为什么? 栈:解决程序的运行问题,即程序如何执行,或者说如何处理数据. 堆:解决的是数据存储的问题,即数据怎么放,放在哪儿. 参考链接https ...

随机推荐

  1. 第八届蓝桥杯java b组第八题

    ,标题:包子凑数 小明几乎每天早晨都会在一家包子铺吃早餐.他发现这家包子铺有N种蒸笼,其中第i种蒸笼恰好能放Ai个包子.每种蒸笼都有非常多笼,可以认为是无限笼. 每当有顾客想买X个包子,卖包子的大叔就 ...

  2. Python学习笔记整理总结【Django】:Model操作(一)

    Model操作(一) 一.Django ORM基本配置 ORM:关系对象映射(Object Relational Mapping,简称ORM)db Frist:到目前为止,当我们的程序涉及到数据库相关 ...

  3. Spring 梳理-Spring配置文件 -<context:annotation-config/>和<context:component-scan base-package=""/>和<mvc:annotation-driven /> 的区别

    <context:annotation-config/> 在基于主机方式配置Spring时,Spring配置文件applicationContext.xml,你可能会见<contex ...

  4. Mycat 配置文件rule.xml

    rule.xml配置文件定义了我们对表进行拆分所涉及到的规则定义.我们可以灵活的对表使用不同的分片算法,或者对表使用相同的算法但具体的参数不同. 该文件里面主要有tableRule和function这 ...

  5. volatile 关键字的作用

    简介Java 语言提供了一种稍弱的同步机制,即 volatile 变量,用来确保将变量的更新操作通知到其他线程.volatile 变量具备两种特性:变量可见性.禁止重排序. 作为同步锁在访问 vola ...

  6. Spring IOC(1)----容器刷新(refresh())之前

    首先本次分析是基于注解形式的,想来xml格式的原理都是类似的. 首先说一下什么是Bean定义(beandefinition):bean定义并不是实例化的bean,而是bean对象的一些信息,根据这些定 ...

  7. 纯 CSS 实现幻灯片播放

    介绍:   今日看到一道面试题,关于 使用纯CSS,不利用js, 写一个简单的幻灯效果页面.于是做了一个小demo,建议使用chrome,IE11查看~~ 主要思想: 利用 CSS3的 伪类选择器 : ...

  8. iOS 组件化路由框架 WisdomRouterKit 的应用

    [前言] 大家好,写作是为了和读者沟通交流,欢迎各位开发者一起了解 WisdomRouterKit SDK 的功能. 关于 iOS 组件化路由方案框架: WisdomRouterKit 的功能介绍,之 ...

  9. vue3.0 vue.config.js 配置实战

    今天讲述一下vue-config.js配置,我们前面搭建好脚手架会发现,这个对比2.x版本少了很多东西,没有build的配置,也没有webpack的配置,那么问题来了,我们如何去开发我们的项目呢,比如 ...

  10. ES6——新增数据结构Set与Map的用法

    ES6 提供了新的数据结构 Set以及Map,下面我们来一一讲解. 一.Set 特性 似于数组,但它的一大特性就是所有元素都是唯一的,没有重复. 我们可以利用这一唯一特性进行数组的去重工作. 1.单一 ...