当前位置:网站首页>JVM内存泄漏和内存溢出的原因
JVM内存泄漏和内存溢出的原因
2022-08-09 12:22:00 【Java技术债务】
目录
1 概念
- 内存泄漏: 分配出去的内存没有被回收回来,失去对内存区域的控制,造成资源的浪费,比如:new出来了对象并没有引用,垃圾回收器不会回收他,造成内存泄漏
- 内存溢出: 程序所需要的内存超出了系统所能分配的内存。2 分析内存溢出可能出现的地方 从 Java代码的运行过程来看,有三个区域会发生 OOM,它们分别是:Metaspace、Java 虚拟机栈、堆内存。 Java栈
虚拟机栈,每执行一个方法都会有一个栈帧入栈,栈帧中包含参数、局部变量、返回值地址等信息。如果代码层次太深,不断有方法入栈却没有出栈,Java虚拟机栈就会OOM。
- 虚拟机中的栈内存也是有限的,我们调用方法的时候会创建一个栈帧,紧接着方法入栈。如果一个线程一直调用方法入栈,栈内存终归是要满的,此时线程的栈中就会发生 OOM。
- 发生这种情况一般就是代码除了问题,比如写了个递归调用,和 Metaspace 的内存溢出一样,也很少发生。
- 如果在单线程的情况下,无论是栈帧太大还是虚拟机栈容量太小,当内存无法再分配的时候,虚拟机抛出的是StackOverflowError异常。
- 如果在多线程下,不断地建立线程可能会产生OutOfMemoryError异常。
Metaspace
保存类的基本信息,如果加载太多类就会 OOM
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
回收废弃常量与回收 Java 堆中的对象非常类似。以常量池中字面量的回收为例,假如一个字符串”abc”已经进入了常量池中,但是当前系统没有任何一个 String 对象是叫做”abc”的,也没有其他地方引用了这个字面量,如果这时发生内存回收,而且必要的话,这个”abc”常量就会被系统清理出常量池。常量池中的其他类(接口)、方法、字段的符号引用也与此类似。
注意:在大量使用反射、动态代理、CGLib 等 ByteCode 框架、动态生成 JSP 以及 OSGi这类频繁自定义 ClassLoader 的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。
堆
堆中创建的对象过多就会触发GC,GC 的速度赶不上新建对象的速度也会发生 OOM。
- 高并发场景下,请求量太大,创建了大量新的对象,且这些都是有用的、存活的。堆中无法放入更多对象就会导致堆内存溢出
- 内存泄漏问题,长生命周期的对象引用了大量短生命周期的对象,没有及时取消对它们的引用,导致 GC 无法回收这些理应被回收的对象,就导致了堆内存溢出
- Java堆中只会产生OutOfMemoryError异常。
注意:类需要同时满足下面 3 个条件才能算是“无用的类”
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样,不使用了就必然会回收。
注意:方法区溢出方法区中只会产生OutOfMemoryError异常。
2 分析内存泄漏的原因
原因:
长生命周期对象持有短生命周期对象的引用可能会引起内存泄漏
1、静态集合类:容器使用时引起的内存泄漏
HashMap、Vector等很容易出现内存泄漏, 集合被定义成静态的时候,由于它们的生命周期跟应用程序一样长 他们引用的所有对象Object不能被释放,如果将Object对象置为null,也还是会被Vector引用,可以将Vector对象置为null,切出对静态集合类的引用。
Vector vector = new Vector();
for (int i = 1; i<100; i++) {
Object object = new Object();
vector.add(object);
object = null;
}
//这样会造成内存泄漏
//...对vector的操作
//...与vector无关的其他操作这样会造成短暂的内存泄漏,method方法结束后被回收,
//...对vector的操作
vector = null;
//...与vector无关的其他操作
2、各种连接时:未正确使用close()方法导致的内存泄漏
各种IO或者数据库连接时,最后都需要close()释放对象,这样也是长对象引用短对象,造成的内存泄漏。
SessionFactory factory = new SessionFactory();
try {Session session = factory.connect();
} finally{
session.close();
}
这里必须用close关闭连接,因为SessionFactory是长对象,session是短对象。
3、外部模块的引用
调用外部模块的时候,也应该注意防止内存泄漏。如模块A调用了外部模块B的一个方法,如:public void register(Object o)。这个方法有可能就使得A模块持有传入对象的引用,这时候需要查看B模块是否提供了去除引用的方法,如unregister()
4、单例模式
使用单例模式的时候也有可能导致内存泄漏。因为单例对象初始化后将在JVM的整个生命周期内存在,如果它持有一个外部对象(生命周期比较短)的引用,那么这个外部对象就不能被回收,而导致内存泄漏。如果这个外部对象还持有其它对象的引用,那么内存泄漏会更严重
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
- 如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
- 如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。
正确的方式应该改为下面这种方式:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
// 使用Application 的context
this.context = context.getApplicationContext();
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
或者这样写,连 Context 都不用传进来了: 在你的 Application 中添加一个静态方法,getContext() 返回 Application 的 context,
...
context = getApplicationContext();
...
//获取全局的context - - >return返回全局context对象
public static Context getContext(){
return context;
}
public classAppManager{
private static AppManager instance;
private Context context;
private AppManager() {
// 使用Application 的context
this.context = MyApplication.getContext();
}
public static AppManager getInstance() {
if (instance != null) {
instance = new AppManager();
}
return instance;
}
}
如何解决以及监控JVM内存、CPU的情况,请关注下篇文章。。。
边栏推荐
- 1-hour live broadcast recruitment order: industry big names share dry goods, and enterprise registration opens丨qubit·viewpoint
- 智驾科技完成C1轮融资,此前2轮已融4.5亿元
- AI basketball referee, walking is special, ask harden care don't care
- 注:检测到当前使用的ADB不是HBuilder内置或自定义ADB:PID为:9544进程名称为:adb.exe 路径为:c:\users\administrator\appdata\local\and
- Flutter Getting Started and Advanced Tour (8) Button Widget
- 用 API Factory 产品生成 API 文档
- 放下手机吧:实验表明花20分钟思考和上网冲浪同样快乐
- Nature:猪死亡1小时后,器官再次运转
- Go 事,如何成为一个Gopher ,并在7天找到 Go 语言相关工作,第1篇
- MySQL 原理与优化,Group By 优化 技巧
猜你喜欢
AQS同步组件-FutureTask解析和用例
Manchester city launch emotional intelligence scarf can be detected, give the fans
你没见过的《老友记》镜头,AI给补出来了|ECCV 2022
ABAP 报表中如何以二进制方式上传本地文件试读版
【Untitled】
8、IDEA提交代码出现: Fetch failed fatal: Could not read from remote repository
智驾科技完成C1轮融资,此前2轮已融4.5亿元
链表噩梦之一?5000多字带你弄清它的来龙去脉
The latest interview summary in 20022 brought by Ali senior engineer is too fragrant
Flutter入门进阶之旅(二)Hello Flutter
随机推荐
Ten minutes to teach you how to use VitePress to build and deploy a personal blog site
#物联网征文#小熊派设备开发实战
ERP不规范,同事两行泪 (转载非原创)
听声辨物,这是AI视觉该干的???|ECCV 2022
ABAP 报表中如何以二进制方式上传本地文件试读版
曲鸟全栈UI自动化教学(八):框架代码讲解和进一步优化
用 API Factory 产品生成 API 文档
Golang学习之路(五):Golang的函数
Byte Qiu Zhao confused me on both sides, and asked me under what circumstances would the SYN message be discarded?
26. Pipeline parameter substitution command xargs
GPT-3组合DALL·E,60秒内搞定游戏设定和原型动画!网友看后:这游戏想玩
MongoDB-查询中$all的用法介绍
We really need DApp?Really can't meet our fantasy App?
How should the acceptance criteria for R&D requirements be written?| Agile Practices
内网穿透工具ngrok使用教程
链表噩梦之一?5000多字带你弄清它的来龙去脉
Flutter Getting Started and Advanced Tour (4) Text Input Widget TextField
The core key points of microservice architecture
无需精子卵子子宫体外培育胚胎,Cell论文作者这番话让网友们炸了
Customize VIEW to realize in-app message reminder to rotate up and down