前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >剖析Java OutOfMemoryError异常

剖析Java OutOfMemoryError异常

作者头像
王金龙
发布于 2020-03-04 11:00:14
发布于 2020-03-04 11:00:14
2K00
代码可运行
举报
文章被收录于专栏:王金龙的专栏王金龙的专栏
运行总次数:0
代码可运行

剖析Java OutOfMemoryError异常

在JVM中,除了程序计数器外,虚拟机内存中的其他几个运行时区域都有发生OutOfMemoryError异常的可能,本篇就来深入剖析一下各个区域出现OOM异常的情形,以及如何解决各个区域的OOM问题。

本篇主要包括如下内容:

  • Java堆溢出
  • 运行时常量池和方法区溢出
  • 本地内存溢出

Java堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免JVM清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生溢出异常。

堆溢出复现

要复现这种情况也很简单:将Java堆的大小限制为固定值,且不可扩展(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展);当使用一个 while(true) 循环来不断创建对象就会发生 OutOfMemory,还可以使用 -XX:+HeapDumpOutofMemoryErorr 当发生 OOM 时会自动 dump 堆栈到文件中。

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
        List<String> list = new ArrayList<>() ;
        while (true){
            list.add("1") ;
        }
    }

运行结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.Arrays.copyOf(Arrays.java:3181)
    at java.util.ArrayList.grow(ArrayList.java:265)
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
    at java.util.ArrayList.add(ArrayList.java:462)
    at Main.main(Main.java:13)

Process finished with exit code 1

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space即是说发生了堆溢出。

原因
  1. 代码中可能存在大对象分配 ;
  2. 可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象;
  3. 如果不是以上两种情况,也就是说内存中的对象都必须存活,就应当检查虚拟机的堆参数(-Xmx与-Xms),是否设置的堆内存空间太小,以及检查代码中是否存在某些对象声明周期过长、持有状态时间过长的情况。

上面复现代码产生堆溢出的原因主要是第三点。

解决方法
  1. 检查是否存在大对象的分配,最有可能的是大数组分配;
  2. 通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题
  3. 如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存;
  4. 还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性。

运行时常量池和方法区溢出

运行时常量池是方法区的一部分,我们先对运行时常量池溢出进行测试。

运行时常量池溢出复现

最典型的使用运行时常量池的方法是String的intern()方法,该方法是一个Native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String包含的字符串添加到常量池中,并且返回此String对象的引用。

在JDK1.6及以前的版本中,由于常量池分配在永久代中,可以通过-XX:PermSize和-XX:MaxPermSIze限制方法区大小,从而限制其中常量池的容量

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }

笔者所用为JDK1.8,已经去除了对这两个JVM参数的支持,程序执行的结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10m; support was removed in 8.0

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10m; support was removed in 8.0

暂不做深究。

方法区溢出复现

方法区用于存放class的相关信息,包括类名、访问修饰符、常量池、字段描述、方法描述等。可以通过借助CGLib直接操作字节码运行时生成大量的动态类,来填满方法区。

PermSize 和 MaxPermSize 已经不能使用了,那在JDK1.8中怎么设置方法区大小呢?

JDK 8 中将类信息移到了本地堆内存(Native Heap)中,将原有的永久代移动到了本地堆中成为 MetaSpace ,如果不指定该区域的大小,JVM 将会动态的调整。

可以使用 -XX:MaxMetaspaceSize=10M 来限制最大元空间。这样当不停的创建类时将会占满该区域并出现 OOM。

测试代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) {
        while (true){
            Enhancer  enhancer = new Enhancer() ;
            enhancer.setSuperclass(Main.class);
            enhancer.setUseCache(false) ;
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    return methodProxy.invoke(o,objects) ;
                }
            });
            enhancer.create() ;
        }
    }

设置好JVM参数后,执行上述代码,得到下面的额结果:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
    at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:530)
    at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
    at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:582)
    at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
    at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
    at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:569)
    at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:384)
    at com.etekcity.cloud.Main.main(Main.java:27)

Process finished with exit code 1

这里的 OOM 伴随的是 Exception in thread "main" java.lang.OutOfMemoryError: Metaspace 也就是元空间溢出。

方法区溢出在应用中是比较常见的OOM异常,Spring、Hibernate等框架在对类进行增强时,都会使用到CGLib技术来增强类,增强的类越多,对方法区的容量要求就越大,就越可能出现方法区的OOM异常。

解决方法

因为该OOM原因比较简单,解决方法有如下几种:

  1. 检查是否永久代空间或者元空间设置的过小;
  2. 检查代码中是否存在大量的反射操作;
  3. dump之后通过mat检查是否存在大量由于反射生成的代理类;
  4. 重启JVM。

本机内存溢出

以上OOM异常都是出现于JVM内部,那么如果是机器本身分给JVM的内存不够导致溢出呢。

机器本身分给JVM的内存容量可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值(-Xmx指定一样)。

可以通过反射获取Unsafe实例进行内存分配,测试代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true) {
            unsafe.allocateMemory(1024 * 1024);
        }
    }

运行结果如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at Main.main(Main.19)

有DirectMemory导致的内存溢出,在Heap Dump文件中不会看到明显的异常,如果发现OOM之后的dump文件很小,可以考虑一下是否是这方面的原因。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-02-28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
无显示器玩转树莓派桌面版
zhaoolee在Github开启了长篇连载《树莓派不吃灰》https://github.com/zhaoolee/pi 目前已经更新到18篇,主要是给树莓派刷Ubuntu当做家庭服务器用。
zhaoolee
2023/07/11
6090
无显示器玩转树莓派桌面版
树莓派构建无线打印服务器
如果没有你的打印机驱动也不要紧,上网搜搜PPD文件来告诉CUPS如何配置你的打印机即可。这个文件,我也没有测试过,可以肯定的是有这个东西~~~,如果你编译不了,自带的驱动也没有你的型号,可以测试~~~~~~
云深无际
2021/04/14
3.6K0
树莓派构建无线打印服务器
打印机+树莓派=网络打印机?
  之前买了台pi zero,然后一直仍在家里当网站服务器,感觉有点浪费。正好家里有台打印机,于是找了根OTG线连接打印机实现局域网内共享打印机。
xcsoft
2021/07/14
3.6K1
Linux下用CUPS的打印机服务
目前的Linux发行版本基本上都使用cups作为Linux下管理打印的服务应用。CUPS软件为Unix/Linux用户提供了有效而可靠的方式来管理打印的方法。它生来就支持IPP,并有LPD、SMB和JetDirect接口。CUPS本身可以提供网络打印机功能,使用它可以非常方便的令Linux与Linux之间、Linux与Windows之间实现打印共享。
用户1685462
2021/07/28
4.3K0
树莓派:漂洋过海来看你
给树莓派连上显示器和键盘鼠标,就可以像使用一台电脑一样使用它了。但很多时候,我们是把体积小巧的树莓派当做一个便携设备来使用的。这种时候,用户可不希望随身带着体积庞大的鼠标、键盘和显示器。如果能用手中的
Vamei
2018/01/18
2.5K0
树莓派:漂洋过海来看你
《树莓派4B家庭服务器搭建指南》第十七期:树莓派配合性能更好的闲置笔记本搭建私人影院
Jellyfin是一款开源免费的私人影院(影音媒体管理)系统,可以帮我们把硬盘里的影视资源管理起来,并添加精美的海报.
zhaoolee
2023/07/11
1.5K0
《树莓派4B家庭服务器搭建指南》第十七期:树莓派配合性能更好的闲置笔记本搭建私人影院
将树莓派3B刷成OpenWrt软路由,成为魔法WiFi上网的强大路由器
今天刷油管,我看到了大量带货软路由的视频,心动不已,但看了售价后,我决定让树莓派3B重出江湖!
zhaoolee
2021/03/02
18.1K2
将树莓派3B刷成OpenWrt软路由,成为魔法WiFi上网的强大路由器
利用树莓派搭建超级路由器
我们生活中常见的都是一些比较常见的家庭的路由器。而我又喜欢动手DIY,可又苦于没有钱😂。想玩一些比较高级的东西只能望洋兴叹!!! image.png 准备 树莓派4b Openwrt镜像(文末附下载链接) TF卡 读卡器 写入系统 利用Win32DiskImager将下载下来的openwrt镜像写入TF卡。 image.png 启动 将TF卡插入树莓派。通电即可! 登录 用网线连接pc和树莓派,浏览器访问192.168.1.1看到登录页面,说明写入成功。 image.png 用户名:root 密码:pass
逍遥子大表哥
2021/12/19
6320
利用树莓派搭建超级路由器
利用Ubuntu主机搭建共享打印服务
实验室的打印机自带的无线打印功能不太好用, 基本上大家都处于一种时断时续的薛定谔状态, 惠普smart一次又一次的用行动证明了这玩意实在不是很smart, 所以用 linux 搭建一个共享打印机服务或许是个不错的选择.
叶子Tenney
2023/04/05
6.1K0
利用Ubuntu主机搭建共享打印服务
如何快速实现异地不同网络打印机共享
内网打印机的不同电脑共享比较简单,但是工作生活中经常会出现不同局域网的打印机需要共享的情况,下面我们通过一个特殊的办法实现异地局域网共享打印机。
用户5084575
2019/04/23
2.9K0
如何快速实现异地不同网络打印机共享
《树莓派4B家庭服务器搭建指南》第十八期:代理Windows台式机支持Remote Desktop外网远程桌面连接, 随时玩转Stable Diffusion WebUI
最近几天, zhaoolee在家中Windows台式机折腾Stable Diffusion WebUI , 为了出门在外也能访问Windows台式机的Stable Diffusion WebUI, 我打算用树莓派代理台式机的3389端口,将其映射到公网上,以便随时访问Windows台式机(文末有Stable Diffusion出的图)。
zhaoolee
2023/07/11
1.1K0
《树莓派4B家庭服务器搭建指南》第十八期:代理Windows台式机支持Remote Desktop外网远程桌面连接, 随时玩转Stable Diffusion WebUI
打印机安全研究(一):不容乐观的网络打印机安全状况
打印机是人们在生活和办公中经常使用的电子设备,家庭、办公室、公司、政府单位、医院、学校......几乎每一个单位和机构都会使用打印机。从安全的角度来看,由于打印设备部署于内部网络,通过它们可以直接访问
FB客服
2018/02/09
2.8K0
打印机安全研究(一):不容乐观的网络打印机安全状况
树莓派4B如何手动固定IP地址
在使用树莓派的过程中,DHCP往往会自动分配树莓派的IP,因此树莓派的IP地址并不是固定的,那么每次在远程登录树莓派前都需要查看一下树莓派的IP地址,非常麻烦。因此,我们手动给树莓派设定一个静态IP地址后,树莓派的IP地址就是固定的了。
全栈程序员站长
2022/09/06
3.8K0
树莓派4B如何手动固定IP地址
树莓派4裸机基础教程:环境搭建
树莓派4作为一款学习嵌入式arm开发的开发板,是非常不错的选择。嵌入式开发往往需要的不仅仅是理论知识,还需要动手操作,然后实际体验效果。由于目前开发板要么资料太少,要么板子太贵,或者可玩性太低,所以嵌入式的入门和深入一直都是非常困难的问题。我写树莓派4裸机基础教程、树莓派4驱动进阶、树莓派4的RTOS这一些列的文章,也是希望借此机会,和大家分享一下嵌入式开发过程的方方面面,也希望对学习嵌入式感兴趣的人在阅读完成这些文章中会有所收获。由于树莓派4的外设,相对于前代的树莓派2、树莓派3等标准许多,完全可以作为学习嵌入式,学习arm编程的不错选择。所谓万变不离其宗,学会树莓派4的嵌入式开发,以后做其他的芯片的底层开发时,也是可以借鉴这种思想的。
bigmagic
2020/09/18
2.6K1
3个有用的树莓派网络项目
尽管树莓派是全世界电脑爱好者的挚爱伴侣,但它没有得到足够的赞誉。事实上,各种类型的单板计算机都没有得到应有的效果——我只是碰巧有一个树莓派。正是在对我树莓派所在的空间角落里匆匆瞥一眼,完成了我分配的任务,我才考虑我想的更高级的项目。
天然 8129060
2021/01/29
2K0
树莓派初次使用(史上最全最详细教程!!)「建议收藏」
由于自己电脑装虚拟机莫名的卡,所以搞个树莓派来当我的私密环境(嘿嘿,别想歪了!!!)废话不多说,我们开始吧!
全栈程序员站长
2022/11/04
2.7K0
树莓派无屏幕无线远程
同样,在SD卡的根目录(boot)新建“wpa_supplicant.conf”文件;
菜菜有点菜
2022/03/17
7420
树莓派无屏幕无线远程
利用树莓派+AdGuard屏蔽小米广告
一旦启动并运行,您可以在浏览器中输入以下内容,在端口3000上访问您的AdGuard Home Web界面 - http://192.168.10.20:3000/进行安装。
逍遥子大表哥
2021/12/17
4.1K0
利用树莓派+AdGuard屏蔽小米广告
uos访问windows共享打印机_Linux打印机安装命令
创作立场声明:个人瞎折腾,文中部分内容来自网络,本人并非专业人士,只是将个人的折腾经验分享给大家,如有错误请大家指正
全栈程序员站长
2022/11/07
6.1K0
树莓派介绍以及FAQ【这是我见过最全的树莓派教程】
树莓派是什么? 树莓派(Raspberry Pi)是尺寸仅有信用卡大小的一个小型电脑,您可以将树莓派连接电视、显示器、键盘鼠标等设备使用。 树莓派能替代日常桌面计算机的多种用途,包括文字处理、电子表格、媒体中心甚至是游戏。并且树莓派还可以播放高至 4K 的高清视频。 我们希望将树莓派推广给全世界的青少年电脑爱好者,用于培养计算机程序设计的兴趣和能力。
全栈程序员站长
2022/11/04
5K0
推荐阅读
相关推荐
无显示器玩转树莓派桌面版
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验