内存泄漏和内存溢出

概述

内存泄漏:是指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。通常少次数的内存无法及时回收并不会到程序造成什么影响,但是如果在内存本身就比较少获取多次
导致内存无法正常回收时,就会导致内存不够用,最终导致内存溢出。
内存溢出:OOM指程序申请内存时,没有足够的内存供申请者使用 1M 实际要占用 2M 内存,就说分配的内
存不够,导致内存溢出,给了你一块存储 int 类型数据的存储空间,但是你却存储 long 类型的数据,长期出现内存泄漏,导致系统内存越用越少,最终导致内存不够用,导致系统崩溃,出现 OOM
区别
内存泄露:是指对象实例在新建和使用完毕后,仍然被引用,没能被垃圾回收释放,一直累计只到没有剩余的内存可用;
内存溢出:当我们新建一个实例对象时,实例对象所需占用的内存空间大于堆的可用空间.

内存异常的情况

方法区内存异常
报错方式
Exception in thread “main” java.lang.OutOfMemoryError: Metaspace
原因
异常导致-XX:MetaspaceSize或-XX:MaxMetaspaceSize配置不足
解决思路
当不通过日志文件主观来判断出问题代码时,通过分析dump文件来分析

Java栈内存异常
报错方式
Exception in thread “main” java.lang.StackOverflowError
原因
对象过大或过多异常,导致-Xss配置的内存不足
是否有递归调用
是否有大量循环或死循环
全局变量是否过多
数组、List、map数据是否过大

堆内存异常
报错方式
java.lang.OutOfMemoryError: Java heap space
原因
异常导致-Xms或-Xmx配置不足
使用了大量的递归或无限递归对象
使用了大量循环或死循环(循环中用到了大量的新建的对象)
使用了向数据库查询过多,可能会造成内存溢出。
有数组,List,Map中存放的是对象的引用而不是对象,这些引用会让对应的对象不能被释放。会大量存储在内存中。

内存溢出

内存溢出是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于虚拟机能提供的最大内存。
引起内存溢出的原因
jmap+MAT分析定位
引起内存溢出的原因有很多种
内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
集合类中有对对象的引用,使用完后未清空,使得 JVM 不能回收;
代码中存在死循环或循环产生过多重复的对象实体;
使用的第三方软件中的 BUG;
出现内存溢出如何排查
检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
检查代码中是否有死循环或递归调用。
检查是否有大循环重复产生新对象实体。
检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
启动参数内存值设定的过小;
先找到PID
ps -ef | grep java
jmap 转存快照
jmap -dump:format=b,file=/opt/dump/test.dump {PID}
当程序出现OutofMemory时,将会在相应的目录下生成一份dump文件,如果不指定选项HeapDumpPath则在当前目录下生成dump文件
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/opt/dumps
内存溢出的解决方案
第一步,修改 JVM 启动参数,直接增加内存。(-Xms,-Xmx 参数一定不要忘记加。)
第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。
第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。
总结:遇到线上问题,首先确认排查问题的思路:
查看日志
查看CPU情况 top
查看TCP情况 netstat
查看java线程,jstack
查看java堆,jmap
通过MAT分析堆文件,寻找无法被回收的对象
问题排查
1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取比较多条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
2.检查代码中是否有死循环或递归调用。
3.检查是否有大循环重复产生新对象实体。
4.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。
5.结合内存查看工具动态查看内存使用情况
问题解决
1.检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。对代码进行走查和分析,找出可能发生内存溢出的位置。修正。
2.修改JVM启动参数,直接增加内存。
第一个异常:设置堆的方法是通过-Xms(堆的最小值),-Xmx(堆的最大值)
第一个异常:设置栈大小的方法是设置-Xss参数-Xss100k
第三个异常:设置元空间-XX:PermSize和-XX:MaxPermSize参数(java8中去掉了PermGen(1.8前称永久代,主要用来
存放Class的静态信息,Main方法信息,常量信息,静态方法和变量信息,共享变量等信息
参数设置示例:
-XX:PermSize=5M -XX:MaxPermSize=7M)
改为 Metaspace 默认情况下,类元数据只受可用的本地内存限制(容量取决于是32位或是64位操作系统的可用虚拟内存大小)
新参数(MaxMetaspaceSize)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整)
重点排查以下几点:
1.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能
引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
2.检查代码中是否有死循环或递归调用。
3.检查是否有大循环重复产生新对象实体。
4.检查对数据库查询中,是否有一次获得全部数据的查询。一般来说,如果一次取十万条记录到内存,就可能
引起内存溢出。这个问题比较隐蔽,在上线前,数据库 数据较少,不容易出问题,上线后,数据库中数据多了,
一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。
5.检查 List、MAP 等集合对象是否有使用完后,未清除的问题。List、MAP 等集合对象会始终存有对对象的
引用,使得这些对象不能被 GC 回收。
第四步,使用内存查看工具动态查看内存使用情况。
栈内存溢出
栈是线程私有的,他的生命周期与线程相同,每个方法在执行的时候都会创建一个栈帧,用来存储 局部变量表,操作数栈,动态链接,方法出口等信息。局部变量表又包含基本数据类型,对象引用 类型 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflflowError异常,方法递归调用产生这种结果。 如果Java虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是无法申请到足够的内存去完成 扩展,或者在新建立线程的时候没有足够的内存去创建对应的虚拟机栈,那么Java虚拟机将抛出一
个OutOfMemory 异常。(线程启过多)
参数 -Xss 去调整JVM栈的大小

内存泄漏

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
什么是内存泄露,通俗的来说就是堆中的一些对象已经不会再被使用了,但垃圾收集器却无法将它们从内存中清除。内存泄漏很严重的问题,因为它会阻塞内存资源并随着时间的推移降低系统性能。如果不进行有效的处理,最终的结果将会使应用程序耗尽内存资源,无法正常服务,导致程序崩溃,抛出java.lang.OutOfMemoryError异常。堆内存中通常有两种类型的对象:被引用的对象和未被引用的对象。被引用的对象是应用程序中仍然具有活跃的引用,而未被引用的对象则没有任何活跃的引用。
垃圾收集器会回收那些未被引用的对象,但不会回收那些还在被引用的对象。这也是内存泄露发生的源头。
内存泄露往往有以下表象:当应用程序长时间连续运行时,性能严重下降;抛出OutOfMemoryError异常;程序莫名其妙的自动崩溃;应用程序耗尽链接对象;当然,如果打印GC日志,有些场景下还会看到频繁执行full GC等状况。
内存泄漏发生的案例:
ThreadLocal内存泄漏问题
HashMap自定义key 对象 避免内存泄漏问题
通过以上案例排查内存泄漏问题
排查思路:查找到java虚拟机 哪些对象占用空间最大 前20个 列出分析
内存泄漏是指不再被使用的对象或者变量一直被占据在内存中。理论上来说,Java是有GC垃圾回 收机制的,也就是说,不再被使用的对象,会被GC自动回收掉,自动从内存中清除。 但是,即使这样,Java也还是存在着内存泄漏的情况,java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露, 尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收 ,这就是java中内存泄露的发生场景

内存泄露场景
静态属性导致内存泄露、未关闭的资源、使用ThreadLocal、String的intern方法、finalize()方法
静态属性导致内存泄露
导致内存泄露的一种情况就是大量使用static静态变量。在Java中,静态属性的生命周期通常伴随着应用整个生命周期(除非ClassLoader符合垃圾回收的条件)。
下面来看一个具体的会导致内存泄露的实例:

public class StaticTest { 
    public static List<Double> list = new ArrayList<>(); 

    public void populateList() { 
        for (int i = 0; i < 10000000; i++) { 
            list.add(Math.random()); 
        } 
        Log.info("Debug Point 2"); 
    } 

    public static void main(String[] args) { 
        Log.info("Debug Point 1"); 
        new StaticTest().populateList(); 
        Log.info("Debug Point 3"); 
    } 
} 

如果监控内存堆内存的变化,会发现在打印Point1和Point2之间,堆内存会有一个明显的增长趋势图。
但当执行完populateList方法之后,对堆内存并没有被垃圾回收器进行回收。
因此,我们要十分留意static的变量,如果集合或大量的对象定义为static的,它们会停留在整个应用程序的生命周期当中。而它们所占用的内存空间,本可以用于其他地方。
那么如何优化呢?第一,进来减少静态变量;第二,如果使用单例,尽量采用懒加载。
未关闭的资源
无论什么时候当我们创建一个连接或打开一个流,JVM都会分配内存给这些资源。比如,数据库链接、输入流和session对象。忘记关闭这些资源,会阻塞内存,从而导致GC无法进行清理。特别是当程序发生异常时,没有在finally中进行资源关闭的情况。这些未正常关闭的连接,如果不进行处理,轻则影响程序性能,重则导致OutOfMemoryError异常发生。
finalize()方法
使用finalize()方法会存在潜在的内存泄露问题,每当一个类的finalize()方法被重写时,该类的对象就不会被GC立即回收。GC会将它们放入队列进行最终确定,在以后的某个时间点进行回收。
如果finalize()方法重写的不合理或finalizer队列无法跟上Java垃圾回收器的速度,那么迟早,应用程序会出现OutOfMemoryError异常。
假设某个类重写了finalize()方法,并且重写的方法在执行时需要一些时间。
String的intern方法
字符串常量池在Java7中从PermGen移动到了堆空间。在Java6及以前版本,我们使用字符串时要多加小心。
如果读取了一个大字符串对象,并且调用其intern方法,intern()会将String放在JVM的内存池中(PermGen),而JVM的内存池是不会被GC的。同样会造成程序性能降低和内存溢出问题。

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值