Java常见内存溢出异常分析
Java虚拟机规范规定JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等,而Hotspot jvm的实现中,将堆内存分为了三部分,新生代,老年代,持久带,其中持久带实现了规范中规定的方法区,而内存模型中不同的部分都会出现相应的OOM错误,接下来我们就分开来讨论一下。
栈溢出(StackOverflowError)
栈溢出抛出java.lang.StackOverflowError
错误,出现此种情况是因为方法运行的时候栈的深度超过了虚拟机容许的最大深度所致。
出现这种情况,一般情况下是程序错误所致的,比如写了一个死递归,就有可能造成此种情况。 下面我们通过一段代码来模拟一下此种情况的内存溢出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import java.util.*;
import java.lang.*;
public class OOMTest{
public void stackOverFlowMethod(){
stackOverFlowMethod();
}
public static void main(String... args){
OOMTest oom = new OOMTest();
oom.stackOverFlowMethod();
}
}
|
运行上面的代码,会抛出如下的异常:
1
2
|
Exception in thread "main" java.lang.StackOverflowError
at OOMTest.stackOverFlowMethod(OOMTest.java: 6 )
|
堆溢出(OutOfMemoryError:java heap space)
堆内存溢出的时候,虚拟机会抛出java.lang.OutOfMemoryError:java heap space
,出现此种情况的时候,我们需要根据内存溢出的时候产生的dump文件来具体分析(需要增加-XX:+HeapDumpOnOutOfMemoryError
jvm启动参数)。出现此种问题的时候有可能是内存泄露,也有可能是内存溢出了。
如果内存泄露,我们要找出泄露的对象是怎么被GC ROOT引用起来,然后通过引用链来具体分析泄露的原因。
如果出现了内存溢出问题,这往往是程序本生需要的内存大于了我们给虚拟机配置的内存,这种情况下,我们可以采用调大-Xmx来解决这种问题。
下面我们通过如下的代码来演示一下此种情况的溢出:
1
2
3
4
5
6
7
8
9
10
|
import java.util.*;
import java.lang.*;
public class OOMTest{
public static void main(String... args){
List< byte []> buffer = new ArrayList< byte []>();
buffer.add( new byte [ 10 * 1024 * 1024 ]);
}
}
|
我们通过如下的命令运行上面的代码:
java -verbose:gc -Xmn10M -Xms20M -Xmx20M -XX:+PrintGC OOMTest
程序输入如下的信息:
1
2
3
4
5
|
[GC 1180K->366K(19456K), 0.0037311 secs]
[Full GC 366K->330K(19456K), 0.0098740 secs]
[Full GC 330K->292K(19456K), 0.0090244 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at OOMTest.main(OOMTest.java: 7 )
|
从运行结果可以看出,JVM进行了一次Minor gc和两次的Major gc,从Major gc的输出可以看出,gc以后old区使用率为134K,而字节数组为10M,加起来大于了old generation的空间,所以抛出了异常,如果调整-Xms21M,-Xmx21M,那么就不会触发gc操作也不会出现异常了。
通过上面的实验其实也从侧面验证了一个结论:当对象大于新生代剩余内存的时候,将直接放入老年代,当老年代剩余内存还是无法放下的时候,出发垃圾收集,收集后还是不能放下就会抛出内存溢出异常了
持久带溢出(OutOfMemoryError: PermGen space)
我们知道Hotspot jvm通过持久带实现了Java虚拟机规范中的方法区,而运行时的常量池就是保存在方法区中的,因此持久带溢出有可能是运行时常量池溢出,也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。当持久带溢出的时候抛出java.lang.OutOfMemoryError: PermGen space
。
我在工作可能在如下几种场景下出现此问题。
- 使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的class没有被卸载掉。
- 如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。
- 一些第三方框架,比如spring,hibernate都通过字节码生成技术(比如CGLib)来实现一些增强的功能,这种情况可能需要更大的方法区来存储动态生成的Class文件。
我们知道Java中字符串常量是放在常量池中的,String.intern()这个方法运行的时候,会检查常量池中是否存和本字符串相等的对象,如果存在直接返回对常量池中对象的引用,不存在的话,先把此字符串加入常量池,然后再返回字符串的引用。那么我们就可以通过String.intern方法来模拟一下运行时常量区的溢出.下面我们通过如下的代码来模拟此种情况:
1
2
3
4
5
6
7
8
9
10
11
12
|
import java.util.*;
import java.lang.*;
public class OOMTest{
public static void main(String... args){
List<String> list = new ArrayList<String>();
while ( true ){
list.add(UUID.randomUUID().toString().intern());
}
}
}
|
我们通过如下的命令运行上面代码:
java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest
运行后的输入如下图所示:
1
2
3
|
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at OOMTest.main(OOMTest.java: 8 )
|
通过上面的代码,我们成功模拟了运行时常量池溢出的情况,从输出中的PermGen space可以看出确实是持久带发生了溢出,这也验证了,我们前面说的Hotspot jvm通过持久带来实现方法区的说法。
OutOfMemoryError:unable to create native thread
最后我们在来看看java.lang.OutOfMemoryError:unable to create natvie thread
这种错误。 出现这种情况的时候,一般是下面两种情况导致的:
- 程序创建的线程数超过了操作系统的限制。对于Linux系统,我们可以通过ulimit -u来查看此限制。
- 给虚拟机分配的内存过大,导致创建线程的时候需要的native内存太少。我们都知道操作系统对每个进程的内存是有限制的,我们启动Jvm,相当于启动了一个进程,假如我们一个进程占用了4G的内存,那么通过下面的公式计算出来的剩余内存就是建立线程栈的时候可以用的内存。
线程栈总可用内存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占用的内存
通过上面的公式我们可以看出,-Xmx 和 MaxPermSize的值越大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越小。因此如果是因为这种情况导致的unable to create native thread
,那么要么我们增大进程所占用的总内存,或者减少-Xmx或者-Xss来达到创建更多线程的目的。
Java常见内存溢出异常分析相关推荐
- Java 常见内存溢出异常与代码实现
Java 堆 OutOfMemoryError Java 堆是用来存储对象实例的, 因此如果我们不断地创建对象, 并且保证 GC Root 和创建的对象之间有可达路径以免对象被垃圾回收, 那么当创建的 ...
- Java常见内存溢出(OOM)解决方案
Java 常见内存溢出(OOM)解决方案 一,jvm内存区域 1, 程序计数器 一块很小的内存空间,作用是当前线程所执行的字节码的行号指示器. 2, java栈 与 ...
- java模拟内存溢出并分析_模拟内存溢出通过MAT分析
构建一个简单的Springboot应用,模拟出OOM场景,再导出heap dump文件,通过Mat分析. 搭建简易Springboot,模拟OOM场景 搭建一个简易的springboot工程,在con ...
- java模拟内存溢出并分析_本地模拟内存溢出并分析Dump文件
java Dump文件分析 前言 dump文件是java虚拟机内存在某一时间点的快照文件,一般是.hprof文件,下面自己模拟一下本地内存溢出,生成dump文件,然后通过mat工具分析的过程. 配置虚 ...
- jvm(2)-OutOfMemoryError 异常(内存溢出异常)
[0]README 0.1)本文转自 深入理解 jvm, 旨在学习 OutOfMemoryError 异常(内存溢出异常) 的触发类型: 0)准备知识 0.1)除了程序计数器外,虚拟机内存的其他几个运 ...
- delphi 算术溢出解决方法_性能优化系列:JVM 内存划分总结与内存溢出异常详解分析...
前言 那些使用过 C 或者 C++ 的读者一定会发现这两门语言的内存管理机制与 Java 的不同.在使用 C 或者 C++ 编程时,程序员需要手动的去管理和维护内存,就是说需要手动的清除那些不需要的对 ...
- 《深入理解JVM.2nd》笔记(二):Java内存区域与内存溢出异常
文章目录 概述 运行时数据区域 程序计数器 Java虚拟机栈 本地方法栈 Java堆 方法区 运行时常量池 直接内存 HotSpot虚拟机对象探秘 对象的创建 第一步 第二步 第三步 第四步 最后一脚 ...
- 《深入理解java虚拟机》第2章 Java内存区域与内存溢出异常
Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来. 2.1 概述 https://blog.csdn.net/q5706 ...
- java 计数器越界,[总结]-第二章 Java内存区域与内存溢出异常
[总结]-第二章 Java内存区域与内存溢出异常 一.知识点 1.虚拟机运行时数据区 方法区:运行时常量池(JDK1.7被移出) 堆:存放对象实例或数组.新生代和老年代 虚拟机栈:线程私有.栈 本地方 ...
最新文章
- 详解Apache下.htaccess文件常用配置
- 分布式环境下,互斥性与幂等性问题,分析与解决思路
- 深入理解Linux中的文件权限
- mysql数据库连接jar_mysql数据库连接jar包
- 创业新力量缔造新未来,岳麓峰会再增“长沙势能”
- Spring Boot 开发web 项目
- 【SpringBoot】Spring项目中value注解,@Value不能够读取到配置文件的值,无法成功注入值的问题汇总及解决
- java获取实体类的属性和值
- functools.reduce() 函数(不同于einops.reduce)
- 通过DOS命令nslookup查域名DNS服务器
- 【转】iphone开发中NSMutableAttributedString/NSAttributedString 富文本设置
- Excel 2016双击文件打开为空白的解决办法-亲测解决。
- socket 关于同一条TCP链接数据包到达顺序的问题
- 关于人生的思考(暂时想不出更好的标题)
- excel文件被写保护怎么解除_u盘被写保护怎么解除_u盘怎么解除写保护状态
- linux用迅雷下载软件,wine 迅雷下载安装(wine 迅雷极速版)
- 由内而外全面造就自己
- android倒计时动画特效,Android实现答题倒计时效果
- VIVO NEX 3 5G版上手评测,除99.6%的屏占比,还有什么理由入手
- 生产订单完工确认(CO11N) BAPI : BAPI_PRODORDCONF_CREATE_TT