【深入浅出JVM原理及调优】「搭建理论知识框架」全方位带你深度剖析Java线程转储分析的开发指南

简介: 学习JVM需要一定的编程经验和计算机基础知识,适用于从事Java开发、系统架构设计、性能优化、研究学习等领域的专业人士和技术爱好者。

专栏介绍

学习JVM需要一定的编程经验和计算机基础知识,适用于从事Java开发、系统架构设计、性能优化、研究学习等领域的专业人士和技术爱好者。

前提准备

  • 编程基础:具备良好的编程基础,理解面向对象编程(OOP)的基本概念,熟悉Java编程语言。
  • 数据结构与算法:对基本的数据结构和算法有一定了解,理解内存管理、线程操作等基本概念。

面向人群

学习本专栏以及本章内容的前提和适用人群如下:

  • Java开发人员:JVM是Java程序的核心执行引擎,因此Java开发人员需要深入了解JVM的工作原理和运行机制,以优化程序性能并解决相关问题。
  • 系统架构师和高级工程师:对系统整体性能、稳定性有较高要求的人群,有必要深入理解JVM以优化系统性能。
  • Java程序员和技术爱好者:具备一定Java编程经验,有意向深入了解JVM内部工作原理的人群。
  • 研究人员和学生:从事计算机科学相关研究或学习的人群,有兴趣深入研究JVM内部原理和优化方法。
  • JVM运维工程师:负责JVM性能优化、故障排查和调优的专业人员,需要对JVM有深入的理解。

知识脉络

每位Java开发者都了解到Java字节码是在Java运行时环境(JRE)上执行的。JRE包含了最为关键的组成部分:Java虚拟机(JVM),它负责分析和执行Java字节码。通常情况下,大多数Java开发者无需深入了解虚拟机的内部运行原理。即使对虚拟机的运行机制不甚了解,也不会对开发工作产生太多影响。然而,对JVM有一定了解的话,将更有助于深入理解Java语言,并解决一些看似困难的问题。

本专栏全面系统地剖析了特定虚拟机产品(即HotSpot,Oracle官方虚拟机)的实现,本人不仅深刻地讲解了看似深奥的原理,还提供了大量易于上手的实践案例,下面是总体的JVM相关的知识拓扑架构。

tips:当然还有一些最新的JVM特性未在这张图并非展示本专栏的全部内容,另外还包含了最新的JVM特性。


分析线程转储

本文将教您如何分析JVM线程转储,并确定问题的根本原因。从我的角度来看,线程转储分析是任何参与Java EE生产支持的个人都需要掌握的最重要技能集。您可以从线程转储快照中获得的信息量通常远远超出了您的想象。

线程转储分析的介绍

在深入研究线程转储分析和问题模式之前,了解基本原理是至关重要的。

JVM和线程运行机制

"Java虚拟机是Java EE平台的基石,它为您的中间件和应用提供了运行环境。在这里,您的程序会被部署并得以运行。"

JVM是一种中间件软件,为Java/Java EE程序提供运行环境。它支持字节码格式的运行时,并具备多种特性,如IO设施、数据结构、线程管理、安全和监控。此外,JVM还通过垃圾收集器实现动态内存分配和管理。

JVM和中间件之间的软件交互

下面的图表显示了JVM、中间件和应用程序之间的高级交互视图。

这是一个展示JVM、中间件和应用程序之间典型而简单交互的图示。在标准的Java EE应用程序中,线程的分配主要在中间件内核和JVM之间完成(尽管在应用程序本身或某些API直接创建线程时可能会有一些例外,但这并不常见,需要非常小心地处理)。

注意,JVM本身会管理一些线程,例如垃圾收集(GC)线程,用于处理并发的垃圾收集任务。

由于大多数线程分配是由Java EE容器完成的,因此理解和识别线程堆栈跟踪,并正确地从线程转储数据中识别它们非常重要。这将帮助您快速了解Java EE容器试图执行的请求类型。

从线程转储分析的角度来看,您可以学习如何区分不同的线程池,并识别请求类型。这将帮助您更好地理解JVM中的线程池,并进行有效的分析。

JVM线程转储

JVM线程转储器是在特定时间生成的一个快照,它提供了所有已创建的Java线程的完整列表。这个转储器可以帮助您获取关于线程的详细信息,以便进行分析和调试。

Java快照的基本信息

找到的每个单独的Java线程都为您提供以下信息:

  • 线程名称:通常被中间件供应商用于识别线程Id及其关联的线程池名称和状态(正在运行、被卡住等)。
  • 线程类型和优先级等:守护进程prio=3 中间件软件通常创建它们的线程作为守护进程,这意味着它们的线程在后台运行;为其用户提供服务,例如您的Java EE应用程序
  • Java线程ID ex: tid=0x000000011e52a800 这是通过java.lang获得的Java线程ID。Thread.getId(),通常实现为一个自动增量的长1..n
  • 本地线程ID ex: nid=0x251c关键信息,因为这个本地线程ID允许您相关,例如哪些线程从操作系统的角度使用最多的CPU在您的JVM等。
  • Java线程状态和细节等:等待监视器输入[0x fffffff ea5afb000]java.lang.thread。状态:已阻塞(在对象监视器上)允许快速了解线程状态及其潜在的当前阻塞条件
  • Java线程堆栈跟踪:这是迄今为止您将从线程转储文件中找到的最重要的数据。这也是您将花费大部分分析时间的地方,因为Java堆栈跟踪为您提供了90%的信息,以便查明许多问题模式类型的根本原因。
  • Java堆分解:从HotSpot VM 1.6开始,您还将在线程转储快照的底部发现HotSpot内存空间利用率的细分,如Java堆(YoungGen,OldGen)和PermGen/metaspace。当怀疑过多的GC是可能的根本原因时,这一点非常有用,因此您可以对已找到的线程数据/模式进行开箱即用的相关性。

内存回收日志

Heap
PSYoungGen total 466944K, used 178734K [0xffffffff45c00000, 0xffffffff70800000,0xffffffff70800000)
eden space 233472K, 76% used 
[0xffffffff45c00000,0xffffffff50ab7c50,0xffffffff54000000)
from space 233472K, 0% used 
[0xffffffff62400000,0xffffffff62400000,0xffffffff70800000)
to space 233472K, 0% used 
[0xffffffff54000000,0xffffffff54000000,0xffffffff62400000)
PSOldGen total 1400832K, used 1400831K [0xfffffffef0400000, 
0xffffffff45c00000, 0xffffffff45c00000)
object space 1400832K, 99% used 
[0xfffffffef0400000,0xffffffff45bfffb8,0xffffffff45c00000)
PSPermGen total 262144K, used 248475K [0xfffffffed0400000, 
0xfffffffee0400000, 0xfffffffef0400000)
object space 262144K, 94% used 
[0xfffffffed0400000,0xfffffffedf6a6f08,0xfffffffee0400000)
AI 代码解读

线程转储分解概述

为了让你更好地理解,找到下面的图表,显示一个HotSpot VM线程转储及其常见的线程池的可视化分解发现:

您可以从HotSpot VM线程转储文件中找到一些信息。根据您的问题模式,其中的一些将比其他的更重要,现在,根据我们的示例Hotspot线程转储,在下面找到每个线程转储部分的详细说明。

全线程转储标识符

这基本上是唯一的关键字,一旦你生成线程转储(例如:通过UNIX,你可以在<PID>中找到。这是线程转储快照数据的开头。

Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.0-b11 mixed mode):
AI 代码解读

Java EE中间件,第三方和自定义应用程序线程

这部分是线程转储的核心,您通常将在这里花费大部分的分析时间。找到的线程的数量将取决于您使用的中间件软件、第三方库(可能有自己的线程)和您的应用程序(如果创建任何自定义线程,这通常不是最佳实践)。

在我们的示例线程转储中,Weblogic是所使用的中间件。从Weblogic9.2开始,使用一个自调优线程池和唯一标识符“Weblogic.内核”。默认值(自调)。

"[STANDBY] ExecuteThread: '414' for queue: 'weblogic.kernel.Default (selftuning)'" daemon prio=3 tid=0x000000010916a800 nid=0x2613 in Object.wait() 
[0xfffffffe9edff000]
 java.lang.Thread.State: WAITING (on object monitor)
 at java.lang.Object.wait(Native Method)
 - waiting on <0xffffffff27d44de0> (a weblogic.work.ExecuteThread)
 at java.lang.Object.wait(Object.java:485)
 at weblogic.work.ExecuteThread.waitForRequest(ExecuteThread.java:160)
 - locked <0xffffffff27d44de0> (a weblogic.work.ExecuteThread)
 at weblogic.work.ExecuteThread.run(ExecuteThread.java:181)
AI 代码解读
HotSpot VM Thread

这是一个由Hotspot管理的内部线程,以便执行内部本机操作。通常,您不应该担心这个问题,除非您看到高CPU(通过线程转储和prstat /本地线程id相关性)。

"VM Periodic Task Thread" prio=3 tid=0x0000000101238800 nid=0x19 waiting on condition
AI 代码解读

HotSpot GC Thread

当使用HotSpot并行GC时(现在在使用多物理核硬件时很常见),HotSpot VM会默认创建或根据JVM调优特定#的GC线程。这些GC线程允许VM以并行的方式执行其定期的GC清理,从而导致GC时间的总体减少;以增加CPU利用率为代价。

"GC task thread#0 (ParallelGC)" prio=3 tid=0x0000000100120000 nid=0x3 runnable 
"GC task thread#1 (ParallelGC)" prio=3 tid=0x0000000100131000 nid=0x4 runnable
AI 代码解读

这也是至关重要的数据,因为当遇到与GC相关的问题时,如GC过多、内存泄漏过多等,您将能够使用本地id值(nid=0x3)将从OS / Java进程中观察到的任何高CPU与这些线程关联起来。

JNI全局引用计数

JNI(Java本机接口)全局引用基本上是从本地代码到由Java垃圾收集器管理的Java对象的对象引用。它的作用是防止收集本机代码仍在使用的对象,但在技术上讲,在Java代码中没有“实时”引用。

为了检测与JNI相关的泄漏,密切关注JNI的参考文献也很重要。如果程序直接使用JNI或使用第三方工具,如监控工具,容易导致本机内存泄漏,就会发生这种情况

JNI global references: 1925
AI 代码解读

Java堆利用率视图

这些数据被添加到JDK中,并为您提供了Hotspot堆的一个简短和快速的视图。我发现它很有用当故障排除GC相关问题以及高CPU因为你得到线程转储和Java堆在一个快照允许你确定(或排除)任何压力点在一个特定的Java堆内存空间以及当前线程计算目前正在完成。正如您在我们的示例线程转储中看到的,Java堆OldGen已经用爆了!

Heap
PSYoungGen total 466944K, used 178734K [0xffffffff45c00000, 
0xffffffff70800000, 0xffffffff70800000)
 eden space 233472K, 76% used 
[0xffffffff45c00000,0xffffffff50ab7c50,0xffffffff54000000)
 from space 233472K, 0% used 
[0xffffffff62400000,0xffffffff62400000,0xffffffff70800000)
 to space 233472K, 0% used 
[0xffffffff54000000,0xffffffff54000000,0xffffffff62400000)
PSOldGen total 1400832K, used 1400831K [0xfffffffef0400000, 
0xffffffff45c00000, 0xffffffff45c00000)
 object space 1400832K, 99% used
[0xfffffffef0400000,0xffffffff45bfffb8,0xffffffff45c00000)
PSPermGen total 262144K, used 248475K [0xfffffffed0400000, 
0xfffffffee0400000, 0xfffffffef0400000)
 object space 262144K, 94% used 
[0xfffffffed0400000,0xfffffffedf6a6f08,0xfffffffee0400000)
AI 代码解读

为了让您快速从线程转储中识别出问题模式,您首先需要了解如何读取线程堆栈跟踪以及如何正确地获取“故事”。这意味着,如果我让你告诉我线程#38在做什么;你应该能够精确地回答;包括线程堆栈跟踪是否显示了一个健康(正常)和挂起条件。

Java堆栈跟踪重新访问

你们大多数人都熟悉Java堆栈跟踪。这是我们在抛出Java异常时从服务器和应用程序日志文件中找到的典型数据。在此上下文中,Java堆栈跟踪为我们提供了触发Java异常的线程的代码执行路径,例如,一个java.lang.NoClassDefFound错误,java.lang.Nullpointer异常等。这样的代码执行路径使我们能够看到最终导致Java异常的不同代码层。

Java堆栈跟踪必须始终从自下而上读取
  • 底部的一行将公开请求的发起者,例如Java / Java EE容器线程。
  • 堆栈跟踪顶部的第一行将显示最后一个异常被触发的Java类。

让我们通过一个简单的例子来介绍一下这个过程。我们创建了一个示例Java程序,简单地执行一些类方法调用并抛出一个异常。所生成的程序输出如下所述:

JavaStrackTraceSimulator
Author: Pierre-Hugues Charbonneau
http://javaeesupportpatterns.blogspot.com
Exception in thread "main" java.lang.IllegalArgumentException: 
 at org.ph.javaee.training.td.Class2.call(Class2.java:12)
 at org.ph.javaee.training.td.Class1.call(Class1.java:14)
 at org.ph.javaee.training.td.JavaSTSimulator.main(JavaSTSimulator.java:20)
AI 代码解读
  • Java program JavaSTSimulator is invoked (via the "main" Thread)
  • The simulator then invokes method call() from Class1
  • Class1 method call() then invokes Class2 method call()
  • Class2 method call()throws a Java Exception: java.lang.IllegalArgumentException
  • The Java Exception is then displayed in the log / standard output

如您所示,导致此异常的代码执行路径总是从下向上显示。上面的分析过程对于任何Java程序员来说都应该是非常熟悉的。接下来您将看到的是,线程转储线程堆栈跟踪分析过程与上面的Java堆栈跟踪分析非常相似。

线程转储:线程堆栈跟踪分析

从JVM生成的线程转储为您提供了整个JVM进程中所有“已创建的”线程的代码级执行快照。已创建的线程并不意味着所有这些线程实际上都在做一些事情。在从Java EE容器JVM生成的典型线程转储快照中:

  • 一些线程可以执行原始的计算任务,如XML解析、IO /磁盘访问等。
  • 一些线程可能正在等待一些阻塞的IO调用,比如远程Web服务调用,DB/JDBC查询等。
  • 一些线程可能涉及到垃圾收集,例如GC线程。
  • 一些线程将等待一些工作要做(不做任何工作的线程通常处于等待状态)
  • 一些线程可能正在等待其他线程的完成工作,例如,线程等待获取某些对象上的监视器锁(同步块{})。

线程堆栈跟踪为您提供了其当前执行情况的快照。第一行通常包括线程的本机信息,如其名称、状态、地址等。必须从自下而上开始读取当前的执行堆栈跟踪。请遵循下面的分析过程。你使用线程转储分析的经验越多,你就能越快地阅读和识别每个线程所执行的工作:

  1. 开始从底部读取线程堆栈跟踪
  2. 首先,识别发起者(Java EE容器线程、自定义线程、GC线程、JVM内部线程、独立的Java程序“主”线程等)。
  3. 下一步是确定线程正在执行的请求的类型(WebApp、Web服务、JMS、Remote EJB(RMI)、内部Java EE容器等)。
  4. 下一步是从执行堆栈中跟踪您的应用程序模块涉及线程尝试执行的实际核心工作。分析的复杂性将取决于中间件环境和应用程序的抽象层。
  5. 下一步是查看在第一行之前的最后一个~10-20行。识别线程所涉及的协议或工作,例如HTTP调用、套接字通信、JDBC或原始计算任务,如磁盘访问、类加载等。
  6. 下一步是看第一行。第一行通常告诉线程状态上的LOT,因为它是在您拍摄快照时执行的当前代码片段。

  7. 最后两个步骤的结合将为您提供信息的核心,以总结线程所涉及的工作和/或挂起条件。

现在,使用从JBoss生产环境捕获的线程转储线程堆栈跟踪的真实示例,找到上述步骤的可视化细分。在本例中,许多线程在创建JAX-WS服务实例的新实例时都显示了类似的过度IO问题模式。

正如您所看到的,最后10行和第一行将告诉我们线程涉及什么挂起或慢状态,如果有的话。从底部开始的行将给我们提供发起者和请求类型的详细信息。

相关文章
|
1月前
|
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
172 60
【Java并发】【线程池】带你从0-1入门线程池
JVM简介—1.Java内存区域
本文详细介绍了Java虚拟机运行时数据区的各个方面,包括其定义、类型(如程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区和直接内存)及其作用。文中还探讨了各版本内存区域的变化、直接内存的使用、从线程角度分析Java内存区域、堆与栈的区别、对象创建步骤、对象内存布局及访问定位,并通过实例说明了常见内存溢出问题的原因和表现形式。这些内容帮助开发者深入理解Java内存管理机制,优化应用程序性能并解决潜在的内存问题。
122 29
JVM简介—1.Java内存区域
JVM实战—4.JVM垃圾回收器的原理和调优
本文详细探讨了JVM垃圾回收机制,包括新生代ParNew和老年代CMS垃圾回收器的工作原理与优化方法。内容涵盖ParNew的多线程特性、默认线程数设置及适用场景,CMS的四个阶段(初始标记、并发标记、重新标记、并发清理)及其性能分析,以及如何通过合理分配内存区域、调整参数(如-XX:SurvivorRatio、-XX:MaxTenuringThreshold等)来优化垃圾回收。此外,还结合电商大促案例,分析了系统高峰期的内存使用模型,并总结了YGC和FGC的触发条件与优化策略。最后,针对常见问题进行了汇总解答,强调了基于系统运行模型进行JVM参数调优的重要性。
JVM实战—4.JVM垃圾回收器的原理和调优
JVM实战—1.Java代码的运行原理
本文介绍了Java代码的运行机制、JVM类加载机制、JVM内存区域及其作用、垃圾回收机制,并汇总了一些常见问题。
JVM实战—1.Java代码的运行原理
Java网络编程,多线程,IO流综合小项目一一ChatBoxes
**项目介绍**:本项目实现了一个基于TCP协议的C/S架构控制台聊天室,支持局域网内多客户端同时聊天。用户需注册并登录,用户名唯一,密码格式为字母开头加纯数字。登录后可实时聊天,服务端负责验证用户信息并转发消息。 **项目亮点**: - **C/S架构**:客户端与服务端通过TCP连接通信。 - **多线程**:采用多线程处理多个客户端的并发请求,确保实时交互。 - **IO流**:使用BufferedReader和BufferedWriter进行数据传输,确保高效稳定的通信。 - **线程安全**:通过同步代码块和锁机制保证共享数据的安全性。
72 23
|
28天前
|
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
当我们创建一个`ThreadPoolExecutor`的时候,你是否会好奇🤔,它到底发生了什么?比如:我传的拒绝策略、线程工厂是啥时候被使用的? 核心线程数是个啥?最大线程数和它又有什么关系?线程池,它是怎么调度,我们传入的线程?...不要着急,小手手点上关注、点赞、收藏。主播马上从源码的角度带你们探索神秘线程池的世界...
100 0
【源码】【Java并发】【线程池】邀请您从0-1阅读ThreadPoolExecutor源码
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
144 14
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
66 13
JVM实战—5.G1垃圾回收器的原理和调优
本文详细解析了G1垃圾回收器的工作原理及其优化方法。首先介绍了G1通过将堆内存划分为多个Region实现分代回收,有效减少停顿时间,并可通过参数设置控制GC停顿时长。接着分析了G1相较于传统GC的优势,如停顿时间可控、大对象不进入老年代等。还探讨了如何合理设置G1参数以优化性能,包括调整新生代与老年代比例、控制GC频率及避免Full GC。最后结合实际案例说明了G1在大内存场景和对延迟敏感业务中的应用价值,同时解答了关于内存碎片、Region划分对性能影响等问题。
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。

热门文章

最新文章