《JVM故障诊断指南》之3 —— Java 线程: JVM持有内存的分析



原文链接 原文作者:Byron Kiourtzoglou 翻译:梅小西(904516706)

前面我们已经讨论过JVM里不同的堆空间,这节我们会给你提供教程,是关于如何从你的活动的应用Java线程中确定它持有多少堆空间,以及在哪里占用。这里有个来自Oracle Weblogic 10.0生产环境的真实案例,它能使你更好的理解分析过程。

我们也会演示这种情况,过多的垃圾收集或者堆空间内存占用问题并不总是由于真实的内存泄露引起,也可能是由于线程执行模型问题和太多的短生命对象引起。

后台

Java线程是JVM基础的一部分。你的Java堆空间内存占用不仅仅是由于静态的和长生命的对象导致,还有可能因为短生命对象。

OutOfMemoryError 问题经常被误认为是内存泄露引起。我们经常忽略错误的线程执行模型和它们持有的JVM里的短生命对象,直到它们的执行完成我们才发现。在这种问题情形下:

• 你“预期”的程序中短生命/无状态对象(XML,JSON数据负载等)被线程持有的时间会变得很长(线程锁争用,大量数据负载,远程系统的慢响应时间等)。
• 最后,这种短生命对象会因为垃圾收集而晋升到长生命空间,比如老年代空间。
• 副作用是会导致老年代空间很快被占满,增加了Full GC(major 收集)的频率。
• 由于这种严重的情况,它将导致更多的GC 垃圾收集,增加JVM暂停时间和最终的“OutOfMemoryError: Java 堆空间”。
• 你的应用此时被停掉,你很疑惑到底怎么回事。
• 最后,你考虑增加Java堆空间或者寻找哪里有内存泄露,你真的找对路了么?

上面这种情况,你应该找到线程执行模型并确定在给定的时间内每个线程需要持有多少内存。

OK我找到了这张图片,但是线程栈大小到底是多少呢?

避免在线程栈大小和Java堆内存占用之间产生混淆是非常重要的。线程栈大小是一种特殊的内存空间,它被JVM用于存储每个方法调用。当一个线程调用方法A,它将这个调用入栈。如果方法A调用方法B,同样也会入栈。一旦方法执行完毕,这个调用便从栈里出栈。

这种线程方法调用会导致Java对象产生,并分配在Java堆里。增加线程栈的大小是没有任何效果的。而调整线程栈大小通常是要处理java.lang.stackoverflowerror错误或者“OutOfMemoryError: unable to create new native thread”错误的时候才会需要。

这里写图片描述
案例研究和问题环境

下面的分析是基于我们最近调查的一个真实的生产线问题。
1. 改变了用户web接口(使用Google Web Toolkit 和 JSON作为数据负载)后,发现Weblogic 10.0生产环境上出现了某些性能下降。
2. 初始分析发现出现了“OutOfMemoryError: Java heap space”问题并伴有过多的垃圾收集。在OOM出现后自动(XX:+HeapDumpOnOutOfMemoryError)生成了Java堆转储文件。
3. 通过verbose:gc 日志分析确认32-bit HotSpot JVM 老年代空间(1 GB 容量)被完全消耗。
4. 问题发生前和发生时自动产生了线程转储快照。
5. 此时唯一可能减轻问题的方法是在问题发生时重启受影响的Weblogic 服务器。
6. 最终解决了这个问题是将所有的改变回滚。
7.
团队首先怀疑新代码引起了内存泄露。

线程转储分析:寻找嫌疑

第一步我们要做的是对产生的线程转储数据进行分析。这种数据通常会告诉你在JVM堆里面内存分配的罪魁祸首线程。同样的它也会显示任何一个尝试从远程系统发送和接受数据的贪婪或者阻塞的线程。

我们注意的第一个样例是在Weblogic 控制服务器(JVM线程)里观察到的OOM事件和阻塞线程之间有很近的关联关系。下面是找到的原始线程模式:
这里写图片描述

000337> < [STUCK] ExecuteThread: '22' for queue: 'weblogic.kernel.Default (self-tuning)' has been busy for "672" seconds working on the request which is more than the configured time of "600" seconds.

如你所见,上面的线程出现了阻塞并且花费了很长时间从远程系统里读和接收JSON响应。一旦我们找到这个样例,接下来是找出它和JVM堆转储分析之间的关联,并且确定这个阻塞线程从堆里占用了多少内存。

堆转储分析:暴露留存的对象!

使用MAT工具来做Java堆转储分析。我们会列出不同的分析步骤,它会允许我们精确查明持有的内存大小和源头。

1.加载HotSpotJVM堆转储文件

这里写图片描述

2.选择HISTOGRAM 查看并通过ExecuteThread过滤。
* ExecuteThread 是一种Java Class,它被Weblogic内核用于对象的创建和执行*
这里写图片描述
如你所见,这个图非常有启迪作用。我们可以看到总共210个Weblogic线程被创建。
这些线程总共持有的内存占用是806M。这对于带有1G 老年代的32位的JVM进程来说是非常值得注意的。这个图也告诉了我们这个问题的核心和源于线程自己的内存占有。

3.深入分析线程内存占用

接下来是深入分析线程内存持有。右键点击ExecuteThread 类并且选择“列出所有外部引用的对象”。
这里写图片描述 
如你所见,我们通过线程堆转储分析可以发现“STUCK(阻塞)”线程和大量内存占用有很大关系。这个发现非常意外。

4.线程局部变量鉴别Thread Java Local variables identification

分析的最后一步是需要我们展开几个线程示例并了解内存占用的原始来源。

这里写图片描述

如你所见,最后一步分析发现根源在于大量的JSON数据响应。通过对转储分析,这个问题可以早点暴露出来,我们发现少量的线程花费太多时间去读取和接收JSON响应,这是大量数据负载的一个明显的症状。

很重要一点,通过方法局部变量创建的短生命对象也会出现在堆转储分析中。然而,其中的一些仅仅能被他们的父线程看到,这是由于他们没有被其他对象引用,比如这个例子。为了找出真正的调用者你或许也需要分析这个线程栈,随后通过代码审查确定最终的根源。

通过这些发现,我们的交付团队能确定最近的JSON错误代码变化的产生,在某些情形下,大量的JSON数据可以达到45M以上。如果环境使用了32位JVM而且仅仅只有1G的老年代,基于这个事实,你就能理解为什么只需要几个线程就足够触发一些性能下降。

这个案例说明,合适的容量预计和堆分析,包括你活动的应用程序的内存占有和Java EE容器线程内存占有都是非常重要的。

译者介绍:
梅小西
Java工程师,关注JVM,并发编程,喜欢研究Python,Scala,Golang等。

译者相关译文:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为什么要学JVM1、一切JAVA代码都运行在JVM之上,只有深入理解虚拟机才能写出更强大的代码,解决更深层次的问题。2、JVM是迈向高级工程师、架构师的必备技能,也是高薪、高职位的不二选择。3、同时,JVM又是各大软件公司笔试、面试的重之重,据统计,头部的30家互利网公司,均将JVM作为笔试面试的内容之一。4、JVM内容庞大、并且复杂难学,通过视频学习是最快速的学习手段。课程介绍本课程包含11个大章节,总计102课时,无论是笔试、面试,还是日常工作,可以让您游刃有余。第1章 基础入门,从JVM是什么开始讲起,理解JDK、JRE、JVM的关系,java的编译流程和执行流程,让您轻松入门。第2章 字节码文件,深入剖析字节码文件的全部组成结构,以及javap和jbe可视化反解析工具的使用。第3章 类的加载、解释、编译,本章节带你深入理解类加载器的分类、范围、双亲委托策略,自己手写类加载器,理解字节码解释器、即时编译器、混合模式、热点代码检测、分层编译等核心知识。第4章 内存模型,本章节涵盖JVM内存模型的全部内容,程序计数器、虚拟机栈、本地方法栈、方法区、永久代、元空间等全部内容。第5章 对象模型,本章节带你深入理解对象的创建过程、内存分配的方法、让你不再稀里糊涂。第6章 GC基础,本章节是垃圾回收的入门章节,带你了解GC回收的标准是什么,什么是可达性分析、安全点、安全区,四种引用类型的使用和区别等等。第7章 GC算法与收集器,本章节是垃圾回收的重点,掌握各种垃圾回收算法,分代收集策略,7种垃圾回收器的原理和使用,垃圾回收器的组合及分代收集等。第8章 GC日志详解,各种垃圾回收器的日志都是不同的,怎么样读懂各种垃圾回收日志就是本章节的内容。第9章 性能监控与故障排除,本章节实战学习jcmd、jmx、jconsul、jvisualvm、JMC、jps、jstatd、jmap、jstack、jinfo、jprofile、jhat总计12种性能监控和故障排查工具的使用。第10章 阿里巴巴Arthas在线诊断工具,这是一个特别小惊喜,教您怎样使用当前最火热的arthas调优工具,在线诊断各种JVM问题。第11章 故障排除,本章会使用实际案例讲解单点故障、高并发和垃圾回收导致的CPU过高的问题,怎样排查和解决它们。课程资料课程附带配套项目源码2个159页高清PDF理论篇课件1份89页高清PDF实战篇课件1份Unsafe源码PDF课件1份class_stats字段说明PDF文件1份jcmd Thread.print解析说明文件1份JProfiler内存工具说明文件1份字节码可视化解析工具1份GC日志可视化工具1份命令行工具cmder 1份学习方法理论篇部分推荐每天学习2课时,可以在公交地铁上用手机进行学习。实战篇部分推荐对照视频,使用配套源码,一边练习一遍学习。课程内容较多,不要一次性学太多,而是要循序渐进,坚持学习。      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值