JVM(2) Java内存溢出异常
在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内存溢出异常的更多相关文章
- Java内存溢出异常(上)
上一篇文章我们讲了JVM运行时数据区域与内存溢出异常,其中对于内存溢出异常这部分将的不够详细,这篇文章将着重讲解Java内存溢出异常的相关知识.如果有没看过上一篇文章的小伙伴们,请点击Java内存区域 ...
- Java内存溢出异常(下)
此篇是上一篇文章Java内存溢出异常(上)的续篇,没有看过的同学,可以先看一下上篇.本篇文章将介绍剩余的两个溢出异常:方法区和运行时常量池溢出. 方法区和运行时常量池溢出 这部分为什么会放在一起呢?在 ...
- java内存溢出异常
名称 特征 作用 配置参数 异常 程序 计数器 占用内存小,线程私有, 生命周期与线程相同 大致为字节码行号指示器 无 无 虚拟机栈 线程私有,生命周期与线程 相同,使用连续的内存空间 Java 方法 ...
- 模拟Java内存溢出
本文通过修改虚拟机启动参数,来剖析常见的java内存溢出异常(基于jdk1.8). 修改虚拟机启动参数Java堆溢出虚拟机栈溢出方法区溢出本机直接内存溢出 修改虚拟机启动参数 这里我们使用的是ID ...
- JVM自动内存管理-Java内存区域与内存溢出异常
摘要: JVM内存的划分,导致内存溢出异常的可能区域. 1. JVM运行时内存区域 JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域: 1.1 程序计数器 程序计数器是一块较小的内 ...
- JVM:Java常见内存溢出异常分析
转载自:http://www.importnew.com/14604.html Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等,而Hotspot jvm的实现中,将堆 ...
- JVM高级特性与实践(一):Java内存区域 与 内存溢出异常
套用<围城>中的一句话,“墙外面的人想进去,墙里面的人想出来”,用此来形容Java与C++之间这堵内存动态分配和垃圾收集技术所围成的“围墙”就再合适不过了. 对于从事C.C++的开发人员而 ...
- JVM学习与问题总结——java内存区域与内存溢出异常
java虚拟机将内存分为哪些区域? 根据Java SE7版本的Java虚拟机规范,虚拟机管理的内存包括5个运行时数据区域: 程序计数器 虚拟机栈 本地方法栈 方法区 堆 运行时数据区各部分的作用? 程 ...
- [深入理解JVM虚拟机]第2章-Java内存区域与内存溢出异常
2.0引-Java内存区域中,栈内存和堆内存分别装什么,为什么? 栈:解决程序的运行问题,即程序如何执行,或者说如何处理数据. 堆:解决的是数据存储的问题,即数据怎么放,放在哪儿. 参考链接https ...
随机推荐
- 第六届蓝桥杯java b组第三题
第三题 三羊献瑞 观察下面的加法算式: 其中,相同的汉字代表相同的数字,不同的汉字代表不同的数字. 请你填写“三羊献瑞”所代表的4位数字(答案唯一),不要填写任何多余内容. 答案这个题目完全可以使用暴 ...
- redis 主从复制和哨兵模式(二)
Redis 主从复制 为了分担单机 redis 的数据服务压力,需要进行读写分离,所以搭建 redis 的主从结构,主节点负责写,从节点负责读,主节点定期把数据同步到从节点. 配置主从 # 配置文件中 ...
- jmeter 分布式疑难杂症 待完善
完善中....(可能不会完善了) 前景:在window调度机上配置好了jmeter相关的 jmx文件,因需搞分布式所以弄了几台linux. 但是jmx文件请求包含有bean shell脚本,需导入ja ...
- CentOS8-网卡配置
一. 介绍 Centos8系统更新,新的版本让人看起来感觉很舒服,这时有人会配置CentOS8系统的网卡使系统上网,就会遇到配置好的网卡不会生效,自己想想和配置CentOS7的时候一个样啊,CentO ...
- js 跳转链接的几种方式
1.跳转链接 在当前窗口打开 window.location.href="http://www.baidu.com" 等价于 <a href="baidu.com& ...
- java8 运算语法集
1.分组并进行求和组合运算 示例主要代码: List<String> items = Arrays.asList("apple", "apple", ...
- 前端防止url输入地址直接访问页面
首先,解决这个问题要搞明白此url是从程序内部跳转还是直接在地址栏输入的,如果是程序内部跳转,那就好办啦.方法如下: 判断用户是否登录状态,是否携带token 使用router.beforeEach注 ...
- linux分析工具之vmstat详解
一.概述 vmstat命令是最常见的Linux/Unix监控工具,可以展现给定时间间隔的服务器的状态值,包括服务器的CPU使用率,内存使用,虚拟内存交换情况,IO读写情况.首先我们查看下帮助.如下图所 ...
- 白话HTTPS加密机制
在讲主题之前,我们先来区分两个概念:签名和加密有什么区别? 我们从字面意思看: 签名就是一个人对文件签署自己的名字,证明这个文件是我写的或者我认可的,所以只要别人看到我的签名,认识我字迹的人就知道这个 ...
- Java 学习笔记之 线程interrupted方法
线程interrupted方法: interrupted()是Thread类的方法,用来测试当前线程是否已经中断. public class InterruptThread extends Threa ...