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 ...
随机推荐
- C语言入门-数组
今天十月一日,上午看阅兵激情澎湃,但是下午还是要继续写C语言,前面的这块很简单 int number[100]; scanf("%d" , &number[i]); 一.定 ...
- CentOS7 搭建php环境
1,先安装apache: yum install httpd 配置ServerName,进入httpd.conf文件: vi /etc/httpd/conf/httpd.conf 将#ServerNa ...
- 运算符 字符串 for循环
1. 运算符 1.1赋值运算符 = += -= *= /= //= %= **= 1.2比较运算符 < > = <= == != 1.3成员运算符 in not in 1.4逻辑运算 ...
- MySQL InnoDB如何保证事务特性
如果有人问你"数据库事务有哪些特性"?你可能会很快回答出原子性.一致性.隔离性.持久性即ACID特性.那么你知道InnoDB如何保证这些事务特性的吗?如果知道的话这篇文章就可以直接 ...
- K8s 从懵圈到熟练 – 镜像拉取这件小事
作者 | 声东 阿里云售后技术专家 导读:相比 K8s 集群的其他功能,私有镜像的自动拉取,看起来可能是比较简单的.而镜像拉取失败,大多数情况下都和权限有关.所以,在处理相关问题的时候,我们往往会轻松 ...
- kotlin系列文章 --- 2.基本语法
函数 函数定义使用fun关键字,参数格式为:参数:类型,需要声明返回类型 fun sum(a:Int, b:Int): Int{ return a+b } 表达式作为函数体,返回值类型自动推断 fun ...
- ELK 学习笔记之 Logstash安装
Logstash安装: https://www.elastic.co/downloads/logstash 下载解压: tar –zxvf logstash-5.6.1.tar.gz 在/usr/lo ...
- CentOS7 Redis5.0.5环境搭建
CentOS7 Redis5.0.5环境搭建 1基本环境配置 CentOS Linux release 7.6.1810 (Core) redis 5.0.5 1.下载解压redis.通过wget在官 ...
- TCP三次握手和四次握手全过程 为什么要三次握手而不是二次握手?
三次握手 第一次握手: 客户端发送syn包(syn=x)到服务器,并进入SYN_SEND状态,等待服务器确认: 第二次握手: 服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送 ...
- Codeforces1141F_Same Sum Blocks
题意 给定一个序列,求最多的不相交区间满足区间和相同. 分析 从暴力的角度想,是枚举区间再求和,反过来想,直接记录每个和对应是那些区间,然后排个序求最大不相交即可. 代码 #include <b ...