让Java应用程序运行是一回事,但让他们跑得快就是另外一回事了。在面对对象的环境中,性能问题就像来势凶猛的野兽。但JVM的复杂性将性能调整的复杂程度增加了一个级别。这里Refcard涵盖了JVM internals、class loading(Java8中更新以映射最新的元空间)、垃圾回收、故障诊断、检测、并发性,等等。
性能测试工具Loadrunner(https://www.evget.com/product/3508)
介绍
Java是目前软件开发领域中使用最广泛的编程语言之一。Java应用程序在许多垂直领域(银行、电信、医疗保健等)中都有广泛使用。Refcard的目的是,帮助开发者通过专注于JVM内部,性能调整原则和最佳实践,以及利用现有监测和故障诊断工具,来提升应用程序在商业环境中的性能。
它能以不同的方式定义“optimal performance(最佳性能)”,但基本要素是:Java程序在业务响应时间要求内执行计算任务的能力,程序在高容量下执行业务功能的能力,并具有可靠性高和延迟低的特点。有时,数字本身变得模式化:对于一些大型网站,优秀的页面响应时间应该在500ms以下。在适当的时候,Refcard包括目标数字。但在大多数情况下,您需要根据业务需求和现有的性能基准自己决定这些。
JVM Internals
基础知识
代码编译和JIT
编译Java字节码显然没有直接从主机执行本机代码那么快。为了提高性能,Hotspot JVM找出最繁忙的字节码区域,然后将其编译成更高效地原生、机器代码(自适应优化)。然后这种本地代码就会存储在非堆内存中的代码缓存中。
注意:多数的JVM是通过禁用JIT编译器实现的(Djava.compiler=NONE)。您只需要考虑禁用的关键性优化,比如JVM崩溃。
下图说明了Java源代码,即时编译流程和生命周期。
内存空间
HotSpot Java Virtual Machine是由以下的存储空间组成。
类加载
Java的另一个重要特点是,在JVM启动之后,它能够加载编译的Java类(字节码)。根据程序的大小,在刚刚重启之后,程序在类加载过程中性能会显著降低。这种现象是因为内部JIT编译器在重启之后需要重新开始优化。
自JDK 1.7版本之后,有一些改进值得大家重视。例如默认的JDK class loader具有更好的装在类并发能力。
热点
故障诊断和监视
垃圾回收
Java垃圾回收流程对于程序性能是至关重要的。为了提供有效的垃圾回收,Heap(堆)本质上是划分在子区域中。
堆区域
GC Collectors
选择正确的collector或GC policy可以将程序的性能、可扩展性和可靠性优化到最佳状态。许多应用程序对于响应时间延迟都很敏感,因此大多需要使用并发的回收器,例如HotSpot CMS或IBM GC policy balanced。
我们强烈建议您通过适当的性能和负载测试确定最合适的GC策略。应该在生产环境中执行全面监控策略,以跟踪整体的JVM性能,并确定在之后需要改进的领域。
Garbage First (G1) Collector
HotSpot G1 collector是专为是专为满足用户定义的垃圾回收(GC)高概率暂停时间设计的,同时实现高吞吐量。
最新的HotSpot collector将heap基本划分到一组大小相等的堆区域,虚拟内存的每个区域连续范围。它将回收压缩的活动集中在heap区域,那里充满了可回收的对象(garbage first)。换句话说就是,这个区域有最低限度的“live”对象。
Oracle建议在以下例子和情况下使用G1 collector,尤其是对于目前正在使用CMS或parallel collectors的:
-
专为large heaps(>= 6 GB),并限制GC延迟(暂停时间<= 0.5秒)的应用程序设计。
-
超过50%的Java heap被实时数据占用(对象不能被GC回收)。
-
对象分析率和促进作用显著变化。
-
不期望过长的垃圾回收或压缩停顿(超过0.5至1秒)。
Java Heap尺寸
你一定要知道没有GC策略可以挽救Java Heap尺寸不足的现象。这些演习涉及到为不同的存储空间(包括新旧不同的版本)配置最大和最小的容量,包括元数据和本地内存容量。这里有一些建议准则:
-
在32-bit或64-bit JVM之间进行明智的选择。如果程序运行需要超过2GB内存,并且JVM暂停时间在可接受范围内,可以考虑使用64-bit JVM。
-
永远将应用程序放在第一考虑。确保将其配置好,并根据程序的内存占用量调整heap尺寸。建议通过性能和负载测试来衡量实时数据占有量。
-
larger heap并不总是表现得更好、更快,因此不需要过度调整Java heap。并行中的JVM性能调优,找准机会减少或“spread”程序的内存占有量,以保证JVM的平均响应时间<1%。
-
对于32-bit JVM,为了从元数据和本地heap中留出一些内存,考虑2GB的最大heap尺寸。
-
对于64-bit JVM,我们要想办法在垂直和水平层面进行扩展,而不是试图将Java heap尺寸增加到15GB以上。这种做法往往提供更好的吞吐量,更好地利用硬件,提高应用程序的故障切换功能。
-
不许重复开发:充分利用开源以及商业故障排除的优势和监控工具,使这些变成可能。APM(应用性能管理)产品在过去十年里发展迅猛。
JDK 1.8 Metaspace指南
Hot Spots
故障诊断和监视
Java并发性
Java并发性可以定义为程序同时执行多个任务的能力。对于大型的Java EE系统,这意味着执行多个用户的业务功能的同时,实现最佳的吞吐量和性能的能力。
无论是硬件能力还是JVM稳定状况,Java并发性问题可能引起程序的瘫痪,严重影响程序的整体性能和可用性。
Thread Lock Contention
当您评估Java应用程序的并发线程的稳定状况时,你会经常遇到Thread lock contention的问题,这是目前最常见的Java并发问题。
例如:Thread lock contention会触发non-stop,它会尝试将一个缺少Java类(ClassNotFoundException的)加载到默认的JDK 1.7 ClassLoader。
如果您在成熟的技术环境中遇见像Thread Dump analysis这样的问题,我们强烈建议您积极面对它。这个问题的根源通常不同于之前的Java synchronization to legitimate IO blocking或者其他的non-thread safe calls。Lock contention问题往往是另一个问题的“症状”。
Java-level Deadlocks
真正的Java-level deadlocks是不太常见的,它同样可以极大程度地影响应用程序的性能和稳定性。当遇到两个或多个线程永远阻塞的时候,就会触发这样的问题。这种情况不同于其他常见的那种“day-to-day”线程问题,例如 lock contention、threads waiting on blocking IO calls等等。真正的lock-ordering deadlock问题可以被看做如下:
Oracle HotSpot 和IBM JVM为大多数的deadlock detectors情况提供了解决方案,帮助您快速找出造成这种状况的罪魁祸首的线程。遇到类似lock contention troubleshooting的问题,建议从诸如线程转储分析为出发点来解决该问题。
一旦找到造成问题的代码根源,解决方案涉及lock-ordering条件寻址和来自JDK其他可用的并发编程技术,如java.util.concurrent.locks.ReentrantLock,提供了诸如tryLock()的方法。这种方法给予开发人员更大的灵活性,也为防止deadlock和thread lock “starvation”提供了更多方式。
Clock Time和CPU Burn
在进行JVM调优的同时,也有必要检查应用程序的行为,更确切地说是最高clock time和CPU burn的贡献者。
当Java垃圾回收和线程并发不再是压力点,深入到你的应用程序代码的执行模式,并专注于顶级响应时间贡献者(也叫作clock time)是很重要的。检查应用程序代码的消CPU耗和Java 线程(CPU burn)也同样至关重要。CPU使用率较高(>75%)是不正常的(良好的物理资源的利用率)。因为这往往意味着效率低下和容量问题。对于大型的Java EE企业应用,保持安全的CPU缓冲区是必要的,以应对突发的负载冲击情况。
摒弃那些传统的跟踪方法,如在代码中加入响应时间“日志”。Java剖析工具和APM解决方案恰恰可以帮助您分析这类型的问题。这种方式更加高效、可靠。对于Java生产环境缺乏一个强大的APM解决方案。您仍然可以依赖诸如Java VisualVM的工具,通过多个快照进行thread dump分析,并使用OS CPU分析每个线程。
最后的建议是,不要妄图同时解决所有的问题。列出排在最前面的5个clock time和CPU burn问题,然后寻找解决方案。
Application预算
其他关于Java应用程序性能的重要方面是稳定性和可靠性。在有着99.9%典型可用目标的SLA umbrella下,稳定和可靠对于程序的操作尤为重要。这些系统应该具有高容错级别,并对应用和资源进行严格的预算,以防止发生多米诺效应。用这种方法可以防止一些这样的情况,例如,一个业务流程使用所有可用的物理,中间件或JVM资源。
Hot Spots
超时管理
Java application与外部系统之间缺乏合理的超时时间,由于中间件和JVM线程消耗(blocking IO calls),可能导致严重的性能下降和中断。合理的超时时间可以避免在遇到外部服务提供商速度缓慢的时候,Java线程等待太久。
工具