成为Java GC专家(5)

先别急着怪GC:Java GC问题的性能标尺、触发原因解码与根因判定解析 接口毛刺出现时,GC常被第一个怀疑,却未必是元凶。本文先用单次停顿不超过TP9999、GC吞吐不低于99.99%两条约束,把SLA换算成每分钟6毫秒的停顿预算;再对照JDK 8到25的HotSpot源码解读GCCause,并在JDK 8上实测13种触发场景,发现禁用显式GC会连带屏蔽jcmd GC.run,JNI临界区可引发462次Full GC;又用一小时仿真比较时序、概率、实验、反证四种判因方法,揭示混杂与时长偏倚陷阱;最后按Mutator类型、九类问题和排查难度给出定位思路与适用范围。 阅读详情

转载地址:http://www.importnew.com/13954.html

这是“成为Java GC专家”系列的第五篇文章。在第一篇深入浅出Java垃圾回收机制中,我们已经学习了不同的GC算法流程、GC的工作原理、新生代(Young Generation)和老年代(Old Generation)的概念。你应该了解了JDK7中5种GC类型以及各种类型对应用程序的影响。

在第二篇如何监控Java的垃圾回收中,阐述了JVM是怎样实际执行垃圾回收的,我们怎样去监控GC以及哪些工具能让这个过程更高效。

第三篇如何如何优化Java垃圾回收机制中展示了一些基于真实案例的最佳实践。同时讲解了怎样尽量少地将对象放入老年代空间(Old Area),避免频繁地执行完全垃圾回收(Full GC)。还说明了如何设置GC的类型和内存大小。

在第四篇Apache的MaxClients参数详解及其在Tomcat执行FullGC时的影响中,解释了MaxClients参数的重要性以及它在垃圾回收过程中对整个系统性能的显著影响。

第五篇文章将讲解Java程序性能调优的原则,尤其是在这个过程中必要的知识以及判断你的程序是否需要调优。还会介绍调优过程中你可能遇到的问题。本文最后会给出一些建议,依据这些你能在对Java程序调优时做出更好的决策。

概述

并不是每个程序都需要调优。如果一个程序性能表现和预期一样,你不必付出额外的精力去提高它的性能。然而,在程序调试完成之后,很难马上就满足它的性能需求,于是就有了调优这项工作。无论哪种编程语言,对应用程序进行调优都需要丰富的技术知识并且注意力高度集中。另外,你也不应该用相同的方式对两个程序调优,因为每个程序都有它自己独特的运作方式和不同的资源使用方式。正因如此,调优比写程序需要更多基础知识。例如,你需要熟悉虚拟机、操作系统和计算机架构。而当你面对在这些知识基础上编写的程序时,就能成功地对它进行调优。

有时调优Java程序只需要修改JVM参数,比如GC的参数。但也有些时候需要修改程序代码。无论那种方法,你首先都需要监控执行Java程序的进程。因此本文会讲解下面几个问题:

  • 怎样监控Java程序?
  • 应该给JVM设置怎样的参数?
  • 如何确定是否需要修改代码?

对Java程序进行调优的必要知识

Java程序在Java虚拟机中运行。因此为了进行调优,你需要理解JVM的工作流程。我之前有一篇博文Understanding JVM Internals,将让你对JVM有深入的了解。

本文中有关JVM运作过程的知识主要关于GC和Hotspot。尽管只有这两方面的知识可能无法对所有的Java程序进行调优,但是这两个因素在大多数情况下都影响着Java程序的性能。

值得注意的是,从操作系统的角度来看,JVM也是一个应用程序进程。为了给JVM创造良好的运行环境,你还需要对操作系统分配资源的过程有所了解。这意味着,想要调优Java程序,除了JVM你也应该理解操作系统或者硬件的工作方式。

需要具有的知识还有Java这门语言本身。另外理解锁和并发、类加载和对象创建都是非常重要的。

当开始调优Java程序时,你应该整合以上各方面的知识来完成工作。

Java程序性能调优的过程

图1是一张Java程序性能调优的流程图,摘自由Charlie Hunt和Binu John所著的Java Performance。

图1:Java程序性能调优的过程

JVM分布式模型

JVM分布式模型用于决定是在一个JVM还是多个JVM上执行Java程序。你可以根据其有效性、响应能力和可维护性来进行选择。当在多台服务器上运行JVM时,你也可以选择将多个JVM运行于一台服务器或者每台服务器运行一个JVM。例如,对于每台服务器,你可以运行一个使用8GB堆内存的JVM,也可以运行4个使用2GB的JVM。你理应根据处理器内核的个数还有程序的特性来决定这个数量。当优先考虑响应能力时, 使用2GB的堆内存会优于8GB的,原因是这样能在更短的时间内完成Full GC。当然,8GB的堆内存可以降低Full GC的频率。如果你的程序使用了内部缓存,还可以通过增加缓存命中率来提高响应能力。综上所述,选择合适的模型需要考虑应用程序的特性,然后在各种模型中 选定一个能够扬长避短的。

JVM架构

选择JVM其实就是决定使用32位还是64位的JVM。在相同的条件下,你最好用32位的。因为32位的JVM比64位性能更好。然而,32位 JVM最大支持的堆内存是4GB(无论在32位操作系统还是64位的上,实际可分配的大小都只有2-3GB)。如果需要更大的堆内存,还是用64位的 JVM比较合适。

表1:性能比较(数据来源)

测试基准 时间(秒) 系数
C++ Opt 23 1.0x
C++ Dbg 197 8.6x
Java 64-bit 134 5.8x
Java 32-bit 290 12.6x
Java 32-bit GC* 106 4.6x
Java 32-bit SPEC GC* 89 3.7x
Scala 82 3.6x
Scala low-level* 67 2.9x
Scala low-level GC* 58 2.5x
Go 6g 161 7.0x
Go Pro* 126 5.5x

下一步就是运行程序来测试它的性能。这个过程包括GC调优、改变操作系统设置和修改代码。对于这些工作,你可以使用系统监视工具或者性能分析工具。

注意:针对响应能力的调优和针对吞吐量的调优可能使用不同的方法。如果经常性地发生stop-the-word(串行GC暂时中断程序执行),程序的响应能力就会被降低。比如在高吞吐量时执行Full GC。不要忘记,在调优时往往有得有失。这样需要折衷处理的事情不仅发生在响应能力和吞吐量之间。例如使用更多的CPU资源来降低内存的使用,或者不得不忍受响应能力和吞吐量其中一个性能指标的下降。相反的情况同样可能发生,实际的调优应该根据各指标的优先级来执行。

上面图1中的流程展示了几乎可用于所有Java程序的性能调优过程,包括Swing应用。然而,对于我们公司NHN用于提供网络服务的服务器端程序来说,这个方法多少有些不合适。下面图2中的流程是根据图1修改而来,它更简单,也更适合NHN。

图2:对HNH的Java程序的调优过程

其中,Select JVM表示尽可能使用32位的JVM,除非你需要用64位的JVM来维护一个数GB的缓存。

现在,跟随图2中的流程,你会了解到每一步具体的工作。

JVM参数

我会主要讲解如何为Web服务端程序设置合适的JVM参数。尽管不一定适合所有的案例,但是最好的GC算法是Concurrent Mark Sweep(CMS垃圾回收),特别是对于Web服务端程序。因为低延迟是非常重要的。当然,在使用CMS时,由于新生代空间(New Area)的分配,可能发生较长时间的stop-the-world现象,不过调整新生代空间的大小或者它和整个堆空间的比例可能解决这个问题。

指定新生代空间的大小和指定整个对堆内存的大小同样重要。你最好使用–XX:NewRatio来指定新生代和整个堆的大小比例,或者直接用–XX:NewSize来指定所需的新生代空间。这个配置是非常必要的,因为大部分对象都不会存活很久。在Web程序中,除了缓存数据,其他多数对象都只在HttpRequest到HttpResponse期间创建。这个时间几乎不会超过1秒,表示这些对象的存活时间也不会超过1秒。如果新生代空间不够大,对象会被转移到老年代空间,以便腾出地方给新对象使用。老年代空间(Old Area)垃圾回收的代价是比新生代空间大的多的,因此很需要设置一个充足的新生代空间。

然而,当新生代空间的大小超过一个特定的水平,程序的响应能力会被降低。因为新生代空间的垃圾回收过程,基本上是将数据从一个Survivor Area复制到另外一个(From Space和To Space)。另外,stop-the-world的现象在新生代空间和老年代空间执行垃圾回收时都会发生。如果新生代空间变大,那么Survivor Area的空间也会更大,于是每次复制的数据就更多。基于这样一种特性,我们应该通过指定不同操作系统中HotSpot JVM的NewRatio参数来分配合适大小的新生代空间。

表2:不同操作系统和配置下NewRatio的默认值

操作系统及参数 默认-XX:NewRatio
Sparc -server 2
Sparc -client 8
x86 -server 8
x86 -client 12

如果设置了NewRatio,那么整个堆空间的1/(NewRatio +1)就是新生代空间的大小。上表可以看出Sparc -server的NewRatio默认值很小,因为相比x86的操作系统,Sparc以前更多用于高端应用,这个值就是为它们设置的。但现在x86操作系统的性能有很大提升,使用它们作为服务器已经很普遍了。因此指定NewRatio为2或者3是更好的选择,就和Sparc -server上的配置一样。

另外,你还可以通过指定NewSize和MaxNewSize来代替NewRatio。那么新生代空间创建时的大小就是指定的NewSize,随后可以一直增长到MaxNewSize的值。Eden(新创建对象存放的区域)和Survivor Area两个区域会随比例增加。就和你为-Xms(译者注:原文是-Xs,应该是笔误)和-Xmx设置相同的值一样,将MaxSize和 MaxNewSize设置为相同的也是一个好选择。

如果同时指定了NewRatio和NewSize,你应该使用更大的那个。于是,当堆空间被创建时,你可以用过下面的表达式计算初始新生代空间的大小:

1
min(MaxNewSize, max(NewSize, heap/(NewRatio+ 1 )))

无论如何,仅通过一次尝试就找到合适的堆空间和新生代空间大小是不可能的。根据我在NHN运行Web服务器的经验,建议使用下面的JVM参数来运行Java程序。监控在这些参数的条件下程序的性能表现之后,你就能够选择更合适的GC算法或者配置。

表3:推荐的JVM参数

类型 参数
运行模式 -sever
整个堆内存大小 为-Xms和-Xmx设置相同的值。
新生代空间大小 -XX:NewRatio: 2到4. -XX:NewSize=? –XX:MaxNewSize=?. 使用NewSize代替NewRatio也是可以的。
持久代空间大小 -XX:PermSize=256m -XX:MaxPermSize=256m. 设置一个在运行中不会出现问题的值即可,这个参数不影响性能。
GC日志 -Xloggc:$CATALINA_BASE/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps. 记录GC日志并不会特别地影响Java程序性能,推荐你尽可能记录日志。
GC算法 -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75. 一般来说推荐使用这些配置,但是根据程序不同的特性,其他的也有可能更好。
发生OOM时创建堆内存转储文件 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$CATALINA_BASE/logs
发生OOM后的操作 -XX:OnOutOfMemoryError=$CATALINA_HOME/bin/stop.sh 或 -XX:OnOutOfMemoryError=$CATALINA_HOME/bin/restart.sh. 记录内存转储文件后,为了管理的需要执行一个合适的操作。

测定程序的性能

为了得到程序的性能表现,需要以下这些信息:

  • 系统吞吐量(TPS、OPS):从整体概念上理解程序的性能。
  • 每秒请求数(Request Per Second – RPS):严格来说,RPS和单纯的响应能力是不同的,但是你可以把它理解为响应能力。通过这个指标,你能够了解到用户需要多长时间才能得到请求的结果。
  • RPS的标准差:如果可能的话,还有必要包括事件的RPS。一旦出现了偏差,你应该检查GC或者网络系统。

为了得到更准确的性能表现,你应该等到程序彻底启动完成后再进行测量,因为字节码随后会被HotSpot JIT编译为本地机器码。总体来说,需要在程序加载完指定功能后,用nGrinder等工具测试至少10分钟。

切实地调优

如果nGrinder测试的结果满足了预期,那么你不需要对程序进行性能调优。如果没有达到预期结果,你就应该执行调优来解决问题。接下来会通过实例讲解方法。

stop-the-world耗时过长

stop-the-world耗时过长可能是由于GC参数不合理或者代码实现不正确。你可以通过分析工具或堆内存转储文件(Heap dump)来定位问题,比如检查堆内存中对象的类型和数量。如果在其中找到了很多不必要的对象,那么最好去改进代码。如果没有发现创建对象的过程中有特别的问题,那么最好单纯地修改GC参数。

为了适当地调整GC参数,你需要获取一段足够长时间的GC日志,还必须知道哪些情况会导致长时间的stop-the-world。想了解更多关于如何选择合适的GC参数,可以阅读我同事的一篇博文:How to Monitor Java Garbage Collection。

CPU使用率过低

当系统发生阻塞,吞吐量和CPU使用率都会降低。这可能是由于网络系统或者并发的问题。为了解决这个问题,你可以分析线程转储信息(Thread dump)或者使用分析工具。阅读这篇文章可以获得更多关于线程转储分析的知识:How to Analyze Java Thread Dumps。

你可以使用商业的分析工具对线程锁进行精确的分析,不过大部分时候,只需使用JVisualVM中的CPU分析器,就能获得足够的信息。

CPU使用率过高

如果吞吐量很低但是CPU使用率却很高,很可能是低效率代码导致的。这种情况下,你应该使用分析工具定位代码中性能的瓶颈。可使用的工具有:JVisualVM、Eclipse TPTP或者JProbe。

调优方法

建议你使用如下方法对程序进行调优。

首先,检查性能调优是否必要。测量性能不是一件简单的工作,你也不能保证每次都获得满意的结果。因此如果程序已经满足预期性能需求,不必在调优上增加额外的投入了。

问题只出在一个地方,你要做的就是去解决掉它。二八定律(Pareto principle)对性能调优同样适用。这不是说某个模块的低性能一定只源于一个问题,而是强调我们应该在调优时把注意力放在影响最大的那个问题上。在处理好了最重要的之后,你才应该去解决剩下其他的。也就是建议一次只对一个问题进行修复。

另外需要考虑到气球效应(Balloon effect),有得必有失。你可以通过使用缓存来提高响应能力,但是当缓存逐渐增大,执行一次Full GC的时间也会更长。一般而言,如果你希望内存使用率比较低,那么吞吐量和响应能力可能都会恶化。因此,要知道什么对自己程序来说最重要的,而哪些又是次要的。

到此为止,你应该已经了解了如何对Java程序进行性能调优。为了介绍性能测定的具体过程,我不得不省略其中一些细节,不过我认为这些也足够应对大多数Java Web服务端程序了。

最后祝调优好运!

Logisim 原码一位乘法器设计 图解及代码(计算机组成原理) Logisim 原码一位乘法器设计 图解及代码(计算机组成原理) 包对 阅读详情

相关推荐

手把手教你用Simulink搭建BPSK调制解调系统(附完整参数设置与波形分析)

本文详细指导如何使用Simulink搭建BPSK调制解调系统,涵盖从理论到实践的全流程,包括参数设置、模块连接和波形分析。重点解析调制解调中的关键参数和常见问题,如带通滤波器设计、载波同步等,帮助读者快速掌握BPSK系统的实现与调试技巧。

weixin_27741699的博客 422

JVM的优化

我们首首先要了解1.8jdk 1、我们为什么要对jvm做优化? 在本地开发环境中我们很少会遇到需要对jvm进行优化的需求,但是到了生产环境,我们 可能将有下面的需求: (1)运行的应用“卡住了”,日志不输出,程序没有反应 (2)服务器的CPU负载突然升高 (3)在多线程应用下,如何分配线程的数量? …… 所以我们不仅要让程序能跑起来,而且是可以跑的更快!可以分析解决在生产环境中所遇到的各种“棘手”...

weixin_46106066的博客 5000

ABB RobotWare数据包全版本归档与高效获取指南

记得去年有个汽车产线升级项目,客户现场用的是RobotWare 6.05.0166版本,而我的RobotStudio里只有最新的7.x系列。RobotWare数据包本质上就是ABB机器人的"操作系统镜像",不同版本的机器人控制器需要匹配特定版本的RobotWare。有个容易忽略的细节:偶数版本号(如6.04、6.06)通常比奇数版本更稳定,因为ABB的内部发布策略是偶数版用于正式产线,奇数版多用于测试新功能。最终实现了零停机升级,这个经验告诉我们:版本切换不是简单的覆盖安装,而是需要系统性的验证流程。

weixin_30764883的博客 278

一次JVM参数设置解析

-Xms4096m 最小堆内存 -Xmx4096m 最大堆内存 最小堆内存和最大堆内存(根据实际情况调整) -XX:NewRatio=1 新生代和老年代的比值 ,这里新生代和老年代大小4096/2=2048 -XX:SurvivorRatio=8 新生代Eden和一个Suivior的比值,默认8,Suivior区有2个,Eden大小2048*0.8 XX:MetaspaceSize=512m 元空间大小,不设置,默认系统能申请到的内存大小,为了可控,最好设置,这里有512M -XX:M

熊浪的博客 1095

Paho Asynchronous MQTT C Client Library--MQTTAsync API

异步MQTT 客户端库for C

yoyo勰的博客 1264

Vivado新手必看:5分钟搞定FPGA程序固化到Flash(附MCS文件生成全流程)

本文为FPGA新手详细解析了如何将程序固化到Flash存储器的完整流程。核心在于理解SRAM工艺FPGA断电易失的特性,并利用Vivado工具将比特流文件转换为Flash可识别的MCS文件。文章重点拆解了生成MCS文件的关键配置步骤、硬件连接要点,以及通过JTAG烧写Flash的实战操作,并提供了常见启动失败问题的排查清单,帮助开发者实现设计从调试到产品部署的关键跨越。

weixin_29230901的博客 352

性能优化 - JVM 优化

JVM(Java 虚拟机)是一个能够解释和执行 Java 字节码的虚拟计算机,它是 Java 程序运行的基础平台。JVM 包含了多个子系统,包括类加载器子系统、内存管理子系统、执行引擎子系统等,可以将 Java 程序翻译成为机器指令运行在不同的操作系统平台上。注意:JVM 是运行在操作系统之上的,它与硬件没有直接的交互,如下图所示:JVM 优化则是指对 Java 程序在运行时的性能进行提升的一系列技术和策略。

whoyouare6165的博客 3441

手把手教你用USBasp给Arduino UNO烧写程序(ICSP模式,含BootLoader选择指南)

本文详细介绍了如何使用USBasp编程器通过ICSP模式为Arduino UNO烧写程序,包括硬件连接、软件配置、Hex文件处理和BootLoader选择指南。内容涵盖从新手入门到高级优化的全流程,帮助开发者高效完成烧写任务,特别适合需要修复BootLoader或进行量产烧写的用户。

weixin_33958381的博客 364

成为Java GC专家(5)—Java性能调优原则

本文由 ImportNew - 蒋 生武 翻译自 dzone。欢迎加入翻译小组。转载请参见文章末尾的要求。 这是“成为Java GC专家”系列的第五篇文章。在第一篇深入浅出Java垃圾回收机制中,我们已经学习了不同的GC算法流程、GC的工作原理、新生代(Young Generation)和老年代(Old Generation)的概念。你应该了解了JDK7中5种GC类型以及各种类型对应

青色刀客的专栏 1312

PyTorch强化学习实战(1)——强化学习(Reinforcement Learning,RL)详解

在本节中,开始进入探索强化学习 (Reinforcement Learning, RL) 之旅,首先剖析了强化学习的独特之处及其与监督学习、无监督学习范式的关系。随后系统学习了强化学习的基础形式化框架及其内部关联机制,并深入讲解了马尔可夫过程 (Markov Process, MP)、马尔可夫奖励过程 (Markov Reward Process, MRP) 和马尔可夫决策过程 (Markov Decision Process, MDP),为后续学习奠定基础。

盼小辉丶的博客 2050

【OS】AUTOSAR OS系统调用产生Trap的过程详解

AUTOSAR OS系统调用产生Trap的过程详解

qq_36056498的博客 2438

JVM常用基础参数NewRatio详解

NewRatio参数讲解 在上一篇文章SurvivorRatio的讲解中, 可以知道 新生代占1 , 老年代占2, 即 年轻代占整个堆内存的三分之一. SurvivorRatio的讲解 而NewRatio参数就是用于调整新生代与老年代的占比的. NewRatio 参数实战 使用如下的测试代码, public class MyHelloGc { public static void main(String[] args) throws InterruptedException {

Java持续实践 1万+

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

成为Java GC专家(5):Java应用性能调优的原则

This is the fifth article in the series of "Become a Java GC Expert".In the first issueUnderstanding Java Garbage Collection we have learned a...

chuifuhuo6864的博客 915

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

上一篇: 成为Java GC专家(4)
下一篇: kafka0.72扩容和磁盘优化
willwill1101
博客等级 码龄11年 5粉丝 29原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值