OutOfMemoryError异常总结

JVM内存区域中,除了程序计数器外,其他几个运行时区域都有可能发生OutOfMemoryError(OOM)异常

(1)堆溢出

原因:Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,对象数量达到最大堆容量限制,则发生溢出。

解决:要解决这个区域的异常,一般是先使用内存分析工具(IDEA中的JProfiler插件),对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是有必要的,也就是确认到底是“内存泄漏”还是“内存溢出”

如果是内存泄漏,就用上述插件进一步找到泄露对象到GC Roots的引用连,于是就能知道泄露对象是通过怎样的路径与GC Roots相关联,并导致垃圾回收器GC无法自动回收它们的,也就准确的定位出泄露代码的位置。

如果不存在泄露,换就话说,就是内存中的对象确实都还必须存活,一方面:检查虚拟机的堆参数,将堆参数与计算机物理内存对比一下,看看是否还可以把堆参数调大一点。另一方面:从代码上检查看看是否有哪些对象的生命周期过长,以尝试减少程序运行期的内存消耗。

https://zhuanlan.zhihu.com/p/368830445

第一步 首先确认逻辑问题,

jmap -heap pid

查看内存中对象的数量和大小,判断是否在合理的范围,如果在合理的范围内,增大内存配置,调整内存比例就可以了。

第二步:分析gc是否正常执行

jstat -gcutil <pid> 1000


从这里观察gc是否异常,也可以根据这个进行jvm内存分配调优,来提高性能降低gc对性能的损耗

第三步 确认下版本新增代码的改动,尽快从代码上找出问题。

第四步:开启各种命令行和 导出 dump 各种工具分析

内存溢出和内存泄露的区别与联系

内存溢出:(out of memory)通俗理解就是内存不够,指程序要求的内存超出了系统所能分配的范围,通常在运行大型软件或游戏时,软件或游戏所需要的内存远远超出了你主机内安装的内存所承受大小,就叫内存溢出。比如申请一个int类型,但给了它一个int才能存放的数,就会出现内存溢出,或者是创建一个大的对象,而堆内存放不下这个对象,这也是内存溢出。

 

java虚拟机栈:

两种异常:线程请求的深度大于虚拟机允许深度,StackOverFlowError,
无法申请到足够的空间:OutOfMemoryError
本地方法栈
无法申请到足够的空间:OutOfMemoryError unable to cteate new native thread
如果无法申请到足够的空间抛出OutOfMemoryError : Java heap space
 方法区
JDK 1.7之前   永久代  如果如法申请到足够的空间抛出OutOfMemoryError :PermGen space
直接内存
不会受到Java堆内存限制,但会受到机器总内存的限制
如果如法申请到足够的空间抛出OutOfMemoryError

内存泄漏:(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

因此,我们从上面也可以推断出内存泄露可能会导致内存溢出。

java 内存泄漏:  GC只会回收没有被引用或者根集不可到达的对象(取决于GC算法)

如果长生命周期的对象持有短生命周期对象的引用,就很可能会出现内存泄露

复制代码
public class Simple {
 
    Object object;
 
    public void method1(){
        object = new Object();
    //...其他代码
    }
}
复制代码

这里的object实例,其实我们期望它只作用于method1()方法中,且其他地方不会再用到它,但是,当method1()方法执行完成后,

object对象所分配的内存不会马上被认为是可以被释放的对象,只有在Simple类创建的对象被释放后才会被释放,严格的说,这就是一种内存泄露。解决方法就是将object作为method1()方法中的局部变量

在内存对象明明已经不需要的时候,还仍然保留着这块内存和它的访问方式(引用),这是所有语言都有可能会出现的内存泄漏方式 

(关内静 单存哈)

 1.静态集合类  

如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。

生命周期长的对象持有短生命周期对象的引用,尽管短生命周期的对象不再使用,但是因为长生命周期对象持有它的引用而导致不能被回收

 

  2 各种提供了close()方法的对象

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,以及使用其他框架的时候,除非其显式的调用了其close()方法(或类似方法)将其连接关闭,否则是不会自动被GC回收的。

其实原因依然是长生命周期对象持有短生命周期对象的引用。

 

 

3单例模式导致的内存泄露

单例模式,很多时候我们可以把它的生命周期与整个程序的生命周期看做差不多的,所以是一个长生命周期的对象。如果这个对象持有其他对象的引用,也很容易发生内存泄露。

4内部类对象持有外部类对象的引用

 

在Java中内部类的定义与使用一般为成员内部类与匿名内部类,他们的对象都会隐式持有外部类对象的引用,影响外部类对象的回收。

 

GC只会回收没有被引用或者根集不可到达的对象(取决于GC算法),内部类在生命周期内始终持有外部类的对象的引用,造成外部类的对象始终不满足GC的回收条件,反映在内存上就是内存泄露

 

5改变哈希值

改变哈希值,当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,
在这种情况下,即使在contains方法使用该对象的当前引用作为的参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中单独删除当前对象,造成内存泄漏。
这也是String为什么被设置成了不可变类型,我们可以放心的把String存如HashSet,或者把String当做HashMap的key值;

6缓存泄漏

内存泄漏的另一个常见来源是缓存,一旦你把对象引用放入到缓存中,他就很容易遗忘

(2)虚拟机栈和本地方法栈溢出

多线程下的内存溢出,与栈空间是否足够大并不存在任何联系。为每个线程的栈分配的内存越大(参数-Xss),那么可以建立的线程数量就越少,建立线程时就越容易把剩下的内存耗尽,越容易内存溢出。在这种情况下,如果不能减少线程数目或者更换64位虚拟机时,减少最大堆和减少栈容量能够换区更多的线程

posted on   cltt  阅读(323)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2020-02-19 Backpropagation
2020-02-19 a brief introduction of deep learning
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示