一次恐怖的 Java 内存泄漏排查实战

简介: 一次恐怖的 Java 内存泄漏排查实战

最近在看《深入理解Java虚拟机:JVM高级特性与最佳实践》(第二版)这本书,理论+实践结合,深入浅出,强烈推荐给大家。


这两天对JVM内容进行了一个讨论,讨论的内容主要包括如下几个方面。


1)内存溢出和内存泄露的介绍? 2)如何排查和处理内存泄露?


一、内存溢出和内存泄露

一种通俗的说法。


1、内存溢出:你申请了10个字节的空间,但是你在这个空间写入11或以上字节的数据,出现溢出。


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


下面具体介绍。


1.1 内存溢出

java.lang.OutOfMemoryError,是指程序在申请内存时,没有足够的内存空间供其使用,出现OutOfMemoryError。点击此处查看内存泄漏更多说明。


产生原因


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


JVM内存过小。


程序不严密,产生了过多的垃圾。


程序体现


一般情况下,在程序上的体现为


内存中加载的数据量过于庞大,如一次从数据库取出过多数据。


集合类中有对对象的引用,使用完后未清空,使得JVM不能回收。


代码中存在死循环或循环产生过多重复的对象实体。


使用的第三方软件中的BUG。


启动参数内存值设定的过小。


错误提示


此错误常见的错误提示:image.png解决方法


*1)增加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,设置就能生效了。


2)优化程序,释放垃圾


主要思路就是避免程序体现上出现的情况。避免死循环,防止一次载入太多的数据,提高程序健壮型及时释放。因此,从根本上解决Java内存溢出的唯一方法就是修改程序,及时地释放没用的对象,释放内存空间。


1.2 **[内存泄露**](http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247484867&idx=1&sn=fe5ef395be8f9e5bac5be73b3a88a51f&chksm=eb5380f5dc2409e34510dd17c7368a50489ec20ec72a3837db13cb847d589cad576d476ef6e7&scene=21#wechat_redirect)

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


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


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


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


关于内存泄露的处理页就是提高程序的健壮型,因为内存泄露是纯代码层面的问题。点击此处查看内存泄漏更多说明。


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

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


相同点:都会导致应用程序运行出现问题,性能下降或挂起。 不同点:1) 内存泄露是导致内存溢出的原因之一,内存泄露积累起来将导致内存溢出。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的意思是已使用空间站总空间的百分比。


结果如下图:

image.pngjstat执行结果


查询结果表明:这台服务器的新生代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的原因**](http://mp.weixin.qq.com/s?__biz=MzI3ODcxMzQzMw==&mid=2247484867&idx=1&sn=fe5ef395be8f9e5bac5be73b3a88a51f&chksm=eb5380f5dc2409e34510dd17c7368a50489ec20ec72a3837db13cb847d589cad576d476ef6e7&scene=21#wechat_redirect)

分析方法通常有两种:


1)把堆dump下来再用MAT等工具进行分析,但dump堆要花较长的时间,并且文件巨大,再从服务器上拖回本地导入工具,这个过程有些折腾,不到万不得已最好别这么干。


2)更轻量级的在线分析,使用“Java内存影像工具:jmap”生成堆转储快照(一般称为headdump或dump文件)。


jmap命令格式: jmap [ option ] vmid 使用命令如下: jmap -histo:live 20954 查看存活的对象情况,如下图所示:image.png可以看出HashTable中的元素有5000多万,占用内存大约1.5G的样子。这肯定不正常。


2.3 定位到代码

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


近期热文推荐:


1.600+ 道 Java面试题及答案整理(2021最新版)


2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!


3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!


4.Spring Cloud 2020.0.0 正式发布,全新颠覆性版本!


5.《Java开发手册(嵩山版)》最新发布,速速下载!


觉得不错,别忘了随手点赞+转发哦!



image.png

相关文章
|
16天前
|
安全 Java 程序员
深入理解Java内存模型与并发编程####
本文旨在探讨Java内存模型(JMM)的复杂性及其对并发编程的影响,不同于传统的摘要形式,本文将以一个实际案例为引子,逐步揭示JMM的核心概念,包括原子性、可见性、有序性,以及这些特性在多线程环境下的具体表现。通过对比分析不同并发工具类的应用,如synchronized、volatile关键字、Lock接口及其实现等,本文将展示如何在实践中有效利用JMM来设计高效且安全的并发程序。最后,还将简要介绍Java 8及更高版本中引入的新特性,如StampedLock,以及它们如何进一步优化多线程编程模型。 ####
21 0
|
2天前
|
Java
Java基础却常被忽略:全面讲解this的实战技巧!
本次分享来自于一道Java基础的面试试题,对this的各种妙用进行了深度讲解,并分析了一些关于this的常见面试陷阱,主要包括以下几方面内容: 1.什么是this 2.this的场景化使用案例 3.关于this的误区 4.总结与练习
|
18天前
|
存储 监控 算法
Java内存管理深度剖析:从垃圾收集到内存泄漏的全面指南####
本文深入探讨了Java虚拟机(JVM)中的内存管理机制,特别是垃圾收集(GC)的工作原理及其调优策略。不同于传统的摘要概述,本文将通过实际案例分析,揭示内存泄漏的根源与预防措施,为开发者提供实战中的优化建议,旨在帮助读者构建高效、稳定的Java应用。 ####
31 8
|
15天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
18天前
|
存储 算法 Java
Java内存管理深度解析####
本文深入探讨了Java虚拟机(JVM)中的内存分配与垃圾回收机制,揭示了其高效管理内存的奥秘。文章首先概述了JVM内存模型,随后详细阐述了堆、栈、方法区等关键区域的作用及管理策略。在垃圾回收部分,重点介绍了标记-清除、复制算法、标记-整理等多种回收算法的工作原理及其适用场景,并通过实际案例分析了不同GC策略对应用性能的影响。对于开发者而言,理解这些原理有助于编写出更加高效、稳定的Java应用程序。 ####
|
18天前
|
安全 Java 程序员
Java内存模型的深入理解与实践
本文旨在深入探讨Java内存模型(JMM)的核心概念,包括原子性、可见性和有序性,并通过实例代码分析这些特性在实际编程中的应用。我们将从理论到实践,逐步揭示JMM在多线程编程中的重要性和复杂性,帮助读者构建更加健壮的并发程序。
|
15天前
|
存储 监控 算法
Java内存管理的艺术:深入理解垃圾回收机制####
本文将引领读者探索Java虚拟机(JVM)中垃圾回收的奥秘,解析其背后的算法原理,通过实例揭示调优策略,旨在提升Java开发者对内存管理能力的认知,优化应用程序性能。 ####
29 0
|
6天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
36 6
|
21天前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
19天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
下一篇
DataWorks