• Jvm之内存泄漏


    1 内存溢出

    1.1  概念

    java.lang.OutOfMemoryError,是指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError。产生该错误的原因主要包括:JVM内存过小。程序不严密,产生了过多的垃圾。

    程序体现:

    • 内存中加载的数据量过于庞大,如一次从数据库取出过多数据。
    • Cglib 不断创建新类
    • 大量 JSP 或动态产生 JSP 文件的应用
    • 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。
    • 代码中存在死循环或循环产生过多重复的对象实体。
    • 使用的第三方软件中的BUG。
    • 启动参数内存值设定的过小。

    错误提示:

    • tomcat:java.lang.OutOfMemoryError: PermGen space
    • tomcat:java.lang.OutOfMemoryError: Java heap space
    • weblogic:Root cause of ServletException java.lang.OutOfMemoryError
    • resin:java.lang.OutOfMemoryError
    • java:java.lang.OutOfMemoryError

    1.2 解决办法

    • 增加JVM的内存大小。具体可参考:jvm之内存调优_jvm内存调优-CSDN博客
    • 优化程序,释放垃圾。主要思路就是避免程序体现上出现的情况。避免死循环,防止一次载入太多的数据,提高程序健壮型及时释放。因此,从根本上解决Java内存溢出的唯一方法就是修改程序,及时地释放没用的对象,释放内存空间。

    1.3 内存溢出排查

    内存溢出的排查过程通常包括以下几个步骤:

    1.3.1 检查JVM崩溃日志:

    • 当JVM发生崩溃时,会生成相应的错误日志文件,如`hs_err_pid.log`。这些日志文件包含了堆栈信息、线程状态和系统信息。通过在JVM启动时设置`-XX:ErrorFile`参数,可以将错误日志输出到指定的文件中。

    1.3.2 代码审查:

    • 仔细检查新上线的代码,寻找可能导致内存溢出的潜在问题,如死循环、慢SQL或大数据量查询等。

    1.3.3 内存溢出dump文件分析:

    • 在JVM参数中设置`XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath`,以便在内存溢出时生成dump文件。这些文件的名称通常是`xxx.hprof`。之后,可以使用 Eclipse Memory Analyzer (MAT)对这些dump文件进行分析和诊断。

    1.3.4 内存泄露分析:

    • 在MAT中打开dump文件,并进入“Leak Suspects”选项卡。在这里,你可以看到可能引起内存泄露的问题,以及它们占用的内存大小。进一步的分析可以帮助定位问题的根源。

    以实际案例为例,如果在一个Java应用中观察到大量dubbo线程阻塞,并且JVM堆内存的老年代和新生代都达到了高负载,那么很可能是发生了内存溢出。在上述案例中,日志显示老年代几乎满了,而年轻代由于无法接收老年代的对象而导致频繁的Young GC,最终导致了堆内存溢出。此外,通过对dump文件的分析,可以找到具体的内存泄露点,从而确定问题的根本原因。

    2 内存泄漏

    Memory Leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
    在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:

    • 首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;
    • 其次,这些对象是无用的,即程序以后不会再使用这些对象。

    如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。对于内存泄露的处理页就是提高程序的健壮型,因为内存泄露是纯代码层面的问题。

    2.1 泄漏分类

    • 经常发生:发生内存泄露的代码会被多次执行,每次执行,泄露一块内存;
    • 偶然发生:在某些特定情况下才会发生;
    • 一次性:发生内存泄露的方法只会执行一次;
    • 隐式泄露:一直占着内存不释放,直到执行结束;严格的说这个不算内存泄露,因为最终释放掉了,但是如果执行时间特别长,也可能会导致内存耗尽。

    2.2 导致内存泄漏的常见原因

    • 循环:过多或者死循环导致产生了大量对象
    • 静态集合类:引起的内存泄漏,因为静态集合类的生命周期和JVM是一致的。
    • 单例模式: 如果单例对象引用了外部对象,会导致该外部对象一直不回被回收。因为单例的的静态属性会让对象的生命周期和JVM一致。
    • 变量的不合理作用域,如下:
    1. > public class UsingRandom {        
    2.     private String msg;
    3.     public void receiveMsg(){
    4.         readFromNet();// 从网络中接受数据保存到msg中
    5.         saveDB();// 把msg保存到数据库中
    6.     }
    7. }
    8. //如上面这个伪代码,通过readFromNet方法把接受的消息保存在变量msg中,然后调用saveDB方法把msg的内容保存到数据库中,此时msg已经就没用了,由于msg的生命周期与对象的生命周期相同,此时msg还不能回收,因此造成了内存泄漏。
    9. //实际上这个msg变量可以放在receiveMsg方法内部,当方法使用完,那么msg的生命周期也就结束,此时就可以回收了。还有一种方法,在使用完msg后,把msg设置为null,这样垃圾回收器也会回收msg的内存空间。
    • 数据连接: 像IO,socket连接他们必须被显示的close掉,否则不回被GC回收。
    • 内部类:对象被外部对象长期持久,会导致外部类也无法被回收
    • 哈希值改变: 当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。因为,当修改后,所得的哈希值与最初存储进HashSet集合中时的哈希值就不同了。在这种情况下,即使用contains()方法,也将返回找不到对象的结果,但是HashSet却一直持有修改前的对象的实例,导致不能被GC,造成内存泄露。
    • 监听器和回调: 在Java语言中, 往往呢会使用到监听器 ,一个应用可能会使用到多个监听器。 比如说, 在我们Java Web中有底层的网络监听器listener ,监听器的作用就是去监听指定的类或者对象他产生的行为 ,从而做出对应的响应, 因为监听器往往都是全局存在的, 如果对于监听器中所使用这些对象或者是变量 ,你没有有效的控制的话 ,很容易产生内存泄露 。
    • 缓存: 内存泄漏的另一个常见来源是缓存。举个例子,我们有时候为了减少与db的交互次数,会将查询出的对象实例放入缓存中,但是常常会忘记对这个缓存进行管理。比如忘记限制缓存大小。

    对于这个问题,可以使用WeakHashMap代表缓存,此种Map的特点是,当除了自身有对key的引用外,此key没有其他引用那么此map会自动丢弃此值。

    2.3 内存泄漏排查

    2.3.1 查看JVM状态

    1)查看虚拟机进程,找到需要监控的进程ID

    使用 jps | ps 找到对应的进程ID

    1. jps:jps -l
    2. ps:ps -aux | grep java

    2)使用 jstat实时的查看一下当前程序的资源和性能。
    命令:

    1. jstat -gcutil 进程ID 1000
    2. //1000毫秒查询一次,一直查。gcutil的意思是已使用空间站总空间的百分比。

    执行结果:

    2.3.2 定位问题

    2.3.2.1 dump文件分析

    1)使用 jmap查看存活对象,并生成dump文件。

    jmap命令格式:

    jmap [ option ] vmid

    使用命令如下:

    1.  jmap -histo:live 28558| head -20 
    2. //查看示Java堆中存活对象的统计信息,包括:对象数量、占用内存大小(单位:字节)和类的完全限定名,

    生成heap dump文件:

    jmap -dump:live,format=b,file=heap.hprof 3514  

    2)Java heap分析工具

    • Ecplise用MAT插件
    • Idea安装Jprofiler进行分析
    • 堆Dump可视化分析在线工具: https://heaphero.io/

    3)JVM调优常用工具:
    JVM调优的在线网站_java 堆分析网址-CSDN博客

  • 相关阅读:
    Java框架(四)--Spring AOP面向切面编程(3)--Spring AOP实现原理
    连锁快餐绩效考核中的神秘顾客调查
    ceph 004 纠删码池 修改参数 cephx认证
    阿里云负载均衡SLB,HTTPS动态网站部署负载均衡,实现高并发流量分发
    K8S-解决报错--总结日记
    pytorch UserWarningfault grid_sample; Python opencv Qt报Current thread的新解决方法
    【微服务】软件架构的演变之路
    数字化时代,传统IT和数字型IT能否严格区分?
    软件代码坏味道之滥用switch
    做机器视觉工程师,其实挺没意思的
  • 原文地址:https://blog.csdn.net/ygq13572549874/article/details/136318435