精华内容
下载资源
问答
  • java内存泄漏排查

    2021-08-15 22:32:02
    java内存泄漏排查 查看cpu的实时运行情况 输入以下命令 top 输出内容如下 top - 12:42:18 up 17:58, 1 user, load average: 0.02, 0.05, 0.05 Tasks: 210 total, 1 running, 209 sleeping, 0 stopped, 0 zombie %...

    java内存泄漏排查

    查看cpu的实时运行情况

    输入以下命令

    top
    

    输出内容如下

    top - 12:42:18 up 17:58,  1 user,  load average: 0.02, 0.05, 0.05
    Tasks: 210 total,   1 running, 209 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  0.1 sy,  0.0 ni, 99.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  8008100 total,  3337016 free,  3381284 used,  1289800 buff/cache
    KiB Swap:  8257532 total,  8257532 free,        0 used.  4190364 avail Mem
     PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                           
     5310 root      20   0 3834388 955156  14744 S   1.0 11.9   8:42.44 java                                                                                              
      868 root      20   0  115408    984    704 S   0.3  0.0   0:00.48 ksmtuned                                                                                          
     3134 devbase   20   0 6487524 838328  16216 S   0.3 10.5   7:14.17 java                                                                                              
     4445 root      20   0 4059928 160240  11852 S   0.3  2.0   1:23.55 java                                                                                              
        1 root      20   0  194088   7292   4204 S   0.0  0.1   0:04.00 systemd   
    

    可以把输出内容分成两块来看

    一块是概要信息

    top - 12:42:18 up 17:58,  1 user,  load average: 0.02, 0.05, 0.05
    Tasks: 210 total,   1 running, 209 sleeping,   0 stopped,   0 zombie
    %Cpu(s):  0.0 us,  0.1 sy,  0.0 ni, 99.8 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
    KiB Mem :  8008100 total,  3337016 free,  3381284 used,  1289800 buff/cache
    KiB Swap:  8257532 total,  8257532 free,        0 used.  4190364 avail Mem
    

    一块是每个进程详细信息

     PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                           
     5310 root      20   0 3834388 955156  14744 S   1.0 11.9   8:42.44 java                                                                                              
      868 root      20   0  115408    984    704 S   0.3  0.0   0:00.48 ksmtuned                                                                                          
     3134 devbase   20   0 6487524 838328  16216 S   0.3 10.5   7:14.17 java                                                                                              
     4445 root      20   0 4059928 160240  11852 S   0.3  2.0   1:23.55 java                                                                                              
        1 root      20   0  194088   7292   4204 S   0.0  0.1   0:04.00 systemd   
    

    接下来就分别进行介绍

    概要信息

    概要信息第1行参数

    参数含义备注
    top - 12:42:18系统当前时间可以不用看手机时间啦
    up 17:58系统运行时间看从开机到现在这台电脑跑了多久,显得这台电脑劳苦功高
    1 user当前登录用户数:1个看看有没有其他人登着
    load average: 0.02, 0.05, 0.05系统平均负载(3个值分别为1分钟/5分钟/15分钟)的平均负载看看刚刚一段时间,电脑运行的累不累,负载越高越辛苦

    概要信息第2行参数

    参数含义
    Tasks: 210 total总进程数
    1 running1个进程在运行
    209 sleeping209进程个在睡眠(好家伙)
    0 stopped0个进程处于停止
    0 zombie没有僵尸进程

    概要信息第3行参数

    参数含义缩写
    0.0 us用户空间占用CPU百分比user mode
    0.1 sy内核空间占用CPU百分比system mode
    0.0 ni用户进程空间内改变过优先级的进程占用CPU百分比low priority user mode (nice)
    99.8 id空闲CPU百分比 99.8% (真就偷懒呗)idle task
    0.0 wa等待IO的CPU时间百分比I/O waiting
    0.0 hi硬件中断时间百分比servicing IRQs
    0.0 si软件中断 时间百分比servicing soft IRQs
    0.0 st实时时间百分比steal (time given to other DomU instances)

    概要信息第4行参数

    参数含义
    KiB Mem : 8008100 total物理内存总量(KB)
    3337016 free空闲内存总量(KB)
    3381284 used使用的物理内存总量(KB)
    1289800 buff/cache用作内核缓存的内存量(KB)

    概要信息第5行参数

    参数含义
    KiB Swap: 8257532 total交换区总量(KB)
    8257532 free空闲交换区总量(KB)
    0 used使用的交换区总量(KB)
    4190364 avail Mem可用的内存(KB)

    接下来介绍详细信息

     PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                           
     5310 root      20   0 3834388 955156  14744 S   1.0 11.9   8:42.44 java                                                                                              
      868 root      20   0  115408    984    704 S   0.3  0.0   0:00.48 ksmtuned                                                                                          
     3134 devbase   20   0 6487524 838328  16216 S   0.3 10.5   7:14.17 java                                                                                              
     4445 root      20   0 4059928 160240  11852 S   0.3  2.0   1:23.55 java                                                                                              
        1 root      20   0  194088   7292   4204 S   0.0  0.1   0:04.00 systemd   
    
    简写全称含义
    PIDProcess Id进程ID
    USERUser Name当前进程运行的用户
    PRPriority优先级
    NINice Value负值表示高优先级,正值表示低优先级
    VIRTVirtual Image进程使用的虚拟内存总量(KB)。VIRT=SWAP+RES
    RESResident size进程使用的、未被换出的物理内存大小(KB)。RES=CODE+DATA
    SHRShared Mem size共享内存大小,单位(KB)
    SStatus进程状态(D|R|S|T|Z),可以看后面那进程张状态表
    %CPUcpuCPU占用百分比
    %MEMmemory内存占用百分比
    TIME+TIME系统运行时间
    COMMANDCOMMAND运行的程序

    该表最值得看的就是%CPU和%MEM ,可以看是否内存占用过高或者cpu占用过高。

    若cpu占用过高,可能是空轮循,或者发生了频繁的垃圾回收,或者程序访问压力过大

    若内存占用过高,可能存在内存泄漏

    进程状态表

    状态值含义全称
    R运行running
    D不可中断的睡眠状态uninterruptible sleep
    S睡眠sleeping
    T跟踪/停止traced or stopped
    Z僵尸进程zombie

    查看内存的占用情况

    free -mh
    
    命令组成含义
    free查看内存的命令
    mMB,表示转换为单位MB进行显示
    hhuman,表示以人类可读的方式显示

    输出内容如下

                  total        used        free      shared  buff/cache   available
    Mem:           7.6G        3.2G        3.2G        129M        1.2G        4.0G
    Swap:          7.9G          0B        7.9G
    

    具体含义如下

    参数含义
    total总内存
    used使用内存
    free空闲内存
    shared共享内存
    buff/cache缓冲区内存
    available可用内存

    查看磁盘的情况

    df -h
    

    输出内容

    文件系统                 容量     已用  可用   已用% 挂载点
    devtmpfs                 3.9G     0  3.9G    0% /dev
    tmpfs                    3.9G     0  3.9G    0% /dev/shm
    tmpfs                    3.9G  130M  3.7G    4% /run
    tmpfs                    3.9G     0  3.9G    0% /sys/fs/cgroup
    

    可以看磁盘是否出现沾满的情况

    查看当前程序的进行ID

    jps
    #或者
    jps -V
    

    显示两列,分别是进程ID和启动类的名称

    33809 RemoteJdbcServer
    31428 RemoteMavenServer36
    31286 
    34573 Launcher
    34574 CartoonApplication
    35887 Jps
    

    因为本次运行的服务是CartoonApplication,所以进程id为34574

    查看进程的GC回收状态

    jstat -gc 34574 2000
    
    命令及参数含义
    jstatjdk自带的工具
    -gc查看gc的情况(还可以查看class/compile/gcold)
    34574进程ID,本次要查看的进程ID为34574,生产上要根据上一步的jps获得
    2000每隔2000毫秒打印一次

    返回的结果如下所示:

    image-20210815142738664

    其中

    参数全称释义
    S0CCurrent survivor space 0 capacity (KB).S0的当前总容量(KB)
    S1CCurrent survivor space 1 capacity (KB).S1的当前总容量(KB)
    S0USurvivor space 0 utilization (KB).S0的利用情况(KB)
    S1USurvivor space 1 utilization (KB).S1的利用情况(KB)
    ECCurrent eden space capacity (KB).伊甸园的当前总容量(KB)
    EUEden space utilization (KB).伊甸园的利用情况(KB)
    OCCurrent old space capacity (KB).老年代的当前总容量(KB)
    OUOld space utilization (KB).老年代的利用情况(KB)
    PCCurrent permanent space capacity (KB).永久代的当前总容量(KB)
    PUPermanent space utilization (KB).永久代的利用情况(KB)
    YGCNumber of young generation GC Events.YoungGC的次数(KB)
    YGCTYoung generation garbage collection time.YoungGC的时间
    FGCNumber of full GC events.FullGC次数
    FGCTFull garbage collection time.FullGC时间
    GCTTotal garbage collection time.GC总时间

    这里感觉官方的说法并不好,因为实际上Current capacity其实应该翻译为总容量的意思。

    而utilization不应该翻译为利用率,而应该是使用情况。因为单位是KB。

    我们在这个过程中最应该关心的就是 FGC 的次数

    如果说我们每2秒打印一次,它的FullGC就增加1次。这就说明在这个过程中发生了频繁的GC

    从上图可得,每2秒打印,它的 FGC 一直保持为2,说明从系统运行到现在为止总共只发生了2次GC

    保存进程的栈现场

    jstack 34574 > jstack.log
    
    参数或命令含义
    jstackjdk自带命令,可以查看栈现场的情况
    34574进程ID,本次要查看的进程ID为34574,生产上要根据上一步的jps获得
    >重定向
    jstack.log重定向到当前目录下的jstack.log,若没有则生成

    对刚刚生成的栈现场文件进行分析,查看有多少个线程

    grep 'java.lang.Thread.State' jstack.log | wc -l
    
    参数或命令含义
    grep匹配并过滤出
    ‘java.lang.Thread.State’匹配并过滤出java.lang.Thread.State的文本内容
    jstack.log被过滤的文件
    管道,用于将过滤出的结果传递给下一个命令
    wcword count 统计
    -l按行统计,统计行数

    输出结果

    83
    

    说明当前程序总共在跑的线程数有83个。事实上,83个线程也不是很多

    查看线程是否有异样

    grep -A 1 'java.lang.Thread.State' jstack.log | grep -v 'java.lang.Thread.State' | sort | uniq -c |sort -n
    

    参数命令

    参数含义
    grep -A 1 ‘java.lang.Thread.State’匹配 java.lang.Thread.State并过滤文件,并关联出对应上下各1行
    jstack.log被匹配的文件
    管道,将刚刚的输出结果作为输入
    grep -v ‘java.lang.Thread.State’匹配不含java.lang.Thread.State的内容(这样只剩下上下各一行)
    sort将所有行按照字典序排序
    uniq -c去除排序后相连重复的行
    sort -n以数字的形式排序

    输出

       1 	at java.lang.Thread.sleep(Native Method)
       1 	at java.net.PlainSocketImpl.socketAccept(Native Method)
       1 	at java.net.SocketInputStream.socketRead0(Native Method)
       1 	at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
       3 	at sun.nio.ch.KQueueArrayWrapper.kevent0(Native Method)
      11 
      14 	at java.lang.Object.wait(Native Method)
      51 	at sun.misc.Unsafe.park(Native Method)
      82 --
    
    

    看输出没有任何异样

    保存进程的堆现场

    jmap -dump:format=b,file=heap.log 34574
    
    参数或命令含义
    jmapjdk自带命令,可以查看堆现场的情况
    -dump:format=b以二进制的形式输出堆
    file=heap.log输出文件的文件名为heap.log
    34574进程ID,本次要查看的进程ID为34574,生产上要根据上一步的jps获得

    下载服务器上的堆文件到本地

    服务器上没有好的可视化分析工具,必须把文件下载到本地

    查看文件大小

    ls -lh
    

    显示内容,文件大概219MB

    -rw-------  1 admin  staff   219M  8 15 15:09 heap.log
    

    但是生产上的堆文件可能很大,所以我们需要先对文件内容进行压缩,然后再进行传输

    gzip -6 heap.log
    

    以下是参数的命令

    参数或命令含义
    gzip压缩命令
    -6用于指定压缩等级,-1 压缩等级最低,压缩比最差;-9 压缩比最高。默认压缩比是 -6
    heap.log被压缩的文件名

    稍等片刻压缩完毕

    ls -lh
    

    我们发现源文件消失,压缩后的文件出现,只有48MB,压缩到了原来的21%,非常方便传输

    -rw-------  1 admin  staff    48M  8 15 15:09 heap.log.gz
    

    然后把文件转移到本地后再解压

    gunzip heap.log.gz
    

    使用MAT软件进行堆内存分析

    MAT 是分析 Java 堆内存的利器,使用它打开我们的堆文件(将文件后缀改为 .hprof), 它会提示我们要分析的种类。

    image-20210815153333901

    大对象情况,看起来好像没有,最大的对象也就只有2.4MB。

    查看每个类的情况

    截屏2021-08-1515.34.54

    最大的是char[],总共有7万多个对象,内存占到7MB左右

    image-20210815153632256

    看起来都很正常。

    因为这是本地为了测试jdk的工具,下了正常应用程序的一个堆。为了测试才打开的。事实上说明该本地应用程序确实没有什么问题

    但是思路是一样的,就是通过一步步的命令,层层排查,抽丝拨茧。

    你如果有遇到什么线上排查成功的例子,也可以在评论区分析给大家

    致谢

    https://www.toutiao.com/i6698492096132678152/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1&timestamp=1628998274&app=news_article&utm_source=weixin&utm_medium=toutiao_android&use_new_style=1&req_id=202108151131140101512130844D7398DF&share_token=39c48a51-1e0e-49ce-8095-335479801aa8&group_id=6698492096132678152&wid=1628998991385

    展开全文
  • java内存泄露排查

    2021-02-23 09:37:54
    java内存泄露排查 https://blog.csdn.net/fishinhouse/article/details/80781673
    展开全文
  • Java内存泄漏排查

    2019-01-03 16:34:58
    Java内存泄漏排查 1.内存溢出和内存泄露 通俗点说: 内存溢出:申请了10个字节的空间,但是确在这个空间写入11或以上字节的数据,出现溢出。 内存泄漏:new申请了一块内存,后来很长时间都不再使用了(按理应该释放...

    Java内存泄漏排查

    1.内存溢出和内存泄露

    通俗点说:
    内存溢出:申请了10个字节的空间,但是确在这个空间写入11或以上字节的数据,出现溢出。

    内存泄漏:new申请了一块内存,后来很长时间都不再使用了(按理应该释放),但是因为一直被某个或某些实例所持有导致 GC 不能回收,也就是该被释放的对象没有释放。

    1.1内存溢出(Out Of Memory)

    产生该错误的原因主要包括:

    • JVM内存过小。
    • 程序不严密,产生了过多的垃圾。

    一般情况下,在程序上的体现为:
    内存中加载的数据量过于庞大,如一次从数据库取出过多数据。
    集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。
    代码中存在死循环或循环产生过多重复的对象实体。

    1.2内存泄露(Memory Leak)

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

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

    如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

    关于内存泄露的处理就是提高程序的健壮型,因为内存泄露是纯代码层面的问题。

    1.3内存溢出和内存泄露的关系

    内存泄露会最终会导致内存溢出。

    相同点:都会导致应用程序运行出现问题,性能下降或挂起。

    不同点:1) 内存泄露是导致内存溢出的原因之一,内存泄露积累起来将导致内存溢出。2) 内存泄露可以通过完善代码来避免,内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。

    2.确定频繁Full GC现象

    虚拟机进程状况工具:jps(Java Virtual Machine Process Status Tool) 是 JDK 提供的一个显示当前所有java进程pid的命令,简单实用,非常适合在linux/unix平台上简单察看当前java进程的一些简单情况。
    jps -l
    在这里插入图片描述
    虚拟机统计信息监视工具:监视虚拟机各种运行状态信息:包括程序运行以来共发生YGC(Young GC)次数,耗时(s),发生FGC(Full GC)次数,耗时(s),总耗时GCT(GC Time)。

    jstat -gcutil 进程名 查询刷新时间(ms)
    在这里插入图片描述

    3.找出导致频繁Full GC的原因

    1、线上排查:使用“Java内存影像工具:jmap”生成堆转储快照(一般称为headdump或dump文件),查看对象的存活情况(数量和大小),可以初步分析出到底是哪个对象出现了引用未被垃圾回收收集。
    jmap -histo:live 进程名
    在这里插入图片描述
    jmap -heap 进程名

    2、离线分析:把堆dump下来再用MAT等工具进行分析,但dump堆要花较长的时间并且文件巨大,再从服务器上拖回本地导入工具。主要用到Histogram(展示各个类产生的实例,类似jmap -histo),Dominator Tree(展示大对象的占用率)。

    jmap -dump:live,format=b,file=heap.dump-ip 进程名

    展开全文
  • java内存泄露排查总结

    2021-01-18 11:14:24
    1.内存溢出和内存泄露 一种通俗的说法: 内存溢出:你申请了10...java.lang.OutOfMemoryError:是指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError。 产生的原因 JVM内存过小 程序不严密,

    1.内存溢出和内存泄露

    一种通俗的说法:

    • 内存溢出:你申请了10个字节的空间,但是你在这个空间写入了11个或者以上字节的数据,则出现溢出
    • 内存泄露:你用new申请了一块内存,后来很长时间都不使用了,但是因为一直被某个或者某些实例所持有导致GC不能回收掉,也就是该释放的对象没有释放,则出现泄露。

    1.1 内存溢出

    java.lang.OutOfMemoryError:是指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError。

    产生的原因

    • JVM内存过小
    • 程序不严密,产生了过多的垃圾

    程序提现

    • 内存中加载的数据量过大,如一次性从数据库取出过多数据
    • 集合类中有对对象的引用,使用完后没有清空,是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

    解决方法

    • 增加JVM的内存大小:对于tomcat容器,找到tomcat在电脑中的安装目录,进入这个目录,然后进入bin目录中,在window环境下找到bin目录中的catalina.bat,在linux环境下找到catalina.sh。编辑catalina.bat文件,找到JAVA_OPTS(具体来说是 set "JAVA_OPTS=%JAVA_OPTS% %LOGGING_MANAGER%")这个选项的位置,这个参数是Java启动的时候,需要的启动参数。也可以在操作系统的环境变量中对JAVA_OPTS进行设置,因为tomcat在启动的时候,也会读取操作系统中的环境变量的值,进行加载。如果是修改了操作系统的环境变量,需要重启机器,再重启tomcat,如果修改的是tomcat配置文件,需要将配置文件保存,然后重启tomcat,设置就能生效了。
    • 优化程序,释放垃圾:主要思路就是避免程序体现上出现的情况。避免死循环,防止一次载入太多的数据,提高程序健壮型及时释放。因此,从根本上解决Java内存溢出的唯一方法就是修改程序,及时地释放没用的对象,释放内存空间。

    1.2 内存泄露

    Memory Leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

    在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点:

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

    如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。

    关于内存泄露的处理页就是提高程序的健壮型,因为内存泄露是纯代码层面的问题。

    1.3 内存溢出和内存泄露的联系

    内存泄露会最终会导致内存溢出。
    相同点:都会导致应用程序运行出现问题,性能下降或挂起。
    不同点:1) 内存泄露是导致内存溢出的原因之一,内存泄露积累起来将导致内存溢出。2) 内存泄露可以通过完善代码来避免,内存溢出可以通过调整配置来减少发生频率,但无法彻底避免。

    2、一个Java内存泄漏的排查案例

    2.1 确定频繁的Full GC现象

    首先通过“虚拟机进程状况工具:jps”找出正在运行的虚拟机进程,最主要是找出这个进程在本地虚拟机的唯一ID(LVMID,Local Virtual Machine Identifier),因为在后面的排查过程中都是需要这个LVMID来确定要监控的是哪一个虚拟机进程。
    同时,对于本地虚拟机进程来说,LVMID与操作系统的进程ID(PID,Process Identifier)是一致的,使用Windows的任务管理器或Unix的ps命令也可以查询到虚拟机进程的LVMID。
    jps命令格式为:
    jps [ options ] [ hostid ]
    使用命令如下:
    使用jps:jps -l
    使用ps:ps aux | grep tomat

    找到你需要监控的ID(假设为20954),再利用“虚拟机统计信息监视工具:jstat”监视虚拟机各种运行状态信息。
    jstat命令格式为:
    jstat [ option vmid [interval[s|ms] [count]] ]
    使用命令如下:
    jstat -gcutil 20954 1000
    意思是每1000毫秒查询一次,一直查。gcutil的意思是已使用空间站总空间的百分比。
    结果如下图:

    查询结果表明:这台服务器的新生代Eden区(E,表示Eden)使用了28.30%(最后)的空间,两个Survivor区(S0、S1,表示Survivor0、Survivor1)分别是0和8.93%,老年代(O,表示Old)使用了87.33%。程序运行以来共发生Minor GC(YGC,表示Young GC)101次,总耗时1.961秒,发生Full GC(FGC,表示Full GC)7次,Full GC总耗时3.022秒,总的耗时(GCT,表示GC Time)为4.983秒。

    2.2 找出频繁Full GC的原因

    分析方法通常有两种:

    • 把堆dump下来在用MAT等工具进行分析,但是dump堆要花较长时间,并且文件巨大,再从服务器上拖回本地导入工具,这个过程有些折腾,不到万不得已最好别这么干。
    • 更轻量级的在线分析,使用jmap(java内存影响工具)生成堆转存快照(一般称为headdump或者dump文件)

    jmap命令格式:
    jmap [ option ] vmid
    使用命令如下:
    jmap -histo:live 20954
    查看存活的对象情况,如下图所示:

    按照一位IT友的说法,数据不正常,十有八九就是泄露的。在我这个图上对象还是挺正常的。

    我在网上找了一位博友的不正常数据,如下:

    可以看出HashTable中的元素有5000多万,占用内存大约1.5G的样子。这肯定不正常。

    2.3 定位到代码

    定位带代码,有很多种方法,比如前面提到的通过MAT查看Histogram即可找出是哪块代码。——我以前是使用这个方法。也可以使用BTrace,我没有使用过。

    举例

    一台生产环境机器每次运行几天之后就会莫名其妙的宕机,分析日志之后发现在tomcat刚启动的时候内存占用比较少,但是运行个几天之后内存占用越来越大,通过jmap命令可以查询到一些大对象引用没有被及时GC,这里就要求解决内存泄露的问题。

    Java的内存泄露多半是因为对象存在无效的引用,对象得不到释放,如果发现Java应用程序占用的内存出现了泄露的迹象,那么我们一般采用下面的步骤分析:

    1.  用工具生成java应用程序的heap dump(如jmap)
    2. 使用Java heap分析工具(如MAT),找出内存占用超出预期的嫌疑对象
    3. 根据情况,分析嫌疑对象和其他对象的引用关系。
    4. 分析程序的源代码,找出嫌疑对象数量过多的原因。

    以下一步步的按照项目实例来操作,去解决内存泄露的问题。

    1. 登录linux服务器,获取tomcat的pid,命令:

    ps -ef|grep java

    2.利用jmap初步分析内存映射,命令:

    jmap -histo:live 进程号 | head -7  

    3. 如果上面一步还无法定位到关键信息,那么需要拿到heap dump,生成离线文件,做进一步分析,命令:

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

    4.拿到heap dump文件,利用eclipse插件MAT来分析heap profile。

    1. 安装MAT插件
    2. 在eclipse里切换到Memory Analysis视图
    3. 用MAT打开heap profile文件。

    Memory Analyzer插件下载

    直接看到下面Action窗口,有4种Action来分析heap profile,介绍其中最常用的2种:

    Histogram:这个使用的最多,跟上面的jmap -histo 命令类似,只是在MAT里面可以用GUI来展示应用系统各个类产生的实例。

    Shllow Heap排序后发现 Cms_Organization 这个类占用的内存比较多(没有得到及时GC),查看引用:

    分析引用栈,找到无效引用,打开源码:

    有问题的源码如下:

    public class RefreshCmsOrganizationStruts implements Runnable{  
      
        private final static Logger logger = Logger.getLogger(RefreshCmsOrganizationStruts.class);  
          
        private List<Cms_Organization> organizations;  
      
        private OrganizationDao organizationDao = (OrganizationDao) WebContentBean  
                .getInstance().getBean("organizationDao");  
        public RefreshCmsOrganizationStruts(List<Cms_Organization> organizations) {  
            this.organizations = organizations;  
        }  
      
        public void run() {  
            Iterator<Cms_Organization> iter = organizations.iterator();  
            Cms_Organization organization = null;  
            while (iter.hasNext()) {  
                organization = iter.next();  
                synchronized (organization) {  
                    try {  
                        organizationDao.refreshCmsOrganizationStrutsInfo(organization.getOrgaId());  
                        organizationDao.refreshCmsOrganizationResourceInfo(organization.getOrgaId());  
                        organizationDao.sleep();  
                    } catch (Exception e) {  
                        logger.debug("RefreshCmsOrganizationStruts organization = " + organization.getOrgaId(), e);  
                    }  
                }  
            }  
        }  
      
    }  

    分析源码,定时任务定时调用,每次调用生成10个线程处理,而它又使用了非线程安全的List对象,导致List对象无法被GC收集,所以这里将List替换为CopyOnWriteArrayList 。

    Dominator Tree:这个使用的也比较多,显示大对象的占用率。

    同样的打开源码:

    public class CategoryCacheJob extends QuartzJobBean implements StatefulJob {  
          
        private static final Logger LOGGER = Logger.getLogger(CategoryCacheJob.class);  
          
        public static Map<String,List<Cms_Category>> cacheMap = new java.util.HashMap<String,List<Cms_Category>>();  
      
        @Override  
        protected void executeInternal(JobExecutionContext ctx) throws JobExecutionException {  
            try {  
                //LOGGER.info("======= 缓存编目树开始 =======");  
                MongoBaseDao mongoBaseDao = (MongoBaseDao) BeanLocator.getInstance().getBean("mongoBaseDao");  
                MongoOperations mongoOperations = mongoBaseDao.getMongoOperations();  
                  
                /* 
                LOGGER.info("1.缓存基础教育编目树"); 
                Query query = Query.query(Criteria.where("isDel").is("0").and("categoryType").is("F")); 
                query.sort().on("orderNo", Order.ASCENDING); 
                List<Cms_Category> list = mongoOperations.find(query, Cms_Category.class); 
                String key = query.toString().replaceAll("\\{|\\}|\\p{Cntrl}|\\p{Space}", ""); 
                key += "_CategoryCacheJob"; 
                cacheMap.put(key, list); 
                */  
                  
                //LOGGER.info("2.缓存职业教育编目树");  
                Query query2 = Query.query(Criteria.where("isDel").is("0").and("categoryType").in("JMP","JHP"));  
                query2.sort().on("orderNo", Order.ASCENDING);  
                List<Cms_Category> list2 = mongoOperations.find(query2, Cms_Category.class);  
                String key2 = query2.toString().replaceAll("\\{|\\}|\\p{Cntrl}|\\p{Space}", "");  
                key2 += "_CategoryCacheJob";  
                cacheMap.put(key2, list2);  
                  
                //LOGGER.info("3.缓存专题教育编目树");  
                Query query3 = Query.query(Criteria.where("isDel").is("0").and("categoryType").is("JS"));  
                query3.sort().on("orderNo", Order.ASCENDING);  
                List<Cms_Category> list3 = mongoOperations.find(query3, Cms_Category.class);  
                String key3 = query3.toString().replaceAll("\\{|\\}|\\p{Cntrl}|\\p{Space}", "");  
                key3 += "_CategoryCacheJob";  
                cacheMap.put(key3, list3);  
                  
                //LOGGER.info("======= 缓存编目树结束 =======");  
            } catch(Exception ex) {  
                LOGGER.error(ex.getMessage(), ex);  
                LOGGER.info("======= 缓存编目树出错 =======");  
            }  
        }  
      
    }  

    这里的HashMap也有问题:居然使用定时任务,在容器启动之后定时将数据放到Map里面做缓存?这里修改这部分代码,替换为使用memcached缓存即可。

    内存泄漏的原因分析,总结出来只有一条:存在无效的引用!良好的编码规范以及合理使用设计模式有助于解决此类问题。

    线程安全的CopyOnWriteArrayList介绍

    CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。

    当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。

    当元素在新数组添加成功后,将array这个引用指向新数组。

    CopyOnWriteArrayList的整个add操作都是在锁的保护下进行的。 
    这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。

    CopyOnWriteArrayListadd操作的源代码如下:

     public boolean add(E e) {
        //1、先加锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //2、拷贝数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //3、将元素加入到新数组中
            newElements[len] = e;
            //4、将array引用指向到新数组
            setArray(newElements);
            return true;
        } finally {
           //5、解锁
            lock.unlock();
        }
    }

    由于所有的写操作都是在新数组进行的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分几种情况: 
    1、如果写操作未完成,那么直接读取原数组的数据; 
    2、如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据; 
    3、如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取数据。

    可见,CopyOnWriteArrayList的读操作是可以不用加锁的。

    CopyOnWriteArrayList的使用场景

    通过上面的分析,CopyOnWriteArrayList有几个缺点:

    1. 由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致yong gc或者full gc
    2. 不能用于实时读的场景,像拷贝数组,新增元素都需要时间,所以调用一个set操作后,读取到的数据可能还是旧的,虽然CopyOnWriteArrayList 能做到最终一致性,但是还无法满足实时性的要求

     

    CopyOnWriteArrayList 合适读多写少的场景,不过这类慎用 ,因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

     

    展开全文
  • 原文地址:java程序内存泄漏的定位与分析 1,什么叫内存泄露 编写java程序最为方便的地方就是我们不需要管理内存的分配和释放,一切由jvm来进行处理,当java对象不再被应用时,等到堆内存不够用时,jvm会进行垃圾...
  • Java内存泄露排查攻略

    2019-03-05 14:05:20
    会变回类似弱引用的可达性状态了,就会产生内存泄漏。所以,检查弱引用指向对象是否被垃圾 收集,也是诊断是否有特定内存泄漏的一个思路,如果我们的框架使用到弱引用又怀疑有内存泄 漏,就可以从这个角度检查。 .....
  • 一次恐怖的 Java 内存泄漏排查实战

    千次阅读 2018-07-30 12:56:48
    转载自 一次恐怖的 Java 内存泄漏排查实战 最近在看《深入理解Java虚拟机:JVM高级特性与最佳实践》(第二版)这本书,理论+实践结合,深入浅出,强烈推荐给大家。 这两天对JVM内容进行了一个讨论,讨论的内容...
  • 转载:https://mp.weixin.qq.com/s/ppS2zdFfDiFiVIcOBUqvqA1)内存溢出和内存泄露的介绍?...2、内存泄漏:你用new申请了一块内存,后来很长时间都不再使用了(按理应该释放),但是因为一直被某个或某些实...
  • 一次JAVA内存泄露排查

    2021-08-24 10:42:22
    我们的项目是部署到tomcat的,一个tomcat下有若干的Java项目在跑,我们发现在每次重新部署(把war包直接丢进webapps目录下)项目时经常会出现长时间卡顿等现象,并且后续的解决方案一般是重启tomcat。 这个是我做的...
  • 干货分享:一次Java内存泄漏排查实战 IT大咖说2019-06-06 20:07:23 不知道是公司网络广了就这样还是网络运维组不给力,网络总有问题,不是这边交换机脱网了,就是那边路由器坏了,还偶发地各种超时,而我们灵敏...
  • 小心踩雷,一次Java内存泄漏排查实战

    千次阅读 多人点赞 2019-06-04 08:45:00
    前些日子小组内安排值班,轮流看顾我们的服务,主要做一些报警邮件处理、Bug 排查、运营 issue 处理的事。工作日还好,无论干什么都要上班的,若是轮到周末,那这一天算是毁了。 不知道是公司网络广了就...
  • 点击关注公众号,Java干货及时送达来源 |https://zhenbianshu.github.io/前些日子小组内安排值班,轮流看顾我们的服务,主要做一些报警邮件处理、Bug 排查、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,646
精华内容 6,258
关键字:

java内存泄漏排查

java 订阅