介绍
Java是当今软件开发世界中使用最广泛的编程语言之一。 Java应用程序在许多垂直领域(银行,电信,医疗保健等)中使用,在某些情况下,每个垂直方向都会提供一组特定的设计优化。许多与性能相关的最佳实践在各种应用中都是常见的。本指南目的是帮助开发人员通过关注JVM内部组件,性能调优原则和最佳实践以及如何利用可用的监控和故障排除工具,尽可能多地提供业务环境中的应用程序性能。
可以以不同的方式定义“最佳性能”,但基本要素是:Java程序在业务响应时间要求内执行计算任务的能力,以及应用程序实现其业务功能的高容量,及时性,高可靠性和低延迟。有时候,这些数字本身就变得模式化了:对于一些主要的网站来说,每个用户功能的页面响应时间最大为500ms被认为是最佳的。但在大多数情况下,您将需要根据业务需求和现有的性能基准自行决定这些数据。
JVM内部
基础
代码编译和JIT
Java字节码解释显然没有直接从主机执行本地代码快。为了提高性能,Hotspot JVM寻找最繁忙的字节代码区域,并将其编译为本机更高效的机器代码(自适应优化)。然后将这样的本地代码存储在非堆存储器中的代码高速缓存中。
注意:大多数JVM实现提供了禁用JIT编译器(Djava.compiler = NONE)的方法。您应该只考虑在出现意外的JIT问题(如JVM崩溃)时禁用这样的关键优化。
下图说明了Java源代码,即时编译过程和生命周期。
内存空间
HotSpot Java虚拟机由以下内存空间组成。
内存空间 | 描述 |
---|---|
Java堆 | Java程序类实例和数组的主存储。 |
永久代(JDK 1.7 +)元数据空间 (JDK 1.8+) | Java类元数据的主存储。注意:从Java 8开始,PermGen空间由metaspace替换,并使用本机内存,类似于IBM J9 JVM。 |
本地堆(C-堆) | 本地内存存储为线程,堆栈,代码缓存,包括对象,如MMAP文件和第三方本机库。 |
类加载
Java的另一个重要特性就是能够在启动JVM之后加载编译的Java类(字节码)。根据您的应用程序的大小,类加载过程可能是侵入性的,并且在重新启动后会在高负载下显著的降低应用程序的性能。
重要的是要注意JDK 1.7之后引入了一些改进,例如默认JDK类加载器能够更好地同时加载类的能力。
HotSpot
关注的区域 | 推荐 |
---|---|
JVM重启后的性能下降 | 避免向单个应用程序类加载器部署过多的Java类(例如:非常大的WAR文件) |
在运行时观察到过多的类加载争用(线程锁定,Java文件搜索...),降低了整体性能 | 分析您的应用程序,识别执行频繁动态类加载操作的代码模块。积极地查看不断的类加载错误,例如ClassNotFoundException和NoClassDefFoundError。重新审视Java Reflection API的任何使用情况,并在适用的情况下进行优化。 |
java.lang.OutOfMemoryError: PermGen space (JDK 1.7 +)java.lang.OutOfMemoryError:Metaspace (JDK 1.8 +)观察到的本机内存泄漏. | 如果适用,请重新查看JVM永久代,Metaspace(MaxMetaSpaceSize)和/或本地内存容量的大小。分析您的应用程序类加载器并识别元数据内存泄漏的任何来源。 |
故障排除和监控
目的 | 推荐 |
---|---|
跟踪加载到不同类加载器的java类 | 使用您选择的Java分析器(如JProfiler或Java VisualVM)来分析你的应用程序。专注于类加载器操作和内存占用。通过-verbose:class启用类加载细节。对于IBM JVM,生成多个Java核心快照并跟踪活动类加载器和加载的类。 |
调查类元数据内存泄漏的可疑来源。 | 分析您的应用程序并识别可能的罪魁祸首。生成和分析JVmheap转储快照,主要关注于ClassLoader和java.lang.class实例。 |
确保适当的永久代/元空间和本机内存大小 | 密切监控PermGen,元空间和本地内存利用率,并在适用的情况下调整最大容量。 分析您的应用程序类加载程序大小,并在可能的情况下确定减少应用程序元数据空间的机会。 |
垃圾收集
Java垃圾收集过程是最佳应用程序性能的最重要的因素之一。为了提供有效的垃圾收集,堆本质上分为一些子区域。
堆区域
区域 | 描述 |
---|---|
年轻一代 | 一部分堆被保留用于分配新的或短命的对象。 垃圾由一个快速但要停顿的YG收集器收集。 在年轻空间中活得足够长的对象被提升到年老的空间。 注意:重要的是要意识到,由于JVM暂停时间的增加,YG空间的过大尺寸和/或GC频率可能会显着影响应用程序的响应时间。 |
老年代 | 为长寿命对象保留的一部分堆。 垃圾通常由并行或大部分并发的收集器(如CMS或gencon(IBM JVM))收集。 性能提示:根据您的应用需求选择和测试最佳GC策略是非常重要的。例如,切换到“主要”并发GC收集器(如CMS或G1)可能会显著提高应用程序平均响应时间(降低延迟)。 |
GC收集器
为您的应用选择合适的收集器或GC策略是最佳应用性能,可扩展性和可靠性的决定性因素。许多应用程序对响应时间延迟非常敏感,需要大部分并发收集器(如HotSpot CMS或IBM GC策略平衡)。
作为一般的最佳实践,强烈建议您通过适当的性能和负载测试来确定最合适的GC策略。还应在您的生产环境中实施一个全面的监控策略,以便跟踪整体JVM性能并确定未来的改进领域。
GC | 参数 | 描述 |
---|---|---|
Serial Collector | -XX:+UseSerialGC(Oracle HotSpot) | 年轻代和老年代都是串行收集的,使用单个CPU并以停顿的方式。 注意:此策略只能由对JVM暂停不敏感的客户端应用程序使用。 |
Parallel Collector(吞吐量collector) | -XX:+UseParallelGC-XX:+UseParallelOldGC(Oracle Hotspot)-Xgcpolicy:optthruput(IBmJ9,单一空间,jvm停顿) | 旨在利用可用的CPU内核。 Young和Old收集都使用多个GC线程(通过-XX:ParallelGCThreads = n)完成,从而更好地利用了来自主机的可用CPU核心。 注意:尽管收集时间可以显著的减少,但是堆大的应用程序仍然会暴露在大的停顿的年老代收集中,并影响响应时间。 |
Mostly concurrent collectors (低延时collectors) | Concurrent Mark-Sweep-XX:+UseConcMarkSweepGCGarbage First (G1), JDK 1.7u4+ -XX:+UseG1GC(Oracle HotSpot)-Xgcpolicy:balanced(IBM J9 1.7+,用于Java堆的基于区域的布局,专为Java堆空间设计,大于4 GB) | 旨在最大限度地减少因对老年代进行收集带来的停顿对应用程序响应时间的影响。 CMS收集器的对年老代的收集与应用程序的执行是并发完成的。 注意:YoungGen集合仍然是世界上的世界事件,因此需要进行适当的微调,以减少整体JVM暂停时间。 |
G1收集器
HotSpot G1收集器被设计为以高可能性满足用户定义的垃圾收集(GC)暂停时间目标,同时实现高吞吐量。
这个最新的HotSpot收集器基本上将堆分成一组相等大小的堆区,每个区都是连续的虚拟内存范围。它将其收集和压缩活动集中在可能充满可回收对象(垃圾优先)的堆的区域,或者换句话说,在最少量的“活动”对象的区域上。
Oracle建议使用G1收集器用在以下用例或候选者,特别是对于目前使用CMS或并行收集器的现有应用程序:
专为需要大量堆积(> = 6 GB)的GC等待时间有限(暂停时间<= 0.5秒)的应用而设计。
超过50%的Java堆被活跃数据占用(GC无法回收的对象)。
对象分配率或提升率差异很大。
不期望长时间的垃圾收集或压缩合并停顿(超过0.5至1秒)。
Java堆调整
重要的是要意识到没有GC策略可以将您的应用程序从不足够的Java堆大小调整。这样的练习包括配置各种存储空间的最小容量和最大容量,如Young和Old几代,包括元数据和本地存储器容量。作为起点,以下是一些推荐的指导方针:
在32位或64位JVM之间明智地选择。如果您的应用程序由于大量活跃数据占用而需要超过2 GB的可运行空间并在可接受的JVM暂停时间范围内,请考虑使用64位JVM。
请记住,应用程序是王者:请确保您对其进行配置,并根据我们的应用程序内存空间来调整堆大小。始终建议通过性能和负载测试来测量活跃数据占用。
一个较大的堆并不总是更好或更快:不要超调Java堆。与JVM调优并行,找出减少或“扩展”应用程序内存占用空间的机会,以保持平均JVM暂停时间<1%。 对于32位JVM,请考虑2 GB的最大堆大小,以便将一些内存从地址空间留给元数据和本机堆。
对于64位JVM,可以探索垂直和水平扩展策略,而不是简单地尝试扩展超过15 GB的Java堆大小。这种方法经常提供更好的吞吐量,更好地利用硬件,并增加应用程序故障切换功能。
不要重新发明轮子:利用多种开源和商业的故障排除和监控工具。 APM(应用程序性能管理)产品在过去十年中有了显着的发展。
JDK 1.8 Metaspace指南
目标 | 推荐 |
---|---|
内存大小GC 优化监控& 问题排查 | 默认情况下,Metaspace内存空间是无限制的,并且将使用可用于动态扩展的可用进程和/或OS本机内存。内存空间分为块,并由JVM通过mmap分配。我们建议保持默认的动态调整大小模式作为起始点,从而实现更简单的大小,同时随着时间的推移密切监控应用程序元数据占用空间,从而实现最佳的容量规划。 可以使用新的JVM选项(-XX:MaxMetaspaceSize = <NNN>),允许您限制提交的本地内存量 用于类元数据。建议在面临物理资源(RAM)限制和其他场景(如存在内存泄漏)的情况下将其用作保护机制。 对于具有较大类别元数据占用的Java应用 和/或动态类加载,我们建议调整 通过新的JVM选项的初始Metaspace大小:-XX:MetaspaceSize = <NNN> ex:1 GB。这种调优方法将有助于避免为类元数据引发的早期垃圾收集,特别是在Java应用程序的“预热”期间。 类似于UseCompressedOops for Java对象引用,也可以使用UseCompressedClassesPointers(它被启用 默认情况下)以最小化内存占用。注意:虽然这种调整可以帮助包含“承诺”的内存占用 类指针,使用此选项时,默认的metaspace内存预留为1 GB。您可能会观察到Java进程与JDK 1.7之间的虚拟内存占用空间。为了监视Metaspace使用情况,Oracle已更新了Java VisualVM工具和GC日志。我们建议分析详细信息:gc数据,了解Metaspace内存使用情况,GC行为和动态调整大小频率的详细视图。 性能提示:新的Metaspace实现本身不会解决现有的类元数据内存泄漏。建议在怀疑此类问题时分析任何意外的类元数据可达到的引用。 性能提示:与Metaspace使用默认或无界模式有一些风险。如果不选中,Metaspace内存泄漏可能会耗尽基础设施的物理RAM,并可能导致磁盘分页和/或操作系统挂起。建议密切监测Metaspace的使用情况,并在需要时采取JVM的预防(重启)操作,作为短期操作。长期解决方案通常涉及解决内存泄漏和调整应用程序类元数据占用情况Java Profier工具和JVM堆转储分析的使用将极大地帮助您实现这些目标。 |
Hot Spots
故障排除和监控
目标 | 推荐 |
---|---|
测量和监控您的应用程序YoungGen和OldGen内存占用,包括GC活动。 为您的应用程序确定正确的GC策略和Java堆大小。 微调应用程序内存占用,如活动对象。 | 使用您选择的Java分析器(如JProfiler,Java VisualVM或其他商业APM产品)分析和监视应用程序 通过-verbose:gc启用JVM GC活动日志记录。您也可以使用GCMV(GC Memory Visualizer)等工具来评估JVM暂停时间和内存分配率。 性能提示:过多的内存分配率可能表示需要执行垂直和/或水平扩展,或者将多个JVM进程的实时数据解耦。 对于生命周期长的对象或长期的活跃数据,请考虑生成和分析JVM堆转储快照。堆优化分析在优化应用程序内存占用(保留)方面也非常有用。 性能提示:由于从32位到64位的机器将现有Java应用程序的堆需求提高了1.5倍(较大的普通对象指针),所以在Java1.7之前版本中使用-XX:+ UseCompressedOops是非常重要的(现在是默认值)。这个调优参数大大减轻了与64位JVM相关联的性能损失。 |
调查OutOfMemoryError问题和可疑的OldGen内存泄漏源。
使用Java VisualVM或Plumbr(Java内存泄漏检测器)等工具为您的应用程序分析可能的内存泄漏。 性能提示:将您的分析集中在最大的Java对象累积点上。重要的是要意识到,由于GC活动降低,减少应用程序内存占用将会改善性能。使用诸如内存分析器之类的工具生成和分析JVM堆转储快照。 |
---|
Java并发
Java并发性可以被定义为并行执行程序的几个任务的能力。对于大型Java EE系统,这意味着能够同时执行多个用户业务功能,同时实现最佳吞吐量和性能。
无论您的硬件容量还是JVM的健康状况,Java并发问题都可能使任何应用程序瘫痪,并严重影响整体应用程序性能和可用性。
线程锁争用
在评估Java应用程序的并发线程运行状况时,线程锁争用是迄今为止最常见的Java并发问题。这个问题将通过存在1 ... n个BLOCKED线程(线程等待链)等待获取特定对象监视器上的锁来显示。根据问题的严重性,锁争用可能会严重影响应用程序响应时间和服务可用性。
示例:通过不间断的尝试将未找到的Java类(ClassNotFoundException)加载到默认的JDK 1.7 ClassLoader来触发线程锁争用。
强烈建议您通过经过验证的技术(如线程转储分析)在您的环境中积极评估此类问题的存在。此问题的典型根本原因可能与普通的旧的Java同步到合法的IO阻塞或其他非线程安全调用的滥用有关。锁争用问题通常是另一个问题的“症状”。
Java级死锁
真正的Java级死锁虽然不常见,但也可能会极大地影响应用程序的性能和稳定性。当两个或多个线程永远被阻塞,等待彼此时,会触发此问题。这种情况与其他更常见的“日常”线程问题非常不同,例如锁争用,线程等待阻塞IO调用等。真正的锁定排序死锁可以按照下列方式可视化:
Oracle HotSpot和IBM JVM为大多数场景实现提供了死锁检测器,从而可以快速识别出这种情况下所涉及的凶手线程。与锁争用故障排除类似,建议使用线程转储分析等技术作为起点。
一旦确定了问题代码,解决方案涉及解决锁定排序条件和/或使用JDK中的其他可用的并发编程技术,例如java.util.concurrent.locks.ReentrantLock,它提供了诸如tryLock()的方法。这种方法为Java开发人员提供了更多的灵活性和方法来防止死锁或线程锁定“饥饿”。
时钟时间和CPU刻录
与JVM调优并行,同样重要的是您可以查看应用程序行为,更确切地说,是最高时钟时间和CPU刻录贡献者。
当Java垃圾收集和线程并发不再是一个压力点时,重要的是深入到您的应用程序代码执行模式中,并将重点放在最早的响应时间贡献者(称为时钟时间)上。查看应用程序代码和Java线程(CPU刻录)的CPU消耗也至关重要。高CPU利用率(> 75%)不应该被认为是“正常”(良好的物理资源利用率)。通常是执行效率低和/或容量问题的症状。对于大型Java EE企业应用程序,必须保持安全的CPU缓冲区才能处理意外的负载冲击。
远离传统的跟踪方法,例如在代码中添加响应时间“日志记录”。 Java Profiler工具和APM解决方案正是为了帮助您进行这种分析,并以更有效和可靠的方式进行。对于缺乏强大的APM解决方案的Java生产环境,您仍然可以依赖诸如Java VisualVM,线程转储分析(通过多个快照)和每个线程分析的OS CPU等工具。
最后,不要同时解决所有问题。首先建立一个您的前五大时钟列表和CPU消耗者,并探索解决方案。
应用预算
Java应用程序性能的其他重要方面是稳定性和可靠性。这对于在具有99.9%的典型可用性目标的SLA伞下运行的应用程序尤为重要。这些系统需要高容错级别,严格的应用和资源预算,以防止多米诺骨牌效应情景。此方法可防止一个业务流程使用所有可用的物理,中间件或JVM资源。
Hot Spots
超时管理
由于中间件和JVM线程耗尽(阻塞IO调用),Java应用程序和外部系统之间缺少适当的HTTP / HTTPS / TCP IP超时可能导致严重的性能下降和中断。适当的超时执行将阻止Java线程在外部服务提供商发生显著放慢的情况下等待太久。
工具
目标 | 推荐的工具 |
---|---|
主动和实时的性能监控,调优,警报,趋势,容量管理等 | 企业APM解决方案 注意:APM解决方案提供了一些工具,您可以即时获得大部分以下Java性能目标 |
性能和负载测试 | 商业的性能测试方案Apache JMeterhttp://jmeter.apache.org/ |
JVM垃圾收集评估,内存分配率和故障排除 | Oracle Java VisualVMhttp://docs.oracle.com/javase/8/docs/technotes/guides/visualvm/intro.htmlhttp://java.dzone.com/articles/profile-your-applications-javaOracle Java 任务控制http://www.oracle.com/technetwork/java/javaseproducts/mission-control/java-mission-control-wp-2008279.pdfhttp://www.oracle.com/technetwork/java/javase/jmc53-release-notes-2157171.htmlIBM的Java监控和诊断工具http://www-01.ibm.com/software/support/isa/JVM verbose:gc logsJVM argument : -verbose:gchttp://docs.oracle.com/javase/8/docs/technotes/tools/windows/java.htmlIBM GCMVhttps://www.ibm.com/developerworks/java/jdk/tools/gcmv/ |
JVM堆和类元数据内存泄漏分析 | Oracle Java VisualVM and Oracle Java 任务控制IBM的Java监控和诊断工具 (堆转储分析, hprof and phd 格式)https://www.eclipse.org/mat/https://www.ibm.com/developerworks/java/jdk/tools/memoryanalyzer/Plumbr (Java内存泄漏检测器)https://plumbr.eu/jmap (heap直方图和堆转储的生成)http://www.oracle.com/technetwork/java/javase/tooldescr-136044.html#gbdidJVM verbose:class logsJVM argument :-verbose:gc & -verbose:classIBM Java core 文件分析 (via kill -3 <PID>) |
JVM内存分析和堆容量调整 | Oracle Java VisualVM and Java Mission ControlIBM Java分析器的监控和诊断工具 (JProfiler, YourKit)http://en.wikipedia.org/wiki/JProfilerhttp://www.yourkit.com/Memory Analyzer (堆转储和应用内存占用分析) |
JVM和中间件并发故障排除,如线程锁争用和死锁 | Oracle Java VisualVM and Oracle Java Mission Control (线程监控, 线程转储快照)jstack, 本地 OS 信号像 kill -3 (线程转储快照)http://www.oracle.com/technetwork/java/javase/tooldescr-136044.html#gblfhIBM的Java监控和诊断工具注意:强烈建议您正确了解如何执行JVM线程转储分析 |
Java应用程序时钟分析和分析 | Oracle Java VisualVM and Oracle Java Mission Control (内建的分析器,样本器和记录器)Java profilers (JProfiler, YourKit) |
Java应用程序和线程CPU刻录分析 | Oracle Java VisualVM and Oracle Java Mission Control (CPU profiler)Java profilers (JProfiler, YourKit)注意:如果需要,您还可以退回JVM线程转储和每个线程分析的OS CPU |
Java IO和远程处理竞争分析,包括超时管理评估和调优 | Oracle Java VisualVM and Oracle Java Mission Control(线程监控,线程转储快照) jstack,本机OS信号如kill -3(线程转储快照)IBM的Java监视和诊断工具 注意:强烈建议您正确了解如何执行JVM线程转储分析 |
中间件,Java EE容器调优,如线程,JDBC数据源等。 | Oracle Java VisualVM and Oracle Java Mission Control (额外关注暴露的Java EE容器运行时MBean)Java EE容器管理和管理控制台 |