Java虚拟机三件套解析

简介: Java虚拟机(JVM)生成3个关键工件,这些工件对于优化性能和解决生产问题很有用。这些工件是:垃圾收集(GC)日志线程转储(ThreadDump)堆转储(HeapDump 在本文中,我将尝试简要解析下这3个关键工件,描述下在什么场景中使用它们,它们的外观如何,如何捕获它们,如何分析它们及其之间的差异。

      Java虚拟机(JVM)生成3个关键工件,这些工件对于优化性能和解决生产问题很有用。这些工件是:

  • 垃圾收集(GC)日志
  • 线程转储(ThreadDump)
  • 堆转储(HeapDump

      在本文中,我将尝试简要解析下这3个关键工件,描述下在什么场景中使用它们,它们的外观如何,如何捕获它们,如何分析它们及其之间的差异。      

垃圾收集日志GC Log)

       1、什么是GC日志?

      GC日志包含垃圾回收事件的相关信息。它将指示运行了多少个GC事件,它们是什么类型的GC事件(即,年轻的GC还是全局的GC),每个GC事件暂停了应用程序多长时间,每个GC事件回收了多少个对象。

       2、GC日志的外观如何?

       这里以G1垃圾回收策略为例,展示部分样本的垃圾收集日志文件,内容如下所示:


[administrator@JavaLangOutOfMemory luga %]less echo-admin-gc.log
... ...
2015-09-14T12:32:24.398-0700: 0.356: [GC pause (G1 Evacuation Pause) (young), 0.0215287 secs]
   [Parallel Time: 20.0 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 355.9, Avg: 356.3, Max: 358.4, Diff: 2.4]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 6.4, Max: 16.7, Diff: 16.7, Sum: 51.4]
      [Update RS (ms): Min: 0.0, Avg: 1.0, Max: 2.5, Diff: 2.5, Sum: 8.2]
         [Processed Buffers: Min: 0, Avg: 1.1, Max: 5, Diff: 5, Sum: 9]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.1, Diff: 0.1, Sum: 0.1]
      [Object Copy (ms): Min: 2.9, Avg: 11.9, Max: 17.5, Diff: 14.6, Sum: 95.3]
      [Termination (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.9]
         [Termination Attempts: Min: 1, Avg: 2.5, Max: 5, Diff: 4, Sum: 20]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.5]
      [GC Worker Total (ms): Min: 17.5, Avg: 19.6, Max: 19.9, Diff: 2.4, Sum: 156.5]
      [GC Worker End (ms): Min: 375.8, Avg: 375.9, Max: 375.9, Diff: 0.1]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.5 ms]
   [Other: 1.0 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.4 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.4 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 12.0M(12.0M)->0.0B(14.0M) Survivors: 0.0B->2048.0K Heap: 12.6M(252.0M)->7848.3K(252.0M)]
 [Times: user=0.08 sys=0.00, real=0.02 secs] 
2015-09-14T12:32:24.932-0700: 0.889: [GC pause (G1 Evacuation Pause) (young), 0.0469650 secs]

       3、什么场景下使用GC日志?

      垃圾收集日志用于研究应用程序的GC和内存性能。用于优化GC暂停时间,用于确定应用程序的最佳内存大小,还用于解决与内存相关的问题。

       4、如何生成GC日志?

       在应用程序启动文件中,可以通过传递以下JVM参数来生成垃圾收集日志,具体如下:

       对于Java 8以及之前的版本,具体配置参数:


-XX:+PrintGC
-XX:+PrintGCDateStamps
-Xloggc:<file-path>

      对于从Java 9之后的Java版本:


-Xlog:gc *:file = <file-path>
file-path:是要写入垃圾收集日志文件的位置

       5、如何理解GC日志?

      垃圾收集日志格式会有所不同,具体取决于我们当前环境中的JVM供应商(Oracle、HP、IBM、Azul等及其他),Java版本(1.5、5、6、7、8、9、10、11、12、15…),垃圾收集算法(串行,并行,CMS,G1,Shenandoah,Z GC)和JVM参数。因此,没有一种可用的标准化格式。

       6、哪些工具可以分析GC日志?

      有多种垃圾收集日志分析工具。根据笔者经验,目前比较受欢迎的一些工具:GCeasy、IBM GC和内存可视化工具、HP JMeter、Google Garbage Cat及其他。      

线程转储(ThreadDump

       1、什么是线程转储?

      线程转储是指在某一时间点在应用程序中运行的所有线程的快照。它包含有关应用程序中每个线程的所有信息,例如:线程状态,线程ID,本机ID,线程名称,堆栈跟踪,优先级等等。

       2、线程转储的外观如何?

      常见线程转储文件内容如下所示:


2016-12-03 10:37:28
Full thread dump Java HotSpot(TM) 64-Bit Server VM (23.7-b01 mixed mode):
"Attach Listener" daemon prio=10 tid=0x000000001b03e000 nid=0x3954 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"InvoiceGeneratedQC-A99-6" prio=10 tid=0x00002b7cfc6fb000 nid=0x4479 runnable [0x00002b7d17ab8000]
   java.lang.Thread.State: RUNNABLE
  at com.buggyCompany.rt.util.ItinerarySegmentProcessor.setConnectingFlight(ItinerarySegmentProcessor.java:380)
  at com.buggyCompany.rt.util.ItinerarySegmentProcessor.processTripType0(ItinerarySegmentProcessor.java:366)
  at com.buggyCompany.rt.util.ItinerarySegmentProcessor.processItineraryByTripType(ItinerarySegmentProcessor.java:254)
  at com.buggyCompany.rt.util.ItinerarySegmentProcessor.templateMethod(ItinerarySegmentProcessor.java:399)
  at com.buggyCompany.qc.gds.InvoiceGeneratedFacade.readTicketImage(InvoiceGeneratedFacade.java:252)
  at com.buggyCompany.qc.gds.InvoiceGeneratedFacade.doOrchestrate(InvoiceGeneratedFacade.java:151)
  at com.buggyCompany.framework.gdstask.BaseGDSFacade.orchestrate(BaseGDSFacade.java:32)
  at com.buggyCompany.framework.gdstask.BaseGDSFacade.doWork(BaseGDSFacade.java:22)
  at com.buggyCompany.framework.concurrent.buggyCompanyCallable.call(buggyCompanyCallable.java:80)
  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
  at java.util.concurrent.FutureTask.run(FutureTask.java:166)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
  at java.lang.Thread.run(Thread.java:722)
"InvoiceGeneratedQC-H87-6" prio=10 tid=0x00002b7cfc94a000 nid=0x4472 runnable [0x00002b7d16fad000]
   java.lang.Thread.State: RUNNABLE
  at com.buggyCompany.rt.util.ItinerarySegmentProcessor.setConnectingFlight(ItinerarySegmentProcessor.java:380)
  at com.buggyCompany.rt.util.ItinerarySegmentProcessor.processTripType0(ItinerarySegmentProcessor.java:366)
  at com.buggyCompany.rt.util.ItinerarySegmentProcessor.processItineraryByTripType(ItinerarySegmentProcessor.java:254)
  at com.buggyCompany.rt.util.ItinerarySegmentProcessor.templateMethod(ItinerarySegmentProcessor.java:399)
  at com.buggyCompany.qc.gds.InvoiceGeneratedFacade.readTicketImage(InvoiceGeneratedFacade.java:252)
  at com.buggyCompany.qc.gds.InvoiceGeneratedFacade.doOrchestrate(InvoiceGeneratedFacade.java:151)
  at com.buggyCompany.framework.gdstask.BaseGDSFacade.orchestrate(BaseGDSFacade.java:32)
  at com.buggyCompany.framework.gdstask.BaseGDSFacade.doWork(BaseGDSFacade.java:22)
  at com.buggyCompany.framework.concurrent.buggyCompanyCallable.call(buggyCompanyCallable.java:80)
  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
  at java.util.concurrent.FutureTask.run(FutureTask.java:166)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
  at java.lang.Thread.run(Thread.java:722)

       3、在什么场景下使用线程转储?

      线程转储主要用于解决生产问题,例如CPU尖峰,应用程序无响应,响应时间短,线程挂起,内存消耗高。

       4、如何生成线程转储?

      可以使用8个不同的选项从正在运行的应用程序中捕获线程转储。可参考之前的文章:Java ThreadDump 生成解析。进行线程转储的最常见选择是使用“ Jstack”工具。Jstack工具位于{JDK_HOME}/bin文件夹中。这是捕获线程转储所需发出的命令:


[administrator@JavaLangOutOfMemory luga %]jstack -l  <pid> > <file-path>
pid:是应用程序的进程ID,应捕获其线程转储
file-path:是将写入线程转储的文件路径。

       5、如何理解线程转储?

     可参考之前的文章:Java ThreadDump 生成解析

      6、使用哪些工具来分析线程转储?

      以下是使用最广泛的线程转储分析工具:fastThread、Samurai、IBM Thread&Monitor分析器、Visual VM及其他等等。      

堆转储(HeapDump

       1、什么是堆转储?

      堆转储是指在某一时间点应用程序内存的快照。它包含各种各样的信息,例如内存中的对象是什么,它们携带的值是什么,大小是什么,它们引用的其他对象是什么等。

       2、堆转储的外观如何?

      普通的文件,通常以指定文件类型生成,例如,可生成.phrof后缀类型,或直接heapdump等。(注意:此文件采用二进制格式。因此实际上不易阅读)。

      3、堆转储在什么场景下使用?

      堆转储主要用于解决与内存相关的OutOfMemoryError问题。

     4、如何生成堆转储?

    可以使用7个不同的选项从运行的应用程序中捕获堆转储。可参考之前的文章:Java HeapDump 生成解析进行堆转储的最常见选项是使用“ Jmap”工具。Jmap工具位于{JDK_HOME}/bin文件夹中。具体命令如下:


[administrator@JavaLangOutOfMemory luga %]jmap -dump:format = b,file = <文件路径> <pid>
pid:Java进程ID,应捕获其堆转储
file-path:堆转储将写入的文件路径。

       5、如何理解堆转储?

      堆转储文件为二进制格式,并且通常较大。除此之外,它们的格式严重缺乏文档。因此,必须使用堆转储分析工具来分析和理解它们。

       6、使用哪些工具来分析堆转储?

      以下是使用最广泛的堆转储分析工具:Eclipse MAT、HeapHero、JVisualVM及其他。

相关文章
|
18小时前
|
Java
JAVA多线程深度解析:线程的创建之路,你准备好了吗?
【6月更文挑战第19天】Java多线程编程提升效率,通过继承Thread或实现Runnable接口创建线程。Thread类直接继承启动简单,但限制多继承;Runnable接口实现更灵活,允许类继承其他类。示例代码展示了两种创建线程的方法。面对挑战,掌握多线程,让程序高效运行。
|
1天前
|
安全 Java 调度
Java Queue深度解析:LinkedList为何成为队列的最佳实践?
【6月更文挑战第18天】Java的`LinkedList`适合作为队列,因其双向链表结构支持O(1)的头尾操作。非线程安全的`LinkedList`在单线程环境下效率高,多线程时可通过`Collections.synchronizedList`封装。此外,它还可兼做栈和双端队列,提供任务调度的高效解决方案。
|
2天前
|
存储 缓存 Java
滚雪球学Java(64):LinkedHashSet原理及实现解析
【6月更文挑战第18天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 1
滚雪球学Java(64):LinkedHashSet原理及实现解析
|
2天前
|
存储 算法 Java
Java Set深度解析:为何它能成为“无重复”的代名词?
【6月更文挑战第17天】Java Set实现无重复元素原理:HashSet利用哈希表(HashMap基础),通过hashCode()和equals()检查元素唯一性;TreeSet基于红黑树保持元素排序和唯一。选择合适的Set类(HashSet、TreeSet、LinkedHashSet)并正确实现对象的hashCode()和equals()是关键。示例代码展示了HashSet的去重功能。
|
2天前
|
Java
【Java】Object类简单解析
【Java】Object类简单解析
7 1
|
2天前
|
Java 开发者 C++
Java面向对象的终极挑战:抽象类与接口的深度解析!
【6月更文挑战第17天】在Java OOP中,抽象类和接口助力代码复用与扩展。抽象类不可实例化,提供通用框架,适合继承;接口包含纯抽象方法,支持多态与松耦合。选择抽象类用于继承已有方法和状态,接口则适用于不相关类共享行为。Java 8后接口能含默认方法,增加设计灵活性。抽象类与接口常结合使用,以实现最佳设计,如`Shape`抽象类实现`Drawable`和`Selectable`接口,展现两者协同优势。理解和熟练运用这对概念是提升代码质量的关键。
|
4天前
|
存储 缓存 算法
滚雪球学Java(62):HashSet的底层实现原理解析
【6月更文挑战第16天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
11 3
滚雪球学Java(62):HashSet的底层实现原理解析
|
5天前
|
Java 开发者 UED
Java中的异常处理机制深度解析
在Java编程中,异常处理是确保软件健壮性的关键因素。本文将深入探讨Java的异常处理机制,包括异常的类型、如何捕获和处理异常,以及最佳实践。我们将通过实例学习如何优雅地处理异常,避免常见的陷阱,并提升代码的可维护性和用户体验。
|
5天前
|
搜索推荐 Java 程序员
【案例解析】从菜鸟到高手,如何优雅地使用Java条件语句?
【6月更文挑战第14天】本文介绍了Java编程中条件语句的重要性,特别是if-else和switch的使用。通过四个案例,展示了如何优雅地运用这些语句:案例一演示了基础if-else用于用户登录响应;案例二利用switch处理枚举类型,如订单状态;案例三展示了条件语句的嵌套与组合,用于游戏评分系统;案例四探讨了代码优化与重构,减少冗长的if-else结构。文章强调,掌握条件语句能提升编码效率和代码质量,鼓励开发者不断实践以写出高效优雅的代码。
|
5天前
|
Java 开发者
别再傻傻分不清!Java if-else与switch的性能对比全解析!
【6月更文挑战第14天】本文探讨了Java中if-else与switch语句的性能异同。虽然现代JVM的优化使得两者性能差异不大,但特定情况下仍有区别。switch通过跳转表提供高效执行,尤其适用于枚举和固定值,而if-else依赖条件顺序,JVM可能优化常量条件。实验显示,处理大量重复case时,switch性能更优。选择时还需考虑可读性和维护性,灵活运用以实现高效优雅的代码。

推荐镜像

更多