Tomcat&JVM假死现象的原理浅析 内存泄露?死锁?

排查背景

2023-07-15上午业务人员反映系统出现卡死现象,持续近20min无法访问网页,进一步排查发现:tomcat一直处于运行状态但是就是无法对请求进行响应,即tomcat发生假死现象

Linux命令查询系统相关信息

# 查看系统cpu,内存和磁盘占用情况
top #cpu
free -h #内存
df -hl #磁盘

# 如果cpu占用很高
# 查询到7020这个进程有异常,在继续查看具体异常线程
top -Hp 7020 
 PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 23328 root      20   0 2538892 164144  11856 S  90.0  8.7   0:00.00 java
printf "%x\n" 23328 # 进制转换nid
5b20
jsack -l 7020 | grep '5b20' # 根据nid查询
http-bio-6379-exec-200 #8869954 daemon prio=5 os_prio=0 tid=0x00007f74a81f6800 nid=0x5b20 waiting for monitor entry [0x00007f742457f000]

# 如果内存占用高
# 查看指定pid的所有JVM信息
jinfo pid

# 查看指定进程下JVM内存占用情况
jmap -heap pid
# 查看活跃对象,紧急情况记录到文件中
jmap ‐histo:live pid | more
# 情况紧急将内存使用情况dump到文件中
jmap -dump:format=b,file=dump_tomcat_4728.dat pid
# ive的意思是只保留存活的对象。否则所有对象(包括已经被gc的对象)都会被输出,这在线上环境heap比较大,运行时间比较长的情况线下,会导致heap过大,没有必要。
jmap -dump:live,format=b,file=/data/tmp/heapdump.hprof pid

# -F是进程无法响时使用 (最好几分钟后再做一次快照 多一点对照)
jstack -F pid > file.txt

# 大文件日志查看最近 具体行数自行测试
tail -n  xxxx filename
tail -n 100 file | head -n 3 # 先获取文件的后面100行

排查原理支持

  1. 熟悉JVM原理的应该知道GC root,如果是内存占用高,首先排查GC root有哪些
    • 虚拟机栈中引用的对象
    • 方法区中类静态属性引用的对象
    • 方法区中常量引用的对象
    • 本地方法栈中 JNI 引用的对象
  2. 通过线程的栈信息排查 本地方法栈虚拟机栈中引用的对象(JVM假死情况应该是发生在堆栈中对象创建销毁频繁)
  3. 通过对堆内存的快照文件排查可以分析方法区中的静态变量常量引用的情况
  4. 可以使用idea 的Profiler工具(工具路径:Run|Open Profiler Snapshot)已经jvisualvm和eclipse的MAT内存分析工具(个人推荐Idea和MAT工具,jvisualvm文件太大无法打开)

排查过程简述

  1. 通过上述的Linux命令发现系统cpu和JVM的内存占用接近100%,其中JVM内存信息如下 PSYoungGen和ParOldGen占用99%
Heap
 PSYoungGen      total 1396736K, used 1332687K [0x000000071b300000, 0x0000000786e00000, 0x00000007c0000000)
  eden space 1332736K, 99% used [0x000000071b300000,0x000000076c873f10,0x000000076c880000)
  from space 64000K, 0% used [0x0000000780c00000,0x0000000780c00000,0x0000000784a80000)
  to   space 36352K, 0% used [0x0000000784a80000,0x0000000784a80000,0x0000000786e00000)
 ParOldGen       total 5401600K, used 5401292K [0x00000005d1800000, 0x000000071b300000, 0x000000071b300000)
  object space 5401600K, 99% used [0x00000005d1800000,0x000000071b2b3098,0x000000071b300000)
 Metaspace       used 104056K, capacity 115418K, committed 117336K, reserved 1155072K
  class space    used 10307K, capacity 11787K, committed 12160K, reserved 1048576K
  1. 执行shell jmap -dump:live,format=b,file=/data/tmp/heapdump.hprof pid 导出dump文件
  2. 分析dump文件发现其中有个对象名:XXXX的活跃的实例数非常多,通过查看改对象被引用信息(incoming References or shortest Paths)得知是位于本地缓存,即本地缓存中存在内存泄露情况
  3. 分析线程的栈信息发现tomcat的200个线程全部耗尽且都在等待执行(由于系统的连续FullGC),从而导致系统无法对新的请求进行响应

排查问题总结

  1. 为什么tomcat一直处于运行状态,发生内存泄露不应该直接报OOM错误,tomcat内存溢出后退出运行吗?
    这种假死的状态一般来说是发生内存泄露的过程非常缓慢,当泄露的内存刚好达到一定比例,这个比例取决于【新增的对象填满老年代的时间】/【每次FullGc的持续时间】越小说明FullGC越频繁,系统卡顿越明显,当达到一个临界值后就会产生彻底卡死的现象,一般来说假死是一个逐渐变慢的一个过程,当系统越慢又会导致新增到老年代的对象增多,是一个恶性循环。

最后的最后

如对此类问题有疑问,可以一起讨论讨论。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
JVM内存模型是Java虚拟机在运行时对内存的组织和管理方式。它主要包括堆、栈、方法区、程序计数器和本地方法栈等不同的内存区域。 堆是Java程序运行时动态分配对象的区域,存放的是实例对象。堆可以进一步细分为新生代和老年代等不同的区域,用于实现垃圾回收机制。 栈是线程私有的,用于存储线程的局部变量、方法参数以及方法调用的状态等。栈是一个后进先出(LIFO)的数据结构。 方法区是用于存储已被加载的类信息、常量、静态变量、编译器编译后的代码等数据。在Java 8之前,方法区被实现为永久代(PermGen),而在Java 8之后,它被实现为元空间(Metaspace)。 程序计数器是每个线程私有的,用于指示当前线程执行的字节码行号。 本地方法栈类似于栈,用于存储本地方法的信息。 对于内存优化方面,以下是一些常见的优化技巧: 1. 减少对象创建:避免过多地创建临时对象,尽量使用基本数据类型或复用对象。 2. 合理使用缓存:将经常使用的数据缓存起来,减少对磁盘或网络的访问。 3. 使用适当的数据结构和算法:选择合适的数据结构和算法可以提高程序的性能。 4. 避免过度同步:合理使用同步机制,避免过多地使用锁,可以提高程序的并发性能。 5. 对资源的正确释放:及时释放不再使用的资源,如关闭文件、数据库连接等。 6. 配置合理的堆大小和垃圾回收参数:根据应用程序的需求和硬件环境,调整堆大小和垃圾回收参数,以提高垃圾回收的效率。 7. 使用性能分析工具:使用性能分析工具来帮助定位和解决性能瓶颈问题。 请注意,这些只是一些常见的内存优化技巧,具体的优化策略还需要根据具体的应用场景和需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值