当前位置 : 主页 > 编程语言 > 其它开发 >

JVM性能调优与实战进阶篇

来源:互联网 收集:自由互联 发布时间:2022-05-20
本篇了解大厂都在使用的ZGC的基础原理和处理过程,了解JVM性能调优的大致步骤和过程,配合之前文章Arthas线上诊断工具 ZGC诞生原因 Java生态非常强大,但还不够,有些场景仍处于劣势
JVM性能调优与实战进阶篇-上 本篇了解大厂都在使用的ZGC的基础原理和处理过程,了解JVM性能调优的大致步骤和过程,配合之前文章Arthas线上诊断工具 ZGC 诞生原因

Java生态非常强大,但还不够,有些场景仍处于劣势,而ZGC的出现可以让Java语言抢占其他语言的某些特定领域市场。比如

  • 谷歌主导的Android手机系统显示卡顿。
  • 证券交易市场,实时性要求非常高,目前主要是C++主导。
  • 大数据集群如HBase的性能。
特性
  • ZGC(The Z Garbage Collector)为JDK11推出一款低延迟的垃圾回收器。STW即停顿时间低于1ms,且不会随着堆的大小增加而增加。

    • 实现主要原理:全并发处理(仅对GC ROOTS进行遍历时会暂停)
  • 高版本JDK16之后支持16TB级别的堆;

    • 实现主要原理:Region分区管理、染色指针寻址
  • 应用程序吞吐量最多减少15%。

    • 实现主要原理:当生命周期很短的对象分配速率很高的时候,大量对象不会被进行标记收集,会产生大量浮动垃圾从而影响吞吐量,并且堆中可转移对象的空间就会越来越小。
  • 为未来的GC新特性奠定基础。

    • 实现主要原理:染色指针中未被使用预留的18 bits。
内存布局

ZGC采用堆空间分页模型的机制,堆空间分页模型也非常符合Linux Kernel2.6引入的标准大页(huge page)如4KB的处理方式。本质与G1一样,没有分代的概念,ZGC也采用基于Region的堆内存布局,不一样的是ZGC的Region具有动态性:动态创建销毁、动态容量大小。ZGC一共分为三种Region:

  • 小型Region(小页面):容量固定为 2MB,存放小于256KB的对象。
  • 中型Region(中页面):容量固定为 32MB,存放大于256KB小于4MB的对象。
  • 大型Region(大页面):容量为 2*N MB,可以动态变化,每个大Region中只会存放一个大对象,并且不会被重分配(即后文介绍的对象的复制),因为大对象的复制代价高昂。

image-20220227134728097

image-20220227123743039

指针着色技术(Color Pointers)
  • ZGC只支持64位的系统,也即是64位的指针。
  • ZGC在JDK11的ZGC来分析中低42位即2的42次方来表示使用中的堆空间,也即是可管理的内存,而在JDK更高版本有所变化。
  • ZGC借助几位高位来做GC相关的事情比如快速实现垃圾回收的并发标记、转移和重定位等。
  • 预留用来给未来的GC新特性预留的扩展点

image-20220227124515793

一段C程序mapping.c看下ZGC的64位虚拟地址空间的指针着色技术展示

image-20220227125500950

编译执行,三个地址一样,也即是同一个实地址映射到3个虚地址。

image-20220227125701554

整体流程 概述

主要分为两步

  • 标记阶段(标记垃圾)
  • 转移阶段(对象复制或移动)

image-20220227130147942

image-20220227133154167

垃圾标记

垃圾标记算法采用可达性分析算法

image-20220227130457115

  • Remapped
    • GC前所有内存都是Remapped,或者标记后如果还是Remapped则是垃圾。
  • M0,发生两次GC为例,M0是1次GC。
    • 前一次GC的标记阶段被标记过的活跃对象,但是上次GC未对对象进行转移。
  • M1,发生两次GC为例,M0是2次GC。
    • 本次垃圾回收中识别的活跃对象。

标记阶段,对象分配(Remapped)

  • 初始标记(标记根)
  • 并发标记(标记剩余)
  • 再标记(解决漏标)

标记结束后Remapped对象即为垃圾对象。而下次标记使用M1表示活跃。

image-20220227132624080

ZGC转移
  • 如果是同一个页面则等同于标记整理。
  • 如果是不同页面等同于复制算法。
JVM调优概述 背景
  • 生产环境中的问题

    • 生产环境中的问题。
    • 生产环境发生了内存溢出该如何处理?
    • 生产环境应该给服务器分配多少内存合适?
    • 如何对垃圾回收器的性能进行调优?
    • 生产环境 CPU 负载飙高该如何处理?
    • 生产环境应该给应用分配多少线程合适?
    • 不加 log,如何确定请求是否执行了某一行代码?
    • 不加 log,如何实时查看某个方法的入参与返回值?
  • 为什么要调优

    • 防止出现 OOM
    • 解决 OOM
    • 减少 Full GC 出现的频率
  • 调优场景

    • Full GC 次数频繁。
    • GC 停顿时间过长(超过1秒)。
    • 应用出现OutOfMemory 等内存异常。
    • 系统吞吐量与响应性能不高或下降
  • 不同阶段的考虑

    • 上线前
    • 项目运行阶段
    • 线上出现 OOM
调优概述
  • 监控的依据
    • 运行日志
    • 异常堆栈
    • GC 日志
    • 线程快照
    • 堆转储快照
  • 调优的大方向
    • 合理地编写代码
    • 充分并合理的使用硬件资源
    • 合理地进行 JVM 调优
调优目标

JVM调优目标是使用较小的内存占用来获得较高的吞吐量或者较低的延迟,从这里也可以知道其重要指标有三个:

  • 内存占用:程序正常运行需要的内存大小。
  • 延迟:由于垃圾收集而引起的程序停顿时间。
  • 吞吐量:用户程序运行时间占用户程序和垃圾收集占用总时间的比值。

从上面我们也知道这三者如同分布式CAP理论一样不可完全兼得,对于一个Java程序同时保证内存占用小、延迟低、高吞吐量是不可能的;任何一个指标性能的提高,几乎都是以牺牲其他指标性能的损为代价的,不可兼得。程序的目标不同,调优时所考虑的方向也不同,因此需要结合实际场景,有明确的优化目标,找到性能瓶颈,对瓶颈有针对性的优化。

调优原则
  • 90%也即是大多数的Java应用不需要进行JVM优化。
  • 大多数导致GC问题的原因是代码层面的问题导致的(代码层面)。
  • 上线之前,应先考虑将机器的JVM参数设置到最优。
  • 减少创建对象的数量,减少使用全局变量和大对象(代码层面)。
  • 优先架构调优和代码调优,JVM优化是不得已的手段。
  • 分析GC情况优化代码比优化JVM参数更好。
调优步骤
  • 第 1 步:性能监控
    • GC 频繁
    • cpu load 过高(如top -hP 进程号;top -d 2 -c等)
    • OOM
    • 内存泄露
    • 死锁
    • 程序响应时间较长
  • 第 2 步:性能分析
    • 打印 GC 日志,通过 GCviewer 或者 gceasy来分析异常信息
    • 灵活运用命令行工具、jstack、jmap、jinfo 等
    • dump 出堆文件,使用内存分析工具分析文件
    • 使用阿里 Arthas、jconsole、JVisualVM 来实时查看 JVM 状态
    • jstack 查看堆栈信息
  • 第 3 步:性能调优
    • 适当增加内存,根据业务背景选择垃圾回收器
    • 优化代码,控制内存使用
    • 增加机器,分散节点压力
    • 合理设置线程池线程数量
    • 使用中间件提高程序效率,比如缓存、消息队列等
性能评价/测试指标
  • 停顿时间(或响应时间)
    • 提交请求和返回该请求的响应之间使用的时间,一般比较关注平均响应时间。
      • 数据库查询一条记录(有索引),十几毫秒。
      • 机械磁盘一次寻址定位。4毫秒
      • 从机械磁盘顺序读取 1M 数据。2毫秒
      • 从 SSD 磁盘顺序读取 1M 数据。0.3毫秒
      • 从内存读取 1M 数据。十几微妙
      • Java程序本地方法调用。几微妙
      • 网络传输2Kb数据。1微妙
  • 吞吐量
    • 对单位时间内完成的工作量(请求)的量度
    • 在 GC 中:运行用户代码的事件占总运行时间的比例(总运行时间:程序的运行时间+内存回收的时间)
    • 吞吐量为 1-1/(1+n),其中-XX::GCTimeRatio=n
  • 内存占用
    • Java 堆区所占的内存大小
  • 相互间的关系
    • 以高速公路通行状况为例
    • 吞吐量:每天通过高速公路收费站的车辆的数据
    • 并发数:高速公路上正在行驶的车辆的数目
    • 响应时间:车速
JVM监控及诊断命令行工具

无监控、不调优!命令行安装 jdk 的 bin 目录,这些工具用来获取目标 JVM 不同方面、不同层次的信息,帮助开发人员很好地解决 Java 应用程序的一些疑难杂症。

  • 查看正在运行的Java进程:jps
    • jps(Java Process Status):显示指定系统内所有的 HotSpot 虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。
    • 对于本地虚拟机进程来说,进程的本地虚拟机 ID 与操作系统的进程 ID 是一致的,是唯一的。
    • 基本使用语法为:jps [options] [hostid]
  • 查看JVM统计信息:jstat
    • jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据。在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。常用于检测垃圾回收问题以及内存泄漏问题。(一般生产环境没gui工具,简单可常用这个)
    • 基本使用语法为:jstat - [-t] [-h] [ []],比如jstat -gc 进程id 1000 10
    • jstat 还可以用来判断是否出现内存泄漏
      • 在长时间运行的 Java 程序中,我们可以运行 jstat 命令连续获取多行性能数据,并取这几行数据中 OU 列(即已占用的老年代内存)的最小值。
      • 然后,我们每隔一段较长的时间重复一次上述操作,来获得多组 OU 最小值。如果这些值呈上涨趋势,则说明该 Java 程序的老年代内存已使用量在不断上涨,这意味着无法回收的对象在不断增加,因此很有可能存在内存泄漏。
  • 实时查看和修改JVM配置参数:jinfo
    • jinfo(Configuration Info for Java):查看虚拟机配置参数信息,也可用于调整虚拟机的配置参数
    • 基本使用语法为:jinfo [options] pid,比如jinfo -sysprops 进程id
  • 导出内存映像文件&内存使用情况:jmap
    • 获取 dump 文件(堆转储快照文件,二进制文件),它还可以获取目标 Java 进程的内存相关信息,包括 Java 堆各区域的使用情况、堆中对象的统计信息、类加载信息等。
    • 基本使用语法为:
      • jmap [option]
      • jmap [option] <executable
      • jmap [option] [server_id@]
    • 使用1:导出内存映像文件
      • 手动的方式
      • jmap -dump:format=b,file=<filename.hprof>
      • jmap -dump:live,format=b,file=<filename.hprof>
    • 使用2:显示堆内存相关信息
      • jmap -heap 进程id
      • jmap -histo 进程id
    • 使用3:其他作用
      • jmap -permstat 进程id
      • 查看系统的ClassLoader信息
      • jmap -finalizerinfo
      • 查看堆积在finalizer队列中的对象
  • JDK 自带堆分析工具:jhat
    • jhat(JVM Heap Analysis Tool):Sun JDK 提供的 jhat 命令与 jmap 命令搭配使用,用于分析 jmap 生成的 heap dump 文件(堆转储快照)。jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 文件的分析结果后,用户可以在浏览器中查看分析结果(分析虚拟机转储快照信息)。
    • 使用了 jhat 命令,就启动了一个 http 服务,端口是 7000,即 http://localhost:7000/,就可以在浏览器里分析。
    • 说明:jhat 命令在 JDK9、JDK10 中已经被删除,官方建议用 VisualVM 代替。
    • 基本适用语法:jhat
  • 打印JVM中线程快照:jstack
    • jstack(JVM Stack Trace):用于生成虚拟机指定进程当前时刻的线程快照(虚拟机堆栈跟踪)。线程快照就是当前虚拟机内指定进程的每一条线程正在执行的方法堆栈的集合。
    • 生成线程快照的作用:可用于定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等问题。这些都是导致线程长时间停顿的常见原因。当线程出现停顿时,就可以用 jstack 显示各个线程调用的堆栈情况。
    • 基本语法 : jstack [option] pid
  • 多功能命令行:jcmd
    • 在 JDK 1.7 以后,新增了一个命令行工具 jcmd。它是一个多功能的工具,可以用来实现前面除了 jstat 之外所有命令的功能。比如:用它来导出堆、内存使用、查看 Java 进程、导出线程信息、执行 GC、JVM 运行时间等。
    • jcmd -l:列出所有的 JVM 进程
    • jcmd pid help:针对指定的进程,列出支持的所有具体命令
    • jcmd pid 具体命令:显示指定进程的指令命令的数据
  • 远程主机信息收集:jstatd
    • 之前的指令只涉及到监控本机的 Java 应用程序,而在这些工具中,一些监控工具也支持对远程计算机的监控(如 jps、jstat)。为了启用远程监控,则需要配合使用 jstatd 工具。命令 jstatd 是一个 RMI 服务端程序,它的作用相当于代理服务器,建立本地计算机与远程监控工具的通信。jstatd 服务器将本机的 Java 应用程序信息传递到远程计算机。
JVM监控及诊断工具GUI

前面我们学习Arthas也是一种JVM监控及诊断工具GUI,本篇先抛出影子,后续在单独针对

  • JDK自带的工具
    • jconsole:JDK 自带的可视化监控工具。查看 Java 应用程序的运行概况、监控堆信息、永久区(或元空间)使用情况、类加载情况等。
      • 从 Java5 开始,在 JDK 中自带的 java 监控和管理控制台。用于对 JVM 中内存、线程和类等的监控,是一个基于 JMX(java management extensions)的 GUI 性能监控工具。
    • Visual VM:Visual VM 是一个工具,它提供了一个可视界面,用于查看 Java 虚拟机上运行的基于 Java 技术的应用程序的详细信息。
      • VisualVM是–个功能强大的多合一故障诊断和性能监控的可视化工具。
      • 它集成了多个JDK命令行工具,使用VisualVM可用于显示虚拟机进程及进程的配置和环境信息(jps ,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,替JConsole。
      • 在JDK 6 Update 7以后,Visual VM便作为JDK的一 部分发布(VisualVM 在JDK/bin目录下)。此外,Visual VM也可以作为独立的软件安装。
    • JMC:Java Mission Control,内置 Java Flight Recorder。能够以极低的性能开销收集 Java 虚拟机的性能数据。
  • 第三方工具
    • MAT:MAT(Memory Analyzer Tool)是基于 Eclipse 的内存分析工具,是一个快速、功能丰富的 Java heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗。
      • MAT 不是一个万能工具,它并不能处理所有类型的堆存储文件。但是比较主流的厂家和格式,例如 Sun,HP,SAP 所采用的 HPROF 二进制堆存储文件,以及 IBM 的 PHD 堆存储文件等都能被很好的解析。
      • 最吸引人的还是能够快速为开发人员生成内存泄漏报表,方便定位问题和分析问题。虽然 MAT 有如此强大的功能,但是内存分析也没有简单到一键完成的程度,很多内存问题还是需要我们从 MAT 展现给我们的信息当中通过经验和直觉来判断才能发现。
    • JProfiler:商业软件,需要付费。功能强大。
    • Flame Graphs(火焰图),在追求极致性能的场景下,了解你的程序运行过程中 cpu 在干什么很重要,火焰图就是一种非常直观的展示 CPU 在程序整个生命周期过程中时间分配的工具和调用找中的 CPU 消耗瓶颈。

此外针对JVM运行时参数和分析GC日志再单独增加专题文档

**本人博客网站 **IT小神 www.itxiaoshen.com

上一篇:Redis原理再学习04:数据结构
下一篇:没有了
网友评论