1
这些异常你是否还记得?
正式开讲之前,先罗列一下所知的 OutOfMemoryError (简称 OOM)异常,看看这些异常工作中你是否也遇到过?
Java 堆内存溢出:java.lang.OutOfMemoryError: Java heap space
垃圾回收内存溢出:java.lang.OutOfMemoryError: GC overhead limit exceeded
方法区溢出:java.lang.OutOfMemoryError: PermGen space
Metaspace 内存溢出:java.lang.OutOfMemoryError: Metaspace
直接内存内存溢出:java.lang.OutOfMemoryError: Direct buffer memory
栈内存溢出:java.lang.StackOverflowError
创建本地线程内存溢出:java.lang.OutOfMemoryError: Unable to create new native thread
数组超限内存溢出:java.lang.OutOfMemoryError:Requested array size exceeds VM limit
在实际工作中,若真遇到了上面罗列的这些内存溢出的异常,你是否能够根据异常提示迅速定位是哪儿出了幺蛾子,并是否能够铲除这些幺蛾子呢?
希望通过此篇分享,尽量能够让大家了解每个异常发生的场景,并能够掌握每个异常场景的应对之策。
如上图示意,按照内存共享来划分 JVM 内存,主要划分为线程共享内存区域(堆、方法区)、线程私有内存区域(程序计数器、虚拟机栈、本地方法栈)、直接内存。而在《Java 虚拟机规范》的规定里,除了程序计数器外,虚拟机内存的其它几个运行时区域都可能发生 OOM 异常,接下来通过代码来剖析一下各种 OutOfMemoryError(OOM)的场景。
2
实战:OutOfMemoryError 异常
场景一
java.lang.OutOfMemoryError: Java heap space
/**
* VM options:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
* @author 一猿小讲
*/
public class HeapOOM {
public static void main(String[] args) {
byte[] bytes = new byte[20 * 1024 * 1024];
System.out.println(bytes);
}
}
理论且不谈,直接抛代码,代码很简单,创建一个字节数组对象,要分配 20M 的空间。
若在运行程序时指定 VM 参数:
通过参数 -Xms10m -Xmx10m 将堆的最小值与最大值都设置为 10M,即限制 Java 堆的大小为 10MB,并且避免堆自动扩展;
通过参数 -XX:+HeapDumpOnOutOf-MemoryError 让虚拟机在出现内存溢出异常的时候 Dump 出当前的内存堆转储快照以便进行事后分析。
指定 VM options 后的运行结果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid35115.hprof ...
Heap dump file created [1033561 bytes in 0.005 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at HeapOOM.main(HeapOOM.java:7)
为什么呢?简单解释原因,-Xms10m -Xmx10m 限制了堆的最大值为 10M,而 new byte[20 * 1024 * 1024] 需要 20M 的空间,则堆内存明显不够,则直接导致 OOM。
面对此种异常,常规解决思路:
要检查一下代码是否存在优化的空间;
依据内存溢出时的快照文件 xx.hprof 来判断是否存在内存泄露,不需要的对象有没有被回收掉;
调节虚拟机的堆参数(-Xms -Xmx),适当调大堆内存。
场景二
java.lang.OutOfMemoryError: GC overhead limit exceeded
/**
* VM options:-Xmx6m -XX:+HeapDumpOnOutOfMemoryError
* @author 一猿小讲
*/
public class HeapOOM {
static class GirlFriend {
}
public static void main(String[] args) {
List<GirlFriend> list = new ArrayList<GirlFriend>();
while (true) {
list.add(new GirlFriend());
}
}
}
理论且不谈,直接抛代码,代码很简单,一直往集合中加入新创建的对象(虚妄的单身狗生活:一直创建女朋友对象。)
若在运行程序时指定 VM 参数:
通过参数 -Xmx6m 将堆的最大值设置为 6M;
通过参数 -XX:+HeapDumpOnOutOf-MemoryError 让虚拟机在出现内存溢出异常的时候 Dump 出当前的内存堆转储快照以便进行事后分析。
指定 VM options 后的运行结果:
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to java_pid35304.hprof ...
Heap dump file created [12557270 bytes in 0.082 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at HeapOOM.main(HeapOOM.java:16)
为什么呢?来段洋文,尝试解读一下。
The parallel(concurrent) collector will throw an OutOfMemoryError if too much time is being spent in garbage collection: if more than 98% of the total time is spent in garbage collection and less than 2% of the heap is recovered, an OutOfMemoryError will be thrown.
大概意思应用程序在垃圾收集上花费了太多时间,但是却没有什么卵用,默认超过 98% 的时间用来做GC却回收了不到2%的内存时将会抛出 OutOfMemoryError 异常。
面对此种异常,常规解决思路:
程序启动添加 JVM 参数 -XX:-UseGCOverheadLimit 推迟异常报出,而并非彻底解决了问题;
好好分析快照文件 xx.hprof,排除代码问题,若确实堆内存不足,通过参数 -Xmx 适度调整堆内存大小。
场景三
java.lang.OutOfMemoryError: PermGen space
首先来解释一下 PermGen space 的用处,主要用来存储每个类的信息,例如:类加载器引用、运行时常量池(所有常量、字段引用、方法引用、属性)、字段(Field)数据、方法(Method)数据、方法代码、方法字节码等等。
当出现 java.lang.OutOfMemoryError: PermGen space 异常时,要能够知道可能是由于太多的类或者太大的类被加载到方法区导致的。
解决方案:可以根据具体情况采用 -XX:MaxPermSize=64m 参数来加大分配的内存进行解决。
场景四
java.lang.OutOfMemoryError: Metaspace
在 JDK6、7 还能够见到java.lang.OutOfMemoryError: PermGen space异常的踪影,而在 JDK8 以后,永久代便完全退出了历史舞台,元空间作为其替代者登场,在默认参数设置下,已经很难再迫使虚拟机产生上面所描述的异常了。不过 java.lang.OutOfMemoryError: Metaspace 异常偶尔就会碰到了。
java.lang.OutOfMemoryError: Metaspace(元空间的溢出),为什么会出现这个异常?元空间大小的要求取决于加载的类的数量以及这种类声明的大小,所以主要原因很可能是太多的类或太大的类加载到元空间导致的。
解决方案:
优化参数配置,适度调大该值 -XX:MaxMetaspaceSize;
着重关注代码生成以及依赖的三方包。
场景五
java.lang.OutOfMemoryError: Direct buffer memory
/**
* VM Args:-XX:MaxDirectMemorySize=4m
* @author 一猿小讲
*/
public class DirectMemoryOOM {
private static final int _5MB = 5 * 1024 * 1024;
public static void main(String[] args) throws Exception {
//-XX:MaxDirectMemorySize=4m 本地内存配置的是4MB,这里实际使用的是5MB
ByteBuffer.allocateDirect(_5MB);
}
}
理论且不谈,直接看代码,代码很简单,分配一个 5M 的直接字节缓冲区。
若在运行程序时指定直接内存的容量大小 -XX:MaxDirectMemorySize 为 4M,则程序运行会出现以下效果:
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at DirectMemoryOOM.main(DirectMemoryOOM.java:11)
解决方案:
可以通过参数 -XX:MaxDirectMemorySize 适度调整直接内存的容量大小;
考虑代码是否有优化空间。
场景六
java.lang.StackOverflowError
/**
* 栈溢出模拟
* @author 一猿小讲
*/
public class StackOOM {
public static void main(String[] args) {
love1024();
}
public static void love1024() {
// 递归调用
love1024();
}
}
直接看代码,代码很简单,模拟了一下方法递归调用,程序运行效果如下:
Exception in thread "main" java.lang.StackOverflowError
at StackOOM.love1024(StackOOM.java:12)
at StackOOM.love1024(StackOOM.java:12)
解决方案:
StackOverflowError 属于比较好排查的一种错误,有错误栈可以阅读,大部分出现这种错误,都是程序出现了递归调用的问题;
如果真需要递归调用的存在,可以适度调整参数 -Xss 的大小来解决。
场景七
java.lang.OutOfMemoryError: Unable to create new native thread
/**
* 无法创建本地线程模拟
* @author 一猿小讲
*/
public class ThreadUnableCreateOOM {
public static void main(String[] args) {
while(true) {
new Thread(){
@Override
public void run() {
System.out.println("1024 节日快乐");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
}
}
}.start();
}
}
}
直接看代码,代码很简单,模拟了一下业务研发中若一直启动新的线程去执行任务而带来的效果,运行如下:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at ThreadUnableCreateOOM.main(ThreadUnableCreateOOM.java:18)
为什么呢?因为当 JVM 向操作系统请求创建一个新线程时,然而操作系统也无法创建新的 native 线程时就会抛出 Unable to create new native thread 错误。
解决方案:
优化代码,考虑使用线程池及线程池的数量设置是否合适;
检查操作系统本身的线程数是否可以适度调整。
场景八
java.lang.OutOfMemoryError:Requested array size exceeds VM limit
/**
* OutOfMemoryError: Requested array size exceeds VM limit
* @author 一猿小讲
*/
public class ArrayLimitOOM {
public static void main(String[] args) {
int[] ary = new int[Integer.MAX_VALUE];
}
}
直接看代码,代码很简单,创建一个大小为 Integer.MAX_VALUE 的 int 数组,代码看起来没毛病,程序运行起来很诧异:
Exception in thread "main" java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at ArrayLimitOOM.main(ArrayLimitOOM.java:3)
为什么?当你编写的 Java 程序试图要分配大于 Java 虚拟机可以支持的数组时就会报 OOM,Java 对应用程序可以分配的最大数组大小有限制,不同平台限制有所不同。
解决方案:检查代码是否有必要创建这么大号的数组,是否可以采用集合、拆分等其它方式处理。
3
寄语
一个人想步行穿过大陆,但道路布满了荆棘,这时候他有两种选择:铺一条路,征服大自然,或者准备一双草鞋。
好了,本次就谈到这里,一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。会持续输出原创精彩分享,敬请期待!
1、HMAC-MD5HMAC-MD5签名算法,采用MD5作为散列函数,通过签名秘钥(sigSecret )对指定消息体进行加密,然后采用MD5信息摘要的方式形成新的密文,一般会要求将形成的签名密文转为大写。2、签名的作用防止数据在传输过程中被篡改,通过HMAC-MD5 签名算法校验数据。验证流程:得到请求方传过来的签名sig->自己拿到请求体后,再按双方约定的协议生成一个...
SecureCRT脚本SecureCRT是VanDyke Software的一个商业SSH、Telnet客户端和虚拟终端软件,简单的说,是用于连接本地机器与远程服务器的工具,类似功能的工具有Putty、Xshell等。SecureCRT支持的协议包括SSH1、SSH2、Telnet、RLogin、Serial、TAPI、Raw。这里记录SecureCRT对脚本的支持。一、脚本创建方式SecureCRT支持脚本的文件类型包括js、VBScript、Python等。有两种创建脚本的方式:.
信息学竞赛中,会遇到一些问题,即给出一个矩阵(二维数组),求满足某个条件的子矩阵的个数,或是满足某个条件的最大,最小子矩阵之类的问题,在这里我总结一小点,防止以后再遭套路。
前言 软工文档的总结在软工视频无力总结后,被遗忘了。当初许诺按章节总结的视频博客还是没如愿成为系列总结,但是自己还是过意不去,就这么把软工文档的辛勤成果深埋在云盘里。所以,还是要来博客里总结一下本璐的小感受。收获 从可行性分析报告到项目开发计划。从考虑软件需求的说明书,到精心细致的用户手册。软件工程文档包含了太多思想。自己也是茫然的开始接触文档,也无从下手去写。文档按照例子写
1.安装epel源yum -y install epel-releaseyum repolist 报错如下:“Error: Cannot retrieve metalink for repository: epel. Please verify its path and try again”原因:需要更新CA证书了,那么只需要更新CA证书就可以,不过在此同时需要临时禁用epel源并更新就可以了命令...
【CSDN 编者按】Python 风头正盛,未来一段时间内想必也会是热门编程语言之一。因此,熟练掌握Python 对开发者来说极其重要,说不定能给作为开发者的你带来意想不到的财富。作者 ...
1、安装arduino IDE,安装后按照说明添加libIDE上选择“项目->加载库->添加.ZIP库”,选择C:\Users\用户名\Documents\Arduino\libraries\下的TTGO_TWatch_Library-master.zip;查看是否添加成功:项目->加载库,滑到最后可看见已添加的TTGO...参考:https://github.com/Xinyuan-LilyGO/TTGO_TWatch_Library2、安装esp32 ardui
路径/为所有Linux发行版的根目录。/bin 即binary,存放着最常使用的命令。如文件/目录操作,文件压缩,网络管理,进程管理,模块管理累工具。/sbin 即super user's binary,存放系统管理员才有权限使用的系统管理程序。/usr路径下存放着很多重要的程序和文件,该目录类似于Windows系统中的Program Files目录。/usr/bin 存放系统用...
JUnit4是JUnit框架有史以来的最大改进,其主要目标便是利用Java5的Annotation特性简化测试用例的编写。先简单解释一下什么是Annotation,这个单词一般是翻译成元数据。元数据是什么?元数据就是描述数据的数据。也就是说,这个东西在Java里面可以用来和public、static等关键字一样来修饰类名、方法名、变量名。修饰的作用描述这个数据是做什么用的,差不多和publ
linux多线程,C++多线程,线程同步,互斥锁,条件变量,读写锁,自旋锁,信号量,关卡
作者 |AbhishekPrakash译者 | 苏本如责编 | 屠敏转载自 CSDN(ID:CSDNnews)微软的VS Code无论对Web开发人员或其他程序员来说...
T1:#include<stdio.h>#include<stdlib.h>inline int gi(){ int sum=0,f=1;char ch=getchar(); while(ch>'9' || ch<'0'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0' &a...