专栏介绍

学习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相关的知识拓扑架构。

【深化浅出JVM原理及调优】「建立理论知识结构」全方位带你深度剖析Java线程转储剖析的开发攻略

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


剖析线程转储

本文将教您怎么剖析JVM线程转储,并确认问题的根本原因。从我的视点来看,线程转储剖析是任何参与Java EE出产支撑的个人都需求掌握的最重要技能集。您可以从线程转储快照中取得的信息量一般远远超出了您的幻想。

线程转储剖析的介绍

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

JVM和线程运转机制

“Java虚拟机是Java EE渠道的柱石,它为您的中间件和使用供给了运转环境。在这儿,您的程序会被布置并得以运转。”

【深化浅出JVM原理及调优】「建立理论知识结构」全方位带你深度剖析Java线程转储剖析的开发攻略

JVM是一种中间件软件,为Java/Java EE程序供给运转环境。它支撑字节码格式的运转时,并具有多种特性,如IO设备、数据结构、线程办理、安全和监控。此外,JVM还经过废物搜集器实现动态内存分配和办理。

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

下面的图表显现了JVM、中间件和使用程序之间的高档交互视图。

【深化浅出JVM原理及调优】「建立理论知识结构」全方位带你深度剖析Java线程转储剖析的开发攻略

这是一个展现JVM、中间件和使用程序之间典型而简单交互的图示。在标准的Java EE使用程序中,线程的分配主要在中间件内核和JVM之间完结(虽然在使用程序自身或某些API直接创立线程时或许会有一些破例,但这并不常见,需求非常小心地处理)。

【深化浅出JVM原理及调优】「建立理论知识结构」全方位带你深度剖析Java线程转储剖析的开发攻略

注意,JVM自身会办理一些线程,例如废物搜集(GC)线程,用于处理并发的废物搜集任务。

由于大多数线程分配是由Java EE容器完结的,因而了解和辨认线程仓库盯梢,并正确地从线程转储数据中辨认它们非常重要。这将帮助您快速了解Java EE容器企图履行的恳求类型。

从线程转储剖析的视点来看,您可以学习怎么区别不同的线程池,并辨认恳求类型。这将帮助您更好地了解JVM中的线程池,并进行有用的剖析。

JVM线程转储

JVM线程转储器是在特定时刻生成的一个快照,它供给了一切已创立的Java线程的完好列表。这个转储器可以帮助您获取关于线程的详细信息,以便进行剖析和调试。

Java快照的基本信息

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

【深化浅出JVM原理及调优】「建立理论知识结构」全方位带你深度剖析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)

线程转储分化概述

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

【深化浅出JVM原理及调优】「建立理论知识结构」全方位带你深度剖析Java线程转储剖析的开发攻略

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

全线程转储标识符

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

Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.0-b11 mixed mode):

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)
HotSpot VM Thread

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

"VM Periodic Task Thread" prio=3 tid=0x0000000101238800 nid=0x19 waiting on condition

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

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

JNI大局引证计数

JNI(Java本机接口)大局引证基本上是从本地代码到由Java废物搜集器办理的Java目标的目标引证。它的作用是防止搜集本机代码仍在运用的目标,但在技能上讲,在Java代码中没有“实时”引证。

为了检测与JNI相关的走漏,亲近重视JNI的参考文献也很重要。假如程序直接运用JNI或运用第三方东西,如监控东西,简单导致本机内存走漏,就会产生这种情况

JNI global references: 1925

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)

为了让您快速从线程转储中辨认出问题形式,您首要需求了解怎么读取线程仓库盯梢以及怎么正确地获取“故事”。这意味着,假如我让你告知我线程#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)
  • 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问题形式。

【深化浅出JVM原理及调优】「建立理论知识结构」全方位带你深度剖析Java线程转储剖析的开发攻略

正如您所看到的,最终10行和榜首即将告知咱们线程触及什么挂起或慢状况,假如有的话。从底部开端的即将给咱们供给发起者和恳求类型的详细信息。