在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. Scrapy项目 - 数据简析 - 实现豆瓣 Top250 电影信息爬取的爬虫设计

    一.数据分析截图(weka数据分析截图 ) 本例实验,使用Weka 3.7对豆瓣电影网页上所罗列的上映电影信息,如:标题.主要信息(年份.国家.类型)和评分等的信息进行数据分析,Weka 3.7数据分 ...

  2. 【django】ajax,上传文件,图片预览

    1.ajax 概述: AJAX = 异步 JavaScript 和 XML. AJAX 是一种用于创建快速动态网页的技术. 通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新.这意味 ...

  3. java 中使用StopWatch来计算时间差

    以前在进行时间耗时时我们通常的做法是先给出计算前后两个的时间值,然后通过详见来计算耗时时长. eg: long start = System.currentTimeMillis(); ......业务 ...

  4. windows安装Mycat并测试

    1.下载系统安装包 选择相应的版本进行下载,地址:http://dl.mycat.io/ .Mycat数据库分库分表中间件介绍 http://www.mycat.io/ 2.安装 安装mycat前需要 ...

  5. idea 启动springboot项目报找不到主类

    今天搭建的一个新springboot项目,运行启动类时控制报找不到主类错误 解决方法: 在idea控制台输入mvn clean install命令

  6. .NET进阶篇-丑话先说,Flag先立--致青春

    作为开发者,工作了半年,也总觉得技术栈和刚毕业区别不大,用的技术还都是N年前的,每每看到新东西,也只心里哇塞惊叹一下,然后就回归于忙碌.怪自己的技术池太浅,热门的令人称奇的技术也都是在其他巨人的肩膀上 ...

  7. Stanford公开课《编译原理》学习笔记(2)递归下降法

    目录 一. Parse阶段 CFG Recursive Descent(递归下降遍历) 二. 递归下降遍历 2.1 预备知识 2.2 多行语句的处理思路 2.3 简易的文法定义 2.4 文法产生式的代 ...

  8. cat命令显示文件指定行

    cat filename | tail -n 100 显示文件最后100行 cat filename | head -n 100 显示文件前面100行 cat filename | tail -n + ...

  9. windows下安装和设置gradle

    一.安装前检查 检查jdk是否已经安装 二.下载gradle 1. https://gradle.org/releases/ 2.设置gradle环境变量 3. 环境变量中增加名为GRADLE_HOM ...

  10. 基于 HTML5 的工控物联网的隧道监控实战

    前言 监控隧道内的车道堵塞情况.隧道内的车祸现场,在隧道中显示当前车祸位置并在隧道口给与提示等等功能都是非常有必要的.这个隧道 Demo 的主要内容包括:照明.风机.车道指示灯.交通信号灯.情报板.消 ...