Java性能优化全攻略

让Java应用程序运行是一回事,但让他们跑得快就是另外一回事了。在面对对象的环境中,性能问题就像来势凶猛的野兽。但JVM的复杂性将性能调整的复杂程度增加了一个级别。这里Refcard涵盖了JVM internals、class loading(Java8中更新以映射最新的元空间)、垃圾回收、故障诊断、检测、并发性,等等。

性能测试工具Loadrunner

点击下载

介绍

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 Heap Java程序类实例和数组的主存储器。
Permanent Generation(JDK 1.7及以下版本)Metaspace (JDK 1.8及以上版本) Java类元数据的主存储器。注意:从Java 8开始,PermGen空间就由元空间和使用本地存储器替换了,类似于IBM J9 JVM。
Native Heap(C-Heap) 本地内存存储线程、栈、包括对象的代码缓存,如MMAP文件和第三方本机库。

类加载

Java的另一个重要特点是,在JVM启动之后,它能够加载编译的Java类(字节码)。根据程序的大小,在刚刚重启之后,程序在类加载过程中性能会显著降低。这种现象是因为内部JIT编译器在重启之后需要重新开始优化。

自JDK 1.7版本之后,有一些改进值得大家重视。例如默认的JDK class loader具有更好的装在类并发能力。

热点

关注的区域 建议
JVM重启后的性能下降 避免部署过量的Java类到一个单一的应用程序类加载器(例如:非常大的WAR文件)
运行时发现过多的类加载争夺(thread lock, JAR file searches…) ,降低了整体性能。 分析您的应用程序并识别代码模块进行动态类加载操作过于频繁。积极寻找非一站式类加载错误,如ClassNotFoundException和NoClassDefFoundError。再访Java映射API和适用情况下优化的过度使用。
java.lang.OutOfMemoryError: PermGen space (JDK 1.7及以下版本)java.lang.OutOfMemoryError:元空间(JDK 1.8及以上版本) 再访JVM Permanent Generation、Metaspace (MaxMetaSpaceSize)和本地内存容量在适用情况下的尺寸。分析应用程序类加载器和识别元数据的内存泄漏的源头。

故障诊断和监视

目标 建议
跟踪那些加载到不同的类加载器的Java类。 配置程序选择使用的Java profiler,例如JProfiler或Java VisualVM。将重点放在类加载器的操作和内存占用上。可以通过–verbose:class. for the IBM JVM,生成多个Java核心快照跟踪活动的类加载器和加载类。
调查类元数据的内存泄露的可以来源。 配置程序和定义可能的culprit(s)。生成并分析JVmheap dump快照,专注于类加载器和java.lang.Class中的实例。

确保适当的Permanent Generation / Metaspace和本地内存大小。 密切监视你的PermGen、元空间和本机内存利用率,并调整到适合的最大容量。分析程序类加载器的大小,并寻找机会适当地减少元数据足迹。

垃圾回收

Java垃圾回收流程对于程序性能是至关重要的。为了提供有效的垃圾回收,Heap(堆)本质上是划分在子区域中。

堆区域

区域 描述
最新一代-Young Generation (nursery space) 新的或短暂的对象分配保留堆的一部分。垃圾被一个fast but stop-the-world YG的收集器进行回收。

在young space中呆了足够久的对象就会提升到old space。

注意:YG space的尺寸和GC频率过高将会显著影响程序的响应时间,从而导致JVM的暂停时间增加。

老一代-Old Generation  (tenured space) heap的一部分留给了long-lived对象。垃圾通常通过平行或并发(多数时候)进行收集,诸如CMS或gencon (IBM JVM)。

性能提示:根据应用程序的需求选择并测试最佳的GC策略是非常重要的。例如,当切换到并发GC收集(如CMS或G1)可以显著提高应用程序的平均响应时间(减少延迟)。

GC Collectors

选择正确的collector或GC policy可以将程序的性能、可扩展性和可靠性优化到最佳状态。许多应用程序对于响应时间延迟都很敏感,因此大多需要使用并发的回收器,例如HotSpot CMS或IBM GC policy balanced。

我们强烈建议您通过适当的性能和负载测试确定最合适的GC策略。应该在生产环境中执行全面监控策略,以跟踪整体的JVM性能,并确定在之后需要改进的领域。

GC 论据 描述
串行回收器 -XX:+UseSerialGC (Oracle HotSpot) 无论新旧回收器都使用单独CPU,像是一种stop the world的时尚。

并行回收器(吞吐量回收器) -XX:+UseParallelGC-XX:+UseParallelOldGC
(Oracle Hotspot)

-Xgcpolicy:optthruput
(IBmJ9, single space, stop-the-world)

旨在利用CPU的内核优势。无论新旧回收器都使用多个Gcthreads(via –XX:ParallelGCThreads=n),从而更好地利用来自主机的可用的CPU内核来完成。注意:虽然回收时间可以显著减少,但是有着大尺寸堆的程序面临着large、stop-the-world、old回收,并且响应时间也受到影响。

确保适当的Permanent Generation / Metaspace和本地内存大小。 密切监视你的PermGen、元空间和本机内存利用率,并调整到适合的最大容量。分析程序类加载器的大小,并寻找机会适当地减少元数据足迹。 旨在最大限度地减少旧一代stop-the-world回收器对程序响应时间的影响。大多数使用CMS collector的老一代回收器与所述应用程序的执行同时进行。

注意:YoungGen collections仍然有stop-the-world事件,因此需要适当的微调,以减少总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指南

目标 建议
内存大小GC调整

监控和故障排除

默认情况下,元空间内存空间是无界的,并使用可用于动态扩展的process或OS native memory。内存空间分成快并通过mmap被JVM进行存储。我们建议保持默认设置,以动态调整模式为出发点,将简化的尺寸与密切监测的应用程序元数据占有量相结合,从而进行更好的容量规划。新增一个JVM选项(-XX:MaxMetaspaceSize=<NNN>),可以让您限制分配给class metadata的本地内存。当面临物理资源(RAM)紧张或类似于内存泄露的情况时,建议将它作为一个保障机制。

对那种具有larger class metadata footprint或dynamic classloading的Java应用程序,我们建议通过新的JVM选项调整初始元空间大小 :-XX:MetaspaceSize=<NNN>,例如:1GB。这种调整方法将有助于避免包括class metadata在内的早期垃圾回收,尤其是在Java应用程序的 “warm-up”期。

Hot Spots

故障诊断和监视

目标 建议
测量和监视应用程序YoungGen和OldGen内存占用,包括GC活动。为您的应用程序决定正确的GC策略和Java堆大小。

调整应用程序的内存占用量,如live对象。

分析、监控您所使用的Java分析工具,如JProfiler、Java VisualVM或其他商业APM产品。允许通过–verbose:gc记录JVM GC活动。您也可以使用类似GCMV(GC Memory Visualizer)的工具查看JVM的暂停时间和内存分配率。

性能提示:过多的内存分配率可能意味着需要进行垂直和横向扩展,或从多个JVM进程中分离出实时数据。

为了long-lived对象或long-term实时数据考虑,可以生成并分析JVM heap dump快照。Heap dump分析对于程序内存占用(retention)的优化是非常有帮助的。

性能提示:由于从32位到64位,Java应用程序对heap 的需求会比原来高1.5倍。所以,在Java 1.7及以下的版本(这是默认的)中使用 -XX:+UseCompressedOops是非常重要的。这样的参数调整大大减轻了64位JVM的性能压力。

调查OutOfMemoryError 问题,寻找OldGen内存泄露的根源。 使用类似Java VisualVM、Plumbr的工具(Java内存泄漏检测器),分析可能存在的内容泄露。性能提示:要着重分析最大的Java对象上。要意识到降低内存占有量就意味着提升性能,并降低GC活动。

使用类似 Memory Analyzer的工具生成并分析JVM heap dump快照。

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线程等待太久。

工具

目标 建议工具
自动、实时地性能监控、调节、预警、趋势分析、容量管理,等等 Enterprise APM solutions(企业级APM解决方案)注意:APM解决方案提供了工具,这些现成的功能让您实现以下大部分的Java性能目标。
性能和负载测试 商业性能测试解决方案Apache JMeter

http://jmeter.apache.org/

JVM垃圾回收评估,内存分配率和故障排除 Oracle Java VisualVMhttp://docs.oracle.com/javase/8/docs/technotes/guides/visualvm/intro.html

http://java.dzone.com/articles/profile-your-applications-java

Oracle Java Mission Control

http://www.oracle.com/technetwork/java/javaseproducts/mission-control/java-mission-control-wp-2008279.pdf

http://www.oracle.com/technetwork/java/javase/jmc53-release-notes-2157171.html

IBM Monitoring and Diagnostic Tools for Java (via IBM Support Assistant tool)

http://www-01.ibm.com/software/support/isa/

JVM verbose:gc logs

JVM argument : -verbose:gc

http://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.html

IBM GCMV

https://www.ibm.com/developerworks/java/jdk/tools/gcmv/

JVM堆和类的元数据的内存泄漏分析 Oracle Java VisualVM and Oracle Java Mission ControlIBM Monitoring and Diagnostic Tools for Java

Memory Analyzer (heap dump analysis, hprof and phd formats)

https://www.eclipse.org/mat/

https://www.ibm.com/developerworks/java/jdk/tools/memoryanalyzer/

JVM内存分析和堆容量评估 Oracle Java VisualVM and Java Mission ControlIBM Monitoring and Diagnostic Tools for Java

Java profilers (JProfiler, YourKit)

http://en.wikipedia.org/wiki/JProfiler

http://www.yourkit.com/

Memory Analyzer (heap dump and application memory footprint analysis)

JVM和中间件并发故障,如thread lock contention和deadlocks Oracle Java VisualVM and Oracle Java Mission Control (threads monitoring, thread dump snapshots)jstack, native OS signal such as kill -3 (thread dump snapshots)

http://www.oracle.com/technetwork/java/javase/tooldescr-136044.html#gblfh

IBM Monitoring and Diagnostic Tools for Java

注意:强烈推荐大家关注如何执行一个JVM线程转储分析的相关知识。

Java应用程序clock time分析和评测 Oracle Java VisualVM and Oracle Java Mission Control (build-in profiler, sampler and recorder)Java profilers (JProfiler, YourKit)
Java应用程序和线程CPU burn分析 Oracle Java VisualVM and Oracle Java Mission Control (CPU profiler)Java profilers (JProfiler, YourKit)

注意:必要的时候,您还可以依赖JVM线程转储和OS CPU每个线程分析。

Java IO和remoting contention分析,包括超时管理评估和调整 Oracle Java VisualVM and Oracle Java Mission Control(threads monitoring, thread dump snapshots)

jstack, native OS signal such as kill -3 (thread dump snapshots)

IBM Monitoring and Diagnostic Tools for Java

注意:强烈推荐大家关注如何执行一个JVM线程转储分析的相关知识。

中间件,Java EE容器调整,如线程、JDBC数据源,等等 Oracle Java VisualVM and Oracle Java Mission Control (extra focus on exposed Java EE container runtime MBeans)Java EE container administration and management console


相关文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部