应用程序比预期的运行速度慢或比以前运行地慢,或者这个应用程序无响应或挂起。您在生产或在开发期间可能遇到这些情况。这些问题的根源是什么?通常,起因 -- 例如内存泄漏、死锁和同步问题 -- 是很难诊断的。第6 版Java 平台,标准版(Java SE) 提供了现成的监视和管理功能,以帮助您诊断许多共同的Java SE 问题。
这篇文章是监视和管理Java SE 6 应用程序的一个简短课程。它首先描述Java SE 应用程序中共同的问题和它们的症状。其次,它提供了Java SE 6 的监视和管理功能概要。最后,它描述如何使用各种各样的Java 开发套件 (JDK) 工具诊断这些问题。
注:针对Java SE 平台规范的所有API 附加项或其他改进都需要经过JSR 270 专家组 的审核和批准。
|
一般说来,Java SE 应用程序中的问题与重要资源(例如内存、线程、类和锁)有关。资源冲突或泄漏可能导致性能问题或意想不到的错误。表1 总结了一些Java SE 应用程序中共同的问题和它们的症状,并列出开发人员可以用来帮助诊断每个问题的来源的工具。
|
问题 |
症状 |
诊断工具 |
|
||
OutOfMemoryError |
||
内存的使用增长 |
||
|
类的增长率很高 |
内存图( jmap ) |
|
对象被意外引用 |
jconsole 或 jmap 与 jhat |
对象挂起,无法完成 |
jconsole |
|
对象监视器或 java.util.concurrent 锁上的线程阻拦 |
jconsole |
|
线程CPU 时间连续地增加 |
jconsole 与JTop |
|
线程的争用统计值很高 |
jconsole |
|
|
Java 虚拟机(JVM)* 有以下类型的内存:堆内存 、非堆内存 和本机内存。
堆内存 是为所有类实例和数组分配内存的运行时数据区域。非堆内存 包括对JVM 进行内部处理或优化所需的方法区域和内存。它存放每个类的结构,例如一个运行时常数池、字段和方法数据,以及方法和构造函数代码。本机内存 是操作系统处理的虚拟内存。当内存不足,无法分配给应用程序时,即抛出 java.lang.OutOfMemoryError 。
以下错误信息即为每种类型的内存可能抛出的 OutOfMemoryErrors 错误消息:
堆内存错误。 当应用程序创建一个新的对象,但是堆没有充足的空间,并且不可能进一步被扩展时,将抛出 OutOfMemoryError 与以下错误信息:
java.lang.OutOfMemoryError: Java heap space |
非堆内存错误。 永久保存区 是HotSpot VM 实现中的一个非堆内存区,用于存放每个类结构和驻留的字符串 。当这个永久保存区充满时,这个应用程序将不能装载类或分配驻留的字符串,并且会抛出 OutOfMemoryError 与以下错误信息:
java.lang.OutOfMemoryError: PermGen space |
本机内存错误。 Java 本机接口 (JNI) 代码或应用程序的本机库以及JVM 实施从本机堆分配内存。当分派在本机堆发生故障时,抛出 OutOfMemoryError 。例如,以下错误信息表明交换空间不足,这可能由于操作系统的配置问题所致,或由于系统中的另一个进程消耗过多内存:
java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space? |
内存不足问题可能源于配置问题 -- 应用程序真地需要这么多内存 -- 或是由于应用程序的性能问题,需要您分析和优化该程序以减少内存使用。配置内存设置和分析应用程序以减少内存使用超出了这篇文章的范围,您可以参考HotSpot VM 内存管理白皮书 (PDF) 获取相关信息,或使用诸如NetBeans IDE Profiler 之类的分析工具。
JVM 负责自动管理内存,为应有程序索还尚未使用的内存。然而,如果应用程序持续引用它不再需要的对象,这个对象便不能被垃圾回收,它将继续占用空间,直到被删除为止。这种无意识的对象保留称为内存泄漏 。如果应用程序泄漏很多内存,它最终将用尽内存,并且抛出 OutOfMemoryError 。另外,垃圾回收也会常常发生,因为应用程序尝试释放空间,因而造成应用程序运行减慢。
OutOfMemoryError 的另一个可能原因是对finalizer 的过分使用。 java.lang.Object 类有一个被保护的方法叫finalize 。类可以忽略此 finalize 方法,以在该类的对象被垃圾回收之前处理系统资源或执行清理。可以被对象调用的 finalize 方法称为该对象的finalizer 。不保证finalizer 何时运行,也不保证它可以运行。有finalizer 的对象在其 finalizer 运行之前都不会被垃圾回收。因此,为最终完成而挂起的对象将保留内存,即使对象不再被该应用程序引用也是如此,这还可能导致与内存泄漏相似的问题。
当两个或多个线程都等待另一个线程释放锁时,就会发生死锁。Java 编程语言使用监视器来同步线程。每个对象都同一个监视器联系在一起,也被称为对象监视器。如果线程在对象上调用一个 synchronized 方法,则该对象被锁定。另一个线程若在此相同对象上调用 synchronized 方法,则会被阻拦,直至锁被释放为止。除内置的同步支持以外,在J2SE 5.0 中引入的java.util.concurrent.locks 包为锁定和等待条件提供了一个框架。死锁可能涉及对象监视器和 java.util.concurrent 锁。
一般情况下,死锁会导致这种应用程序或它的一部分变得无响应。例如,如果负责图形用户界面 (GUI) 更新的进程被锁死, GUI 应用程序则会冻结,并且不响应任何用户动作。
循环线程也可能造成应用程序挂起。当一个或多个线程在一个死循环中执行时,这个循环也许会消耗所有可利用的CPU 周期并造成这种应用程序的其余部分无响应。
同 步在多线程应用程序中大量使用,这是为了保证对一种共享资源的独占访问或为了在多个线程间协调和完成任务。例如,应用程序在数据结构上使用一台对象监视器 同步更新。当两个线程试图同时更新数据结构时,只有一个线程能获取对象监视器和继续更新数据结构。同时,另一个线程被阻拦,等待进入 synchronized 块,直到第一个线程完成它的更新并释放对象监视器。同步争用情况会影响应用程序的性能和可扩展性。
|
Java SE 6 中的监视和管理支持包括编程接口以及几个有用的诊断工具,用以检查各种各样的虚拟机 (VM) 资源。关于编程接口的信息,请阅读API 规范 。
JConsole 是允许您监测各种各样的VM 资源运行时使用情况的Java 监视和管理控制台。它使您注意到前面部分描述的应用程序执行过程中出现的症状。您可以使用JConsole 连接到在同一机器上本地运行的应用程序或在不同机器上远程运行的应用程序,监测以下信息:
内存使用和垃圾回收活动
线程状态、线程堆栈检索和锁
等待最终完成的对象数目
运行时信息,例如正常运行时间和进程消耗的CPU 时间
VM 信息,例如JVM 的输入参数和应用程序类路径
另外, Java SE 6 还包括其他命令行实用工具。jstat 命令打印各种各样的VM 统计数据,包括内存使用、垃圾回收时间、类加载和及时编译器统计。 jmap 命令允许您获得运行时的堆直方图和堆转储。jhat 命令允许您分析堆转储。jstack 命令允许您获得线程堆栈跟踪。这些诊断工具可以附加到任何应用程序,不需要以特别方式启动。
|
这个部分描述如何使用JDK 工具诊断共同的Java SE 问题。JDK 工具使您得到关于应用程序的更多诊断信息和帮助,以便确定应用程序是否按预想的正常运行。在某些情况下,诊断信息可能很充足,能够帮助您诊断问题和辨认它的起因。其他情况下,您可能需要使用分析工具或调试器来调试问题。
关于每个工具的详情,参见Java SE 6 工具文献。