【java开发】什么是内存溢出和内存泄漏?如何解决?

news/2024/11/14 22:42:51/文章来源:https://www.cnblogs.com/o-O-oO/p/18546991
一、内存溢出1.1 导致的原因1.2 解决方法二、内存泄漏2.1 导致的原因2.2 解决方法2.3 示例代码三、对比四、总结

内存溢出和内存泄漏是我们经常听到的两种内存管理问题,那么,它们是如何导致的?又该如何解决?这篇文章,我们来聊一聊。

一、内存溢出

内存溢出(OutOfMemoryError)是指程序在运行时尝试分配内存,但由于没有足够的内存可用,Java 虚拟机(JVM)抛出了 OutOfMemoryError 错误。常见的内存溢出区域包括堆内存和永久代(在 Java 8 之后被元空间取代)。

1.1 导致的原因

导致内存溢出主要有以下几个原因:
1、堆内存溢出:创建大量对象,导致堆内存耗尽。
2、栈内存溢出:递归调用过深,导致栈内存耗尽。
3、永久代/元空间溢出:类加载过多,导致永久代/元空间耗尽。

下面我们用三个示例,分别展示了堆内存溢出、栈内存溢出和永久代/元空间溢出的情况:
堆内存溢出:

如下示例代码,通过不断向 ArrayList 添加对象来耗尽堆内存。

import java.util.ArrayList;
import java.util.List;public class HeapMemoryOverflow {public static void main(String[] args) {List<Object> list = new ArrayList<>();while (true) {list.add(new Object());}}
}

在运行上述 HeapMemoryOverflow 示例时,可能需要调整 JVM 参数以较小的堆大小运行,例如 -Xmx10m,以更快地观察到 OutOfMemoryError。
栈内存溢出:

如下示例代码,通过递归调用一个没有终止条件的方法,导致栈内存溢出。


public class StackMemoryOverflow {public static void main(String[] args) {recursiveMethod();}public static void recursiveMethod() {// 没有终止条件的递归调用recursiveMethod();}
}

运行StackOverflowError代码,通常会很快发生栈内存溢出,因为默认的栈大小不大。
永久代/元空间溢出:

在 Java 8 之前,永久代溢出可以通过动态生成大量类来模拟,Java 8 之后,永久代被元空间取代,以下是一个使用 CGLIB 动态生成类的示例,可能导致元空间溢出,需要添加 CGLIB 库依赖。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;public class MetaspaceOverflow {public static void main(String[] args) {while (true) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(DummyClass.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {return proxy.invokeSuper(obj, args);}});enhancer.create();}}static class DummyClass {}
}

运行 MetaspaceOverflow 示例时,可以使用 JVM 参数 -XX:MaxMetaspaceSize=10m 来限制元空间大小,以更快地观察到溢出。

1.2 解决方法

在这里,我们只是给了一个大的思路,关于内存溢出的排查工作也是一个很重要的知识点,我们会在后面的文章中去详细介绍。

增加内存:调整 JVM 参数增加堆内存大小,如 -Xmx。
优化代码:减少不必要的对象创建,优化数据结构。
检查递归:避免过深的递归调用。
监控和分析:使用工具如 JVisualVM、JProfiler 分析内存使用情况。

二、内存泄漏

内存泄漏(Memory Leak)是指程序中存在一些对象,它们不再被使用,但由于仍然被引用,垃圾回收器无法回收这些对象。因此,随着时间的推移,内存泄漏会导致可用内存逐渐减少,最终可能导致内存溢出。

2.1 导致的原因

导致内存泄漏主要有以下几个原因:

静态集合类:使用 static 修饰的集合类持有对象引用,因为静态集合的生命周期和 JVM 一致,所以静态集合引用的对象不能被释放。
监听器和回调:注册的监听器或回调未被移除。
长生命周期对象持有短生命周期对象:长生命周期对象不当持有短生命周期对象的引用。

下面我们用三个示例,分别展示了内存泄漏可能发生的场景:
静态集合类导致的内存泄漏

静态集合类持有对象引用,导致这些对象无法被垃圾回收。

import java.util.ArrayList;
import java.util.List;public class StaticCollectionLeak {// 静态集合持有对象引用private static List<Object> objectList = new ArrayList<>();public static void main(String[] args) {for (int i = 0; i < 10000; i++) {// 每次创建一个新对象并添加到静态集合中objectList.add(new Object());}// 即使在这里试图清理掉一些其他的引用System.gc();  // 这些对象仍然无法被回收,因为它们被静态集合引用}
}

监听器和回调未被移除

注册的监听器或回调未被移除,导致内存泄漏。

import java.util.ArrayList;
import java.util.List;public class ListenerLeak {private List<EventListener> listeners = new ArrayList<>();public void addListener(EventListener listener) {listeners.add(listener);}public void triggerEvent() {for (EventListener listener : listeners) {listener.onEvent();}}public static void main(String[] args) {ListenerLeak leakExample = new ListenerLeak();// 匿名类创建的监听器对象leakExample.addListener(new EventListener() {@Overridepublic void onEvent() {System.out.println("Event triggered");}});// 假设在某个时候不再需要监听器,但未移除// listeners.remove(listener); // 应该移除不需要的监听器}
}interface EventListener {void onEvent();
}

长生命周期对象持有短生命周期对象

长生命周期对象不当持有短生命周期对象的引用,导致短生命周期对象无法被回收。

import java.util.HashMap;
import java.util.Map;public class LongLifeCycleLeak {private static Map<String, byte[]> cache = new HashMap<>();public static void main(String[] args) {while (true) {// 短生命周期对象byte[] data = new byte[1024 * 1024]; // 1MB// 长生命周期对象持有短生命周期对象的引用cache.put(String.valueOf(System.nanoTime()), data);// 需要定期移除不再需要的数据,否则会导致内存泄漏// cache.clear(); // 应该在适当时机清理缓存}}
}

2.2 解决方法

在这里,我们只是给了一个大的思路,关于内存泄漏的排查工作也是一个很重要的知识点,我们会在后面的文章中去详细介绍。

及时释放引用:确保不再使用的对象引用被清除。
使用弱引用:对缓存或非关键对象使用 WeakReference。比如 ThreadLocal 的弱引用会导致内存泄漏,因此使用完 ThreadLocal 一定要记得使用 remove 方法来进行清除。
正确管理生命周期:特别是监听器和回调,确保在不需要时移除。

2.3 示例代码

下面示例代码,用于测试内存泄漏。

import java.util.HashMap;
import java.util.Map;public class MemoryLeakExample {private static Map<Integer, String> map = new HashMap<>();public static void main(String[] args) {for (int i = 0; i < 100000; i++) {map.put(i, "value" + i);}}
}

在上面的代码中,如果 map 是一个长期存在的静态变量,并且没有及时清理,则可能导致内存泄漏。

三、对比

关于内存溢出和内存泄漏的比较如下:

触发时机:内存溢出通常在内存耗尽时立即触发,而内存泄漏可能在一段时间后逐渐显现。
影响范围:内存溢出会立即影响程序的可用性,而内存泄漏通常是一个逐步积累的问题。
检测难度:内存溢出较容易检测,而内存泄漏往往需要深入分析和调试。
解决复杂度:内存溢出的解决相对简单,通常通过优化内存使用或增加内存即可。而内存泄漏的解决需要识别并清理不必要的引用,可能涉及更复杂的代码重构。

四、总结

本文,我们分析了Java的内存溢出和内存泄漏并且应示例展示了它们导致的原因,应该说它们是比较常见的内存管理问题,如果在生产环境出现也是比较头疼的问题。所以在日常开发中,我们一定要注意自己的代码风格和代码质量,尽量避免这些问题的发生。

原创 猿java

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/833680.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

关于NVIDIA Jetson AGX Xavier刷机过程记录

刷机记录,再刷机忘记了回来看看~主机电脑安装ubuntu20.04虚拟机,NVIDIA sdkmanager,主机端连上路由器提供的wifi。再将Jetson接上电源,但不开机。拿出附带的typec——USB数据线,typec端连上图中的typec口,USB连上主机电脑。找出一根网线,一端连接Jetson,另一端连接步骤…

shell编程 - 基础篇

1. Shell简介Shell是一个C语言编写的脚本语言,它是用户与Linux的桥梁,用户输入命令交给Shell处理,Shell将相应的操作传递给内核(Kernel),内核把处理的结果输出给用户。2. Shell编程语言必知必会shell命令解释器:bash编程常用命令解释器.命令解释器bash 目前应用最广泛一…

第四届光学与机器视觉国际学术会议(ICOMV 2025) 2025 4th International Conference on Optics and Machine Vision

第四届光学与机器视觉国际学术会议(ICOMV 2025)2025 4th International Conference on Optics and Machine Vision重要信息官网:https://ais.cn/u/vEbMBz

【os】操作系统是怎样一步步接收键盘按键的?

你有没有想过,按下键盘按键后,相应的字符是怎么一步步显示在屏幕上的? 首先来看硬件部分,你至少应该能想到必须得有键盘和CPU:之后呢,cpu是怎么知道有键盘按下呢? 为了让键盘按下按键后能通知到CPU,需要借助键盘控制器,keyboard controller,这当然也是硬件:当按下按…

Kafka学习day01

Kafka的学习day01-Kafka基础环境的搭建Kafka Centos7环境搭建 1. 安装Zookeeper 1.1 官网下载安装包ZooKeeper官网下载地址1.2 使用Xftp或远程工具将ZooKeeper安装包上传文件到服务器或虚拟机1.3 编写配置文件 进入ZooKeeper安装目录 cd {安装目录}/conf/1.3.1 配置文件模版 # …

从数据到知识,知识中台赋能企业智能化升级

在信息爆炸的时代,企业面临着数据泛滥与知识匮乏的双重挑战。如何将海量的数据转化为有价值的知识,进而驱动企业的智能化升级,已成为企业竞争力的关键。知识中台作为企业数字化转型的核心,正逐渐成为企业智能化升级的新引擎。 一、数据与知识的转化 数据本身并不等同于知识…

基于Java+SpringBoot+Mysql在线课程学习教育系统功能设计与实现十

审核前台用户认证信息、查看所有用户、订单、发布文章、发布常见问题等。 该系统总共24张表,代码整洁,每个功能、接口上都有注释说明。 运行环境:jdk1.8、mysql5.x、eclipse/idea、maven3.5/3.6 包远程运行的哦。 特色功能:发布课程、学习课程、分享资料、资料讨论等。 部分…

基于Java+SpringBoot+Mysql在线课程学习教育系统功能设计与实现九

该系统总共24张表,代码整洁,每个功能、接口上都有注释说明。 运行环境:jdk1.8、mysql5.x、eclipse/idea、maven3.5/3.6 包远程运行的哦。 特色功能:发布课程、学习课程、分享资料、资料讨论等。 部分功能:前台课程评论信息控制器Controller、优惠卷信息控制器Controller、…

SharePoint Online页面的一些奇怪参数

前言最近,在查找资料的时候,偶然间发现了一些非常有意思的参数,如下:?env=Embedded or ?env=WebView&wdStartOn=21.正经的SharePoint Online页面2.加了参数的SharePoint Online 页面3.加了另一个参数的SharePoint Online页面结束语相信大家看效果就已经发现了,参数是…

Alpha冲刺(2/14)——2024.11.13

目录一、团队成员分工与进度二、成员任务问题及处理方式三、冲刺会议内容记录会议内容四、GitHub签入记录及项目运行截图GitHub签入记录项目运行截图五、项目开发进展及燃尽图项目开发进展燃尽图六、团队成员贡献表 一、团队成员分工与进度成员 完成的任务 完成的任务时长 剩余…

PS端Flash固化

PS端Flash固化Vivado版本:Vivado2020.2 芯片型号:RFSoC XCZU47DR 前提条件:Vitis工程编译完成,拨码开关拨到PS JTAG模式创建引导镜像 首先右键应用工程系统,点击Create Boot Image。检查镜像工程的文件是否为固化需要的工程文件,点击创建镜像的选项即可完成创建,创建完成…

Office Word 文档格式与目录样式(毕业设计论文常用)

调整格式技巧: Word 中点击 “文件”--》"选项"--》“显示”,将高亮部分全部打钩,有利于查看格式字符、 “分页符” 和“分节符” 两个很有用, 其中分节符 前后的页码是独立的。 样式间的关系: 类比 C++ 中类的继承编写的伪代码,“正文”为基类,派生出 “论文…