JVM --- 三:GC优化基础

本文探讨了JVM垃圾回收器性能的三个关键指标:吞吐量、延迟和内存占用,以及优化JVM垃圾回收器的三个基本原则。文章还详细介绍了如何通过HotSpotVM的命令行选项收集和解析GC日志,以便于理解和优化垃圾回收过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载:http://blog.youkuaiyun.com/zhoutao198712/article/details/7783038

一步步优化JVM

        目录

        一:JVM概述、方法及需求
        二:JVM部署模型和JVM Runtime
        三:GC优化基础
        四:决定Java堆的大小以及内存占用
        五:优化延迟或者响应时间(1)---优化堆大小
        五:优化延迟或者响应时间(2)---优化survivor
        五:优化延迟或者响应时间(3)---优化CMS
        六:优化吞吐量
        七:其他

三:GC优化基础

   本节主要描述关于垃圾回收器性能的三个指标,三个关于垃圾回收器优化的基本原则,以及优化HotSpot VM的垃圾回收器的信息收集,在这些指标中权衡以及信息的收集是非常重要的。

1、性能指标

   吞吐量:衡量垃圾回收器运行在性能峰值的时候不需要关心垃圾回收器暂停的时间或者需要占用内存的能力。

   延迟:衡量垃圾回收器最小化甚至消灭由垃圾回收器引起的暂停时间和应用抖动的能力。

   内存占用:衡量为了高效的运行,垃圾回收器需要的内存。

   一项指标的提升,往往需要牺牲其他一项或者两项指标。换一句话说,一项指标的妥协通常是为了支持提升其他一项或者两项指标。然而,对于大多数应用来说,很少有3项指标都非常重要,通常,一项或者两项比其他的更重要。

   由于始终需要各种权衡,那么知道哪项指标对应用是最有必要的就显得非常重要。

2、原则

   在优化JVM垃圾回收器的时候,有3项基本原则

  • 在minor垃圾回收器中,最大量的对象被回收,这个被称为Minor GC回收原则。秉承这个原则可以减少由应用产生的full垃圾回收数量和频率,Full垃圾回收往往需要更长的时间,以致于应用无法达到延迟和吞吐量的需求。
  • 更多的内存分配给垃圾回收器,也就是说更大的Java堆空间,垃圾回收器和应用在吞吐量和延迟上会表现得更好,这条原则被称为GC最大内存原则。
  • 优化JVM垃圾回收器的3个指标中的2个,这个被称为2/3 GC优化原则。

   在进行优化JVM垃圾回收器的时候, 牢牢记住这三条原则会让你的优化任务更容易完成。

3、命令行选项和GC日志

   从垃圾回收器获取监控信息,是优化JVM的重要操作。收集垃圾回收器信息的最好办法就是收集日志。这个意味着通过HotSpot VM的命令行选项可以收集垃圾回收器的统计信息。开启垃圾回收器日志(即使在生产环境)是很好的主意,其实开启垃圾回收器的开销很小而且可以提供丰富的信息,这些信息和垃圾回收器应用事件或者JVM事件有关系,比如说:一个应用在运行过程中出现了一个比较长的暂停,如果有垃圾回收信息,就可以判断出是垃圾回收器引起的暂停还是应用进行的其他操作引起的暂停。

   有很多的HotSpot VM命令行选项可以用在垃圾回收的日志上面,下面列举几个推荐使用的命令行选项:

-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:<filename>

   -XX:+PrintGCTimeStamps打印出垃圾回收发生的时间是距离HotSpot VM启动时间的秒数。

  -XX:+PrintGCDetails提供了垃圾回收特有的统计信息而且具体信息依赖于使用的垃圾回收器类型。

  -Xloggc:<filename>表示垃圾回收器的信息输出到叫<filename>的文件。

   下面是通过使用-XX:+UseParallelOldGC或者-XX:+UseParallelGC选项来打印出来的垃圾回收信息,而且使用了前面列出的3个选项。

45.152: [GC [PSYoungGen: 295648K->32968K(306432K)] 296198K->33518K(1006848K), 0.1083183 secs]
[Times: user=1.83 sys=0.01, real=0.11 secs]

   45.152是表明距离JVM启动到垃圾回收的秒数,

   GC标签表明是Minor GC或者young代垃圾回收,

   [PSYoungGen: 295648K->32968K(306432K)]提供了young代的空间信息,PSYoungGen表示young代的垃圾回收是使用throughput垃圾回收器。其他可能的young代垃圾回收有ParNew(使用CMS垃圾回收器多线程回收young代空间)、DefNew(使用serial垃圾回收器单线程回收young代空间)。

   在“->”左边的数字(295648K)表示垃圾回收前young代的空间大小,右边数字(32968K)表示垃圾回收后的young代空间大小。young代被分为eden区域和survivor区域。由于在垃圾回收之后,eden区域是空的,右边的数字其实就是survivor区域的空间。在括号里面的数字(306432K)表示young代的总空间。

   296198K->33518K(1006848K)提供了Java堆在垃圾回收前后的使用情况。另外,他提供Java堆的总大小,是young代和old代的和。在->左边的数字(296198K)表示,在垃圾回收前Java堆占用的空间,->右边的数字(33518K)表示垃圾回收后Java堆占用空间。括号里面的数字(1006848K)表示Java堆总共的空间。

   通过young代的占用空间和Java堆占用的空间,可以快速的计算出old代占用的空间。比如:Java堆的大小是1006848K,young代的空间大小是306432K,因此可以计算出old代的空间大小是1006848K-306432K=700416K。在垃圾回收之前,296198K-295648K=550K是old代使用了的空间。在垃圾回收后33518K-32968K=550K。在这个例子中,在垃圾回收前后没有对象从young代移动到old代。这是一个重要的观察说明了Minor GC回收原则。如果有对象被移动到old代,然后变成不可读取的,就不是最大量的对象被回收,会违反Minor GC回收原则。

   0.1083183 secs表明垃圾回收执行的时间。

   [Times: user=1.83 sys=0.01, real=0.11 secs]提供了CPU和占用时间。user表明垃圾回收在用户模式下执行消耗的CPU时间,即:在JVM里面运行的时间,在这个例子中,垃圾回收器在用户模式下消耗1.83秒的CPU时间。sys表示操作系统代表垃圾回收器消耗的时间,在这里例子中,垃圾回收器使用0.01秒的操作系统CPU时间。real表示垃圾回收执行的时间的。这几个数字精确到0.01秒。

   如果你对垃圾回收的实际时间感兴趣,可以设置-XX:+PrintGCDateStamps选项。-XX:+PrintGCDateStamps显示垃圾回收发生的年,月,日和时间。这个选项是在Java 6 Update 4引入的。下面的例子是同时使用-XX:+PrintGCDateStamps和-XX:+PrintGCDetails选项的结果:

2012-06-21T09:57:10.518-0500: [GC[PSYoungGen: 295648K->32968K(306432K)]296198K->33518K(1006848K), 0.1083183 secs]
[Times: user=1.83 sys=0.01, real=0.11 secs] 

   2012-06-21T09:57:10.518-0500字段是使用了ISO 8601日期和时间戳。格式是YYYY-MM-DDTHH-MM-SS.mmm-TZ,分别的意思是:

    YYYY表示4位数的年

    MM表示2位数月,如果只有一位数,前面加0

    DD表示2位数的天,如果只有一位数,前面加0

    T是一个字符用来隔开日期和时间

    HH表示2位数小时,如果只有一位数,前面加0

    MM表示2位数分钟,如果只有一位数,前面加0

    SS表示2位数秒,如果只有一位数,前面加0

    mmm表示3位数毫秒,如果不足三位,前面加0或者00

    TZ表示格林尼治时间的时区

   尽管时区已经包含在输出里面了,但是输出日期和时间不是GMT时间,而是本地化过的时间。

   当为了低延迟而优化HotSpot VM的时候,下面的两个选项是非常有用的,这两个选项会报告应用由于虚拟机的安全点(Safepoint)操作而阻塞的时间以及应用程序在安全点(Safepoint)执行了多长的时间。

-XX:+PrintGCApplicationStoppedTime  

-XX:+PrintGCApplicationConcurrentTime

   安全点(Safepoint)操作会让JVM进入一种所有应用程序的线程都被阻塞以及阻止任何正在执行的本地程序把结果返回给Java代码的状态。当需要进行优化虚拟机内部操作的时候,安全点(Safepoint操作会被执行以使得所有线程都进入阻塞状态避免影响Java堆(垃圾回收是一种安全点(Safepoint操作)。

   由于安全点(Safepoint)操作阻塞了所有Java程序的执行,所以知道程序的响应时间延迟是否和安全点(Safepoint)操作有关系就显得非常重要了。因此,能够观察程序什么时候被阻塞( 通过设置-XX:+PrintGCApplicationStoppedTime选项)通过应用的日志信息能够帮组你识别出,当应用的响应时间超过预期的时候,是安全点(Safepoint)操作引起的还是应用或者操作系统的其他操作引起的。-XX:+PrintSafepointStatistics可以帮助区别垃圾回收的安全点(Safepoint)以及其他的安全点(Safepoint)。

   在发现应用的响应时间超过预期的预期的时候,-XX:+PrintGCApplicationConcurrentTime选项可以用来判断程序是否被执行以及执行了多长时间。

   下图总结了前面提到的垃圾回收器的选项以及给出了使用它们的合适情况。

<think>我们有两个主要问题:1)实验性VM选项未正确解锁2)java命令未找到针对第一个问题:根据错误信息,我们需要在实验性选项前添加-XX:+UnlockExperimentalVMOptions,并且确保该解锁参数在实验性选项之前出现。针对第二个问题:可能是java命令不在PATH环境变量中,或者用户没有安装Java。我们可以在脚本中指定Java的绝对路径,或者设置JAVA_HOME环境变量。参考引用:[^3]提到:实验性参数需要先通过-XX:+UnlockExperimentalVMOptions解锁。[^4]提到:建议将Xms和Xmx设置为相同的值,避免内存伸缩带来的损耗。解决方案:1.在start.sh脚本中,确保在实验性选项之前添加-XX:+UnlockExperimentalVMOptions。2.设置Xms和Xmx为相同的值(例如:-Xms12G-Xmx12G)。3.解决java命令未找到的问题:可以在脚本中设置JAVA_HOME环境变量,并将$JAVA_HOME/bin添加到PATH中,或者直接使用java的绝对路径。示例脚本(start.sh)如下:</think>### 解决 JVM 启动参数配置问题 #### 问题分析 根据错误信息和引用内容,需要解决两个核心问题: 1. **实验性 VM 选项未正确解锁** - `G1NewSizePercent` 是实验性参数,需前置 `-XX:+UnlockExperimentalVMOptions`(引用[3]) - 错误顺序导致 JVM 创建失败:`Error: The unlock option must precede 'G1NewSizePercent'` 2. **Java 命令未找到** - 可能原因:PATH 未配置或 Java 未安装 --- ### 解决方案 #### 1. 修复实验性参数配置 在 `start.sh` 脚本中调整参数顺序,确保: - 解锁参数 **必须** 在实验性参数之前 - `Xms` 和 `Xmx` 设置相同值避免内存抖动(引用[4]) ```bash{4-6} #!/bin/bash # 基础内存配置(Xms=Xmx) JVM_ARGS="-server -Xms12G -Xmx12G" # 先解锁实验性选项(关键!) JVM_ARGS+=" -XX:+UnlockExperimentalVMOptions" # 再配置G1实验参数 JVM_ARGS+=" -XX:+UseG1GC" JVM_ARGS+=" -XX:G1NewSizePercent=30" JVM_ARGS+=" -XX:G1MaxNewSizePercent=40" JVM_ARGS+=" -XX:MaxGCPauseMillis=150" # 其他优化参数 JVM_ARGS+=" -XX:+UseStringDeduplication" JVM_ARGS+=" -XX:+ParallelRefProcEnabled" echo "JVM参数: ${JVM_ARGS}" # 启动应用 java ${JVM_ARGS} -jar your_app.jar ``` **关键修改说明**: - 解锁参数与实验参数顺序关系: $$ \text{正确顺序} = \text{UnlockExperimentalVMOptions} \rightarrow \text{实验参数} $$ - 错误顺序将导致参数解析失败率 $\approx 100\%$ #### 2. 解决 "java命令未找到" 在脚本开头添加 Java 环境检测: ```bash{2-8} #!/bin/bash # 检测Java环境 if ! command -v java &> /dev/null; then echo "错误: Java未安装或PATH未配置" echo "解决方案:" echo " 1. 安装JDK: sudo apt install openjdk-17-jdk" echo " 2. 设置PATH: export PATH=\$PATH:/usr/lib/jvm/java-17-openjdk-amd64/bin/" exit 1 fi # 后续JVM参数配置... ``` **备选方案**(直接指定Java路径): ```bash # 使用绝对路径启动 /usr/lib/jvm/java-17-openjdk-amd64/bin/java ${JVM_ARGS} -jar app.jar ``` --- ### 完整脚本示例 ```bash #!/bin/bash # 1. Java环境检测 JAVA_CMD=$(command -v java) if [ -z "$JAVA_CMD" ]; then echo "错误: Java未找到! 尝试使用绝对路径..." JAVA_CMD="/usr/lib/jvm/java-17-openjdk-amd64/bin/java" [ -x "$JAVA_CMD" ] || { echo "Java安装失败"; exit 1; } fi # 2. JVM参数配置(含正确解锁顺序) JVM_ARGS=( "-server" "-Xms12G -Xmx12G" # 等值内存设置[^4] "-XX:+UnlockExperimentalVMOptions" # 先解锁[^3] "-XX:+UseG1GC" # G1收集器 "-XX:G1NewSizePercent=30" # 实验参数 "-XX:G1MaxNewSizePercent=40" # 实验参数 "-XX:MaxGCPauseMillis=150" # 停顿目标 "-XX:+UseStringDeduplication" # 字符串去重 ) # 3. 启动应用 echo "启动命令: $JAVA_CMD ${JVM_ARGS[*]} -jar server.jar" $JAVA_CMD "${JVM_ARGS[@]}" -jar server.jar ``` --- ### 验证方法 1. **检查参数生效** ```bash # 查看JVM参数 jcmd <PID> VM.flags | grep -E "G1NewSize|Unlock" ``` 预期输出: ``` -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=30 ``` 2. **监控GC性能** ```bash # 实时GC统计(每5秒) jstat -gc <PID> 5000 ``` 关键指标: - YGC(年轻代GC次数)应 $\lt 1 \text{次/分钟}$ - FGC(Full GC次数)应 $\approx 0$ --- ### 注意事项 1. **Java版本兼容性** - G1 实验参数需 Java 8u40+ 或 Java 11+ - 推荐使用 LTS 版本:`sudo apt install openjdk-17-jdk` 2. **参数优化建议** - 初始堆占用率:`-XX:InitiatingHeapOccupancyPercent=35`(引用[3]) - 开启逃逸分析:`-XX:+DoEscapeAnalysis`(引用[5]) 3. **动态参数调整** 运行时可通过 jinfo 修改管理参数(引用[1]): ```bash jinfo -flag +PrintGC <PID> # 动态开启GC日志 ``` > **性能公式参考**: > 最大停顿时间与新生代比例关系: > $$ > \text{MaxPause} \propto \frac{1}{\text{G1NewSizePercent}} > $$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值