首先内存泄漏问题、内存溢出问题都可能会报 OOM(OutofMemoryError)

主要原因是堆空间不足

一、内存泄漏问题导致

1、内存泄漏:严格来说,只有对象不会再被程序用到了,但是 GC 又不能回收他们的情况,才叫内存泄漏
由于这些对象不能被垃圾回收掉,这样的对象多了,有可能就会 OOM。

举一个生活中的不太恰当的例子:假设你有一个妹妹她小时候买了一个很可爱的玩偶,现在她长大了,但是这个玩偶还是一直都没有扔掉它,家里人也不会去丢掉它 ,那么这样的玩偶多了,一直在家就可能把你家堆满(OOM)。这个例子里的这个玩偶就相当于一个没有引用的对象,这个对象(玩偶)程序都不用了,但是由于它和其中某一个有引用的对象有联系,所以垃圾回收时也不会去回收它。

内存泄漏出现的原因总结
(1)单例对象,这样存活时间很长的对象引用了使用时间很短的对象,那么这个被引用的对象就一直不能被回收,单例的生命周期和应用程序是一样长的,所以单例程序中,如果持有对外部对象的引用的话,那么这个外部对象是不能被回收的,则会导致内存泄漏的产生;
(2)各种连接资源对象,数据库连接(dataSourse . getConnection()),网络连接(socket)和io连接必须手动close,否则是不能被回收的,这些资源连接都要在finally{}中手动关闭;
(3)静态集合类,例子:

    static final ArrayList ARRAY_LIST = new ArrayList();
    public void addObject() {
        Object obj = new Object();// 原本obj对象对应的栈帧一出栈obj对象就会消除,未逃逸
        ARRAY_LIST.add(obj);     // 但是由于有静态集合的引用所有一直不能被消除,就出现了内存泄漏
    }


2、解决办法
(1)如果是内存泄漏,可进一步通过工具查看泄漏对象到 GC Roots引用链(引用链:可达路径)。于是就能找到泄漏对象是通过的路径与GC Roots 相关链并导致垃圾收集器无法自动回收它们。掌握了泄漏对象的类型信息,以及GC Roots引用链的信息,就可以比较准确定位出泄漏代码的位置。

(2)也可以通过jstack 查当前程序中当前所有的线程状态。

(3)查看程序停止后产生的日志文件(比如说tomcat根目录下的logs文件下的日志文件),假如有可能出现内存泄漏的线程系统就会打印出来。

3、"GC Roots"根集合:一组必须活跃的引用。

相关的知识:
(1)可达性分析算法:是以根对象集合(GC Roots) 为起始点,按照从上至下的方式进行广度优先搜索的;
(2)使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链;
(3)如果目标对象没有任何引用链相连,则是不可达的,就意味着该对象己经死亡,可以标记为垃圾对象;
(4)在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。

4、分析的工具
MAT是Memory Analyzer的简称,它是一款功能强大的Java堆内存分析器。用于查找内存泄漏以及查看内存消耗情况。MAT是基于Eclipse开发的,是一款免费的性能分析工具。
大家可以通过 https://www.eclipse/mat/downloads.php 下载并使用MAT。MAT工具找GC Roots截图:


可以说MAT是专门用于分析堆转储文件(dump文件)的,它可以离线或在线的分析堆空间中长期未使用,并一直在不断增长的对象,并且不能被垃圾回收掉,像这样的对象可以在MAT工具中很明显的查看到,我们可以找到这样的对象之后,利用这款工具查看该可疑对象的引用,是在哪一个类的哪一个对象一直引用着,这样来一步步的分析是那一段代码导致这样的对象没有使用了,还有可达对象的引用,以此来修改代码,从而排查出问题所在。

JProfiler
20版IDEA集成JProfiler工具(安装、集成、测试)教程

2、JPrifiler工具,是一款比MAT工具更加强大的,对服务器影响更小,用于分析内存(各个区域)、线程、CPU(可以查看哪一个方法被CPU执行的频率)、数据源、MBean对象信息等,其中有一个功能叫Heap Walker是专门用于分析内存泄漏的,它可以查看到每一个对象的前后引用,是谁引用着这个可疑对象,可疑对象又引用着谁。
虽然JProfiler工具功能十分强大,可以自定义很多东西,但它是一款收费的软件,不过,按照我上面提供的链接下载,可以免费使用,你懂的! 😗

这是JProfiler工具集成IDEA的截图:

Arthas
3、其中使用工具去检测只适合在测试阶段,一旦项目上线再使用工具去检测,还会涉及到让运维人员给你开放端口,服务端也必须配置了允许远程监控。开发文档全是中文,很友好的。
所以,那有没有在Linux服务上就可以检查的工具呢?有的,推荐使用阿里推出的开源的工具了,Arthas开发文档GitHub下载jar包地址

服务器上直接下载Arthas的命令,默认会将Arthas的jar包下载到当前目录下。

curl -O https://arthas.aliyun.com/arthas-boot.jar


注意:Arthas能够正常启动的前提是你的服务器上要运行着Java程序,不然Arthas是启动不中止,像这样:

二、内存溢出问题导致


1、 如果不是内存泄漏,换句话说就是内存中的对象确实都是还必须存活着,栈中都还有引用。那就应当检查虚拟机的堆参数( -Xms和-Xmx),与机器物理内存对比看是否还可以调大堆内存大小,从代码上检查是否存在某些对象生命周期过长(静态修饰)、持有状态时间过长的情况,尝试减少程序运行期内存消耗。

2、内存溢出就是没有空闲内存的情况:说明Java虛拟机的堆内存不够。
原因有以下二个:

(1) Java虛拟机的堆内存设置不够。

比如:可能存在内存泄漏问题;也很有可能就是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显式指定JVM堆大小或者指定数值偏小。我们可以通过参数 -Xms(最小堆空间)、-Xmx(最大堆空间)来调整。

(2)代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)


3、初始堆空间默认大小是物理内存的1 / 64(-Xms),最大堆空间默认大小是物理内存1 / 4(-Xmx)。

调节堆内存大小的参数:-Xms600m -Xmx600m,数字与字母之间没有空格

参数表示含义:将堆空间的初始化内存大小设置为600兆,最大堆空间内存大小设置为600兆。

经验:

通常情况我们都是将初始内存大小和最大内存大小设置为一样,这这样就避免初始堆空间不足而去不断申请扩内存或者降低内存这个过程(这个过程叫内存抖动),因为这个过程会耗时,有一定的开销。

② 当空闲内存小于初始总内存大小的 40%时,就会自动申请扩大内存;
当空闲内存大于最大内存大小的 70%时,就会降低内存到初始内存。

4、参数设置:

1)将Modules指定为JDK1.8

2)Java Complier 也设置为JDK1.8(前后两者版本一致就行)

3)进入Run添加参数设置,-Xms600m -Xmx600m

不一定是 600m,你可以根据自己项目的需要自定义堆空间大小

另外:要是在项目部署的时候设置堆空间的方式:
①用Tomcat或者TomEE部署war包项目时:

  • 找到Tomcat的根目录下的bin目录,进入找到catalina.bat \ catalina.sh文件,直接打开并添加如下配置:
  • 在文件的开头直接添加这行配置既可:CATALINA_OPTS=-server -Xms300m -Xmx300m
    Linux部署,修改catalina.sh文件如下:

windows部署,修改catalina.bat文件如下:

②使用SpringBoot框架开发的项目,由于内嵌了Tomcat所有不需要修改Tomcat的文件:
直接在启动命令中添加相应的配置参数即可,如:java -Xms1g -Xmx1g -jar xxx.jar
或者使用Maven插件也可以设置。

常见设置参数:

还可以使用Java自带的jmap命令去查找该程序中的大对象(从而地位到代码):不建议
不建议这么做的原因:jmap是对堆空间的快照,所有会对堆空间有一个冲击,可能会造成STW。

最好是使用工具,工具会很好的使用这些命令,比如说上面提到的 mat 工具就会对 jmp生成的 dump文件进行一个很好的分析。

以上做法要是在线上都是十分小心。线上操作这些命令都得小心,很可能会导致程序出问题。所有最好在程序启动之前就配置好生成日志文件,我们去阅读日志文件然后去排查文件。生产环境一定得配置HeapDumpOnOutOfMemoryError参数,默认是false.

另外说一下:导致报 OOM 的原因有哪些 ?

1、堆空间 -> 堆空间已经装不下新创建的对象;
2、方法区 -> 方法区(落地实现)空间不足;
3、虚拟机栈 -> 创建的线程太多,每一个线程私有的栈空间又很大,就会申请不到内存空间;
4、本地方法栈 -> 本地方法栈也是私有的,当线程很多时也会出现OOM。

也就是说,运行时数据区中,只有程序计数器(占用空间极小)才不会导致程序报 OOM。

有用点个关注,手留余香! 😐 😐 😐

更多推荐

Java中堆的内存泄漏和内存溢出OOM 及问题解决 参数设置