深入理解Java虚拟机:Jvm性能调优

2022-12-01 21:48:57 浏览数 (2)

本篇内容包括:Jvm 性能调优简介;根据需求目标进行 Jvm 调优规划(即 调优的目标、调优的步骤);Jvm 调优参数、命令、工具;以及 Java 中的内存泄露问题的详解…

一、Jvm 性能调优简介

到目前为止,我们已经对 Jvm 进行了简单的了解,知道了 Jvm 运行时各种各样的内存结构,各种垃圾回收机制以及各种对应的垃圾收集器及其配置。而我们整个 Jvm 系列的最终目标不当仅仅以了解基础理论为终点,理论总应作为实践的工具。接下来,我们开始了解 Java 性能优化的最后一环:Jvm 性能调优。

我们常说的 Jvm 性能调优实际上有着三个以及的目的:

  1. 根据需求进行 Jvm 规划和预调优;
  2. 优化运行 Jvm 运行环境(慢,卡顿);
  3. 解决 Jvm 运行过程中出现的各种问题(OOM)

二、根据需求目标进行 Jvm 调优规划

而这一性能调优基本的步骤就是明确优化目标、发现性能瓶颈、性能调优、通过监控及数据统计工具获得数据、确认是否达到目标。或者笼统的概括为 以业务场景开始压力测试监控,查看调优结果 两步。

1、调优的目标

首先我们要明白 Jvm 性能调优的形式方案并不是固定的,不同的应用有着不同的目标与不同的问题。而根据需求进行 Jvm 规划和预调优最先要明确的就是调优的目标。

下面是 Jvm 常提到的性能指标

  • 吞吐量:用户代码时间 /(用户代码执行时间 垃圾回收时间)优先场景:科学计算、数据挖掘;
  • 响应时间:STW 越短,响应时间越好 网站页面、API 提供
  • 内存占用: Java 堆内存占用的大小
  • 收集频率: 垃圾收集器工作的频率
  • 收集效率: 一个对象诞生到死亡的时间

其中 吞吐量、响应时间、内存占用 三条是 GC 的矛盾之处, 如果要使工作线程的吞吐量提高, 就必须使工作线程的暂停时间降低,那么垃圾收集的时间必然就跟着降低,那内存占用就也会提高,高吞吐量这会让应用程序的用户感觉只有应用程序线程在做 “生产性” 工作。 所以直觉上,吞吐量越高程序运行越快。

低暂停时间是 从用户的角度来看不管是 GC 还是其他原因导致一个工作线程被挂起始终是不好的。特别是对于一个交互式应用程序。

所以,在设计(或使用)GC 算法时,我们必须确定我们的目标:一个GC算法只可能针对两个目标之一(即只专注于最大吞吐量和最小合理暂停时间),或尝试找到一个二者的折衷

2、调优的步骤

如何进行调优,下面是调优常用的步骤:

  1. 熟悉业务场景,选择合适的垃圾回收器
    • 垃圾回收器:ParallelGC(PS PO):单位时间内,STW 的时间最短 (发生 2 次 STW,0.2 0.2=0.4),垃圾回收时间占比最低,这样就称吞吐量高
    • 垃圾回收器:CMS,G1:单位时间内,可能发生5次 STW,但是单次的 STW 时间最短(0.1 0.1 0.1 0.1 0.1=0.5)
  2. 计算内存需求,给 Jvm 设置合适的内存大小
    • Eden 区应能容纳所有【并发量 * (请求-响应)】的数据
    • Survivor 区应大到能保留【当前活跃对象 需要晋升对象】的数据
    • 年轻代应该占整个堆内存的四分之一到一半
    • 晋升阈值配置得当,让长时间存活对象尽快晋升
  3. 选定 CPU,越高越好
  4. 设定日志参数
  5. 进行压测监控 JMeter

三、Jvm 调优参数与工具

1、常用的 Jvm 调优的参数
代码语言:javascript复制
-Xms2g
   初始化推大小为 2g
-Xmx2g
   堆最大内存为 2g
-XX:NewRatio=4
   设置年轻的和老年代的内存比例为 1:4
-XX:SurvivorRatio=8
   设置新生代 Eden 和 Survivor 比例为 8:2
–XX: UseParNewGC
    指定使用 ParNew   Serial Old 垃圾回收器组合
-XX: UseParallelOldGC
    指定使用 ParNew   ParNew Old 垃圾回收器组合
-XX: UseConcMarkSweepGC
    指定使用 CMS   Serial Old 垃圾回收器组合
-XX: PrintGC
    开启打印gc信息
-XX: PrintGCDetails
    打印 gc 详细信息
2、常用的 Jvm 调优的命令
  • java -XX: PrintFlagsInitial -version > PrintFlagsInitial.txt — 查看jvm所有默认参数值
  • jps:查看当前java进程
    • -l : 输出主类或jar的完全路径名
    • -v : 输出jvm参数
    • -m : 输出jvm启动时传递给main()的参数
  • jstat : 用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。jstat命令格式 :jstat [Options] vmid [interval] [count]
    • Options, 一般使用 -gcutil 或 -gc 用于查看gc情况
      • -gc:统计 jdk gc时 heap信息,以使用空间字节数表示
      • -gcutil:统计 gc时, heap情况,以使用空间的百分比表示
      • -class:统计 class loader行为信息
      • -compile:统计编译行为信息
      • -gccapacity:统计不同 generations(新生代,老年代,持久代)的 heap容量情况
      • -gccause:统计引起 gc的事件
      • -gcnew:统计 gc时,新生代的情况
      • -gcnewcapacity:统计 gc时,新生代 heap容量
      • -gcold:统计 gc时,老年代的情况
      • -gcoldcapacity:统计 gc时,老年代 heap容量
      • -gcpermcapacity:统计 gc时, permanent区 heap容量
    • vmid是Java虚拟机ID,即当前运行的java进程号,在Linux/Unix系统上一般就是进程ID == pid
    • interval, 间隔时间(单位为秒或毫秒)
    • count,打印次数,如果省略则打印无数次
  • jmap(Java Virtual Machine Memory Map): jdk提供的一个生成java虚拟机的堆转储快照dump文件的命令行工具。例:[option] -dump:live,format=b,file=dump.hprof 1712 — 生成进程 【PID = 1712】的堆转储快照dump文件
    • live —> 只转储活着的对象;没有指定则转储堆中的所有对象
    • format=b —> 表示以hprof二进制格式转储java堆内存
    • file= —> 用于指定快照dump文件的文件名
  • jstack : 用于生成java虚拟机当前时刻的线程快照,为虚拟机内每一条线程正在执行的方法堆栈的集合,用于定位线程出现长时间停顿的原因,诸如死锁、死循环等。 jstack [option] PID [option]
    • -F : 当正常输出请求不被响应时,强制输出线程堆栈
    • -l : 除堆栈外,显示关于锁的附加信息
    • -m : 如果调用到本地方法的话,可以显示C/C 的堆栈
3、常用的 Jvm 调优的工具

借助 GCViewer 日志分析工具,可以非常直观地分析出待调优点。可从以下几方面来分析:

Memory,分析 Totalheap、Tenuredheap、Youngheap 内存占用率及其他指标,理论上内存占用率越小越好;

Pause,分析 Gc pause、Fullgc pause、Total pause 三个大项中各指标,理论上 GC 次数越少越好,GC 时长越小越好;


四、Java 中的内存泄露

内存泄漏:对象已经没有被应用程序使用,但是垃圾回收器没办法移除它们,因为还在被引用着。

在 Java 中,内存泄漏

就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为 Java 中的内存泄漏,这些对象不会被 GC 所回收,然而它却占用内存。

内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你 Out of memory。

Java 内存泄漏的几种情况:

  • 静态集合类:如HashMap、LinkedList等等。如果这些容器为静态的,那么它们的生命周期与程序一致,则容器中的对象在程序结束之前将不能被释放,从而造成内存泄漏。
  • 资源未关闭:各种连接,如数据库连接、网络连接和IO连接等,文件读写等,造成的内存泄漏
  • 此外还有诸如 监听器、内存类、单例模式等等场景的使用,都有可能造成内存泄漏

避免内存泄漏的方法

  • 好的编码习惯:对可能出现内存泄漏的场景给予“特殊关照”:
    • 注意像 HashMap 、ArrayList 的集合对象
    • 注意 事件监听 和 回调函数
    • 在确认一个对象无用后,将其所有引用显式的置为null;
    • 数据库连接,使用 try…finally 结构,在 finally 中关闭 Statement 对象和连接。
  • 好的测试工具:在开发中不能完全避免内存泄漏,关键要在发现有内存泄漏的时候能用好的测试工具迅速定位问题的所在。

0 人点赞