当前位置: 首页 > news >正文

【JVM详解五】JVM性能调优

示例:

配置JVM参数运行
#前台运行
java -XX:MetaspaceSize-128m -XX:MaxMetaspaceSize-128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8 -
XX:+UseConcMarkSweepGC -jar /jar包路径
#后台运行
nohup java -XX:MetaspaceSize-128m -XX:MaxMetaspaceSize-128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio=8
-XX:+UseConcMarkSweepGC -jar /jar包路径参数说明:
-XX:MetaspaceSize=128m (元空间默认大小)
-XX:MaxMetaspaceSize=128m (元空间最大大小)
-Xms1024m (堆最大大小)
-Xmx1024m (堆默认大小)
-Xmn256m (新生代大小)
-Xss256k (棧最大深度大小)
-XX:SurvivorRatio=8 (新生代分区比例 8:2)
-XX:+UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器)

一、JVM性能监控与故障处理工具

1.1 jps:虚拟机进程状况工具

虚拟机进程状况工具;jps主要用来输出JVM中运行的进程状态信息。语法格式如下:

jps [options] [hostid]

  • 第一个参数 options :-q 不输出类名、Jar名和传入main方法的参数;-m 输出传入main方法的参数;-l 输出main类或 Jar的全限名;-v 输出传入JVM的参数。
  • 第二个参数 hostid :主机或者是服务器的id,如果不指定,就默认为当前的主机或者是服务器。
最前方的数字就是LVMID,与下面要介绍的监控工具所需的VMID的区别为:
  • 如果是本地虚拟机进程,VMID和LVMID是一致的。
  • 如果是远程虚拟机进程,那么vmid的格式应当是 :
[protocol:][//]lvmid[@hostname[:port]/servername]
eg:rmi://39939@192.168.2.37:12345

1.2 jstat:虚拟机统计信息监视工具

虚拟机统计信息监控工具;jstat监视虚拟机各种运行状态信息,可以显示本地或者是远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。语法格式如下:

jstat [ option vmid [interval[s|ms] [count]] ]

  • 第一个参数 option:代表着用户希望查询的虚拟机信息,分为类加载、垃圾收集、运行期编译状况3类。
  • 参数interval和count代表查询间隔和次数,如果省略这两个参数,说明只查询一次。

假设需要每1000毫秒查询一次进程9344垃圾收集状况,一共查询2次,那命令应当是:

jstat -gcutil 9344 1000 2

  • S0代表幸存者0区;
  • S1代表幸存者1区;
  • E代表伊甸园区;
  • O代表老年代;
  • M代表方法区;
  • CCS代表压缩类,以上这些值都是占比情况;
  • YGC代表年轻代垃圾回收的次数;
  • YGCT年轻代进行垃圾回收需要的时间;
  • FGC代表代表Full GC的次数;
  • FGCT代表Full GC的时间;
  • GCT代表垃圾回收的总时间。
如果是远程虚拟机进程,那么vmid的格式应当是
  • [protocol:][//]lvmid[@hostname[:port]/servername]
  • 需要远程主机提供RMI支持,sun提供的jstatd可以很方便的建立远程RMI服务器。
eg:rmi://39939@192.168.2.37:12345

1.3 jstack:java虚拟机栈跟踪工具

堆栈跟踪工具;jstack用于生成虚拟机当前时刻的线程快照(threaddump或javacore文件)。线程快照就是当前虚拟机内每一条线程正在执行的方法栈(看java虚拟机栈定义)的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等等。语法格式如下:

jstack [option] vmid

  • 第一个参数:option

  • 第二个参数:vmid

vmid是Java虚拟机ID,在Linux/Unix系统上一般就是进程ID,可通过jps命令获得。

1.4 jinfo:java配置信息工具

实时地查看和调整虚拟机各项参数;命令格式:

jinfo [option] pid

  • 第一个参数:option
  • 第二个参数:pid 指定显示的进程id

注意:

1)、如果运行过程中,通过jinfo修改了,则修改后的值只能通过jinfo看到,jps是看不到的,jps命令只能看到启动时的jvm参数。

2)、很多运行参数是不能调整的,如果出现这种异常,说明不能调整:

1.5 jmap:java内存映像工具

生成堆转储快照(heapdump文件);jmap(Memory Map for Java,内存映像工具),用于生成堆转存的快照,一般是heapdump或者dump文件。如果不适用jmap命令,可以使用-XX:+HeapDumpOnOutOfMemoryError参数,当虚拟机发生内存溢出的时候可以产生快照。或者使用kill -3 pid也可以产生。jmap的作用并不仅仅是为了获取dump文件,它可以查询finalize执行队列,java堆和永久代的详细信息,如空间使用率,当前用的哪种收集器。命令格式如下:

jmap [option] vmid

  • 第一个参数:option
  • 第二个参数 vmid:可通过jps获得
//dump 出堆转储文件 jmap -dump:format=b,file=heapdump.phrof pid

1.6 jhat:jvm堆转储快照分析工具

分析内存转储快照,不推荐使用,而且慢。由于这个工具功能比较简陋,运行起来也比较耗时,所以这个工具不推荐使用,推荐使用MAT。

1.7 JConsole:java监视与管理控制台

基于JMX的可视化管理工具;这个工具相比较前面几个工具,使用率比较高,很重要。它是一个java GUI监视工具,可以以图表化的形式显示各种数据。并可通过远程连接监视远程的服务器VM。用java写的GUI程序,用来监控VM,并可监控远程的VM,非常易用,而且功能非常强。线程监控还可以检查死锁。

1.7.1 启动JConsole

命令行输入 jconsole,然后从下图选择进程(前提是在IDE工具先建立一个线程运行着)

然后我们选择了相应的选项之后,进入这个工具就会出现下面这个界面:

1.8 VisualVM:多合一故障处理工具

概述:
  • Visual VM是一个功能强大的多合一故障诊断和性能监控的可视化工具,基于图形化界面。
  • 它集成了多个JDK命令行工具,使用Visual VM可用于显示虚拟机进程及进程的配置和环境信息(jps,jinfo),监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)等,甚至代替JConsole。
  • 在JDK 6 Update 7以后,Visual VM便作为JDK的一部分发布(VisualVM 在JDK/bin目录下),即:它完全免费。
  • 此外,Visual VM也可以作为独立的软件安装。
  • 直接在命令行打入jvisualvm即可启动。
  • 有一系列功能扩展插件:如visual gc 插件,可查看gc情况
基本功能:
  • 生成/读取内存快照;
  • 查看JVM参数和系统属性;
  • 查看运行中的虚拟机进程;
  • 生成/读取线程快照;
  • 程序资源的实时监控;
  • 其他功能:
    • JMX代理连接
    • 远程环境监控
    • CPU分析和内存分析

1.9 Arthas:aliyun开源工具

前面说过的jvisualvm等要进行远程连接需要在服务端项目开启时配置很多参数,而且如果线上环境是隔离的,那么本地工具是连接不到线上环境的。

Arthas是阿里开源的性能监控神器。阿尔萨斯在远程连接时不需要进行复杂的参数配置,可以在线排除问题,无需重启应用,可以动态跟踪java代码,实时监控JVM状态。

Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load(负载)、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。

开发文档:Arthas Install | arthas

1.9.1 Arthas Install

下载arthas-boot.jar,然后用java -jar的方式启动:
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar

1.9.2 通过浏览器连接 arthas

Arthas 目前支持 Web Console,用户在 attach 成功之后,可以直接访问:http://127.0.0.1:8563/。

可以填入 IP,远程连接其它机器上的 arthas。

Web Console在线教程

1.9.3 使用arthas定位问题

Arthas - Java 线上问题定位处理的终极利器-腾讯云开发者社区-腾讯云

1.9.4 退出 arthas

如果只是退出当前的连接,可以用quit或者exit命令。Attach 到目标进程上的 arthas 还会继续运行,端口会保持开放,下次连接时可以直接连接上。

如果想完全退出 arthas,可以执行stop命令。

启动arthas之后, 选择要attach的进程之后控制台报错内容如下:

上一次没有通过stop完全退出arthas,然后再次attach与上一次不同的进程之后就会出现这个错误。

1.10 GCViewer 日志分析工具

GCViewer 是一个可以将 JVM 中的 gc log 可视化的工具,使用该工具可以帮助你充分的发现 JVM 垃圾回收中的潜在问题,让你可以更加准确的做出关于 JVM GC 优化的决策。
借助 GCViewer 日志分析工具,可以非常直观地分析出待调优点。可从以下几方面来分析:
  • Memory:分析Totalheap、Tenuredheap、Youngheap内存占用率及其他指标,理论上内存占用率越小越好;
  • Pause:分析Gc pause、Fullgc pause、Total pause三个大项中各指标,理论上GC次数越少越好,GC时长越小越好;
安装并启动:
git clone https://github.com/chewiebug/GCViewer.git
//或者用 IDEA打开项目后,用 maven进行打包
mvn clean pacakge
//得到一个 jar包
cd targetjava -jar gcviewer-1.36.jar
首先可以修改 JVM 参数如下:
-Xms60M -Xmx60M -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/Users/xxx/Documents/logs/gc.log
GCViewer 启动后,打开我们获得的 gc log 文件(这里打开是本地程序导出的 log文件),看到类似这样一个界面:
GCViewer 有两个部分,其中显示了日志分析的结果。一个是左侧的图表,另一个是右侧的数据面板。

1)图表

GCViewer 在图表(第一个选项卡)中显示多条线等,可以点击 View 按钮选择显示哪些线条,具体每个曲线的地方代表什么意思官网都讲的很清楚了。https://github.com/chewiebug/GCViewer GitHub - chewiebug/GCViewer: Fork of tagtraum industries' GCViewer. Tagtraum stopped development in 2008, I aim to improve support for Sun's / Oracle's java 1.6+ garbage collector logs (including G1 collector)

其中黑色的竖线表示 Full GC,这样的线越少越好。就像上图所示,Full GC 后老年代并没有释放多少空间,所以才会抛出 OOM 异常。

图表需要参照 这个view中的信息,其中包含full gc等相关信息,这个在本地开发的时候可以时不时拉出来看下是不是有问题,性能或者一些关键的参数都可以在图标上面一目了然:

2)数据面板

点击 Summary

这里只挑几个重要的参数进行介绍:
  • Total heap(usage/alloc.max):占用堆的总大小及使用占比
  • Max heap after full GC:Full GC 后的堆内存大小
  • Freed memory :释放内存
  • Total Time:GC 总耗时
  • Accumulated Pauses:GC 过程中暂停总时长
  • Throughput(吞吐量):上图显示是 86.8%,有些系统要求吞吐量至少为 90%,那么就说明需要进行 GC 优化。
点击 Pause:
  • Accumulated Pauses:GC 过程中暂停总时长
  • Number of Pauses:暂停总次数
  • Avg Pause:平均暂停时间
  • Avg pause interval:平均暂停的间隔时间
  • Full gc Pauses :full gc 暂停信息
  • Gc pauses :gc暂停信息

如果你的 Full GC 平均的暂停时间很长(大于1s),或者平均暂停间隔非常的短(小于10s),说明可能你的 GC 回收是有问题的,可能需要优化。

二、GC日志获取

JVM的GC日志的主要参数包括如下几个:
  • -XX:+PrintGC :输出GC日志。
  • -XX:+PrintGCDetails :输出GC的详细日志。
  • -XX:+PrintGCTimeStamps :输出GC的时间戳(以基准时间的形式)。
  • -XX:+PrintGCDateStamps :输出GC的时间戳(以日期的形式,如 2017-09-04T21:53:59.234+0800)。
  • -XX:+PrintHeapAtGC :在进行GC的前后打印出堆的信息。
  • -XX:+PrintGCApplicationStoppedTime :GC造成的应用暂停的时间
  • -Xloggc:../logs/gc.log :日志文件的输出路径。

在生产环境中,根据需要配置相应的参数来监控JVM运行情况。

三、gc日志分析

JVM和GC调优,总结下CMS的一些特点和要点,让我们先简单的看下整个堆年轻代和年老代的垃圾收集器组合(以下配合java8完美支持,其他版本可能稍有不同),其中标红线的则是我们要着重讲的内容:

3.1 ParNew and CMS

"Concurrent Mark and Sweep" 是CMS的全称,官方给予的名称是:“Mostly Concurrent Mark and Sweep Garbage Collector”;

年轻代:采用 stop-the-world mark-copy 算法(ParNew);

年老代:采用 Mostly Concurrent mark-sweep 算法(CMS);

设计目标:年老代收集的时候避免长时间的暂停;

能够达成该目标主要因为以下两个原因:
  • 它不会花时间整理压缩年老代,而是维护了一个叫做 free-lists 的数据结构,该数据结构用来管理那些回收再利用的内存空间。
  • mark-sweep分为多个阶段,其中一大部分阶段GC的工作是和Application threads的工作同时进行的(当然,gc线程会和用户线程竞争CPU的时间),默认的GC的工作线程为你服务器物理CPU核数的1/4。
日志:

3.1.1 Minor GC

我们来分析下对象晋升问题(原文中的计算方式有问题):

开始的时候:整个堆的大小是 10885349K,年轻代大小是613404K,这说明老年代大小是 10885349-613404=10271945K,

收集完成之后:整个堆的大小是 10880154K,年轻代大小是68068K,这说明老年代大小是 10880154-68068=10812086K,

老年代的大小增加了:10812086-10271945=608209K,也就是说 年轻代到年老代promot了608209K的数据;

3.1.2 Full/Major GC

Phase 1: Initial Mark(初始标记)

这是CMS中两次stop-the-world事件中的一次。它有两个目标:一是标记老年代中所有的GC Roots;二是标记被年轻代中活着的对象引用的对象。

标记结果如下:

分析:
  • 016-08-23T11:23:07.321-0200: 64.42 – GC事件开始,包括时钟时间和相对JVM启动时候的相对时间,下边所有的阶段改时间的含义相同;
  • CMS Initial Mark – 收集阶段,开始收集所有的GC Roots和直接引用到的对象;
  • 10812086K – 当前老年代使用情况;
  • (11901376K) – 老年代可用容量;
  • 10887844K – 当前整个堆的使用情况;
  • (12514816K) – 整个堆的容量;
  • 0.0001997 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] – 时间计量;

Phase 2: Concurrent Mark(并发标记)

这个阶段会遍历整个老年代并且标记所有存活的对象,从“初始化标记”阶段找到的GC Roots开始。并发标记的特点是和应用程序线程同时运行。并不是老年代的所有存活对象都会被标记,因为标记的同时应用程序会改变一些对象的引用等。

在上边的图中,一个引用的箭头已经远离了当前对象(current obj)。

分析:
  • CMS-concurrent-mark – 并发收集阶段,这个阶段会遍历整个年老代并且标记活着的对象;
  • 035/0.035 secs – 展示该阶段持续的时间和时钟时间;
  • [Times: user=0.07 sys=0.00, real=0.03 secs] – 同上。

四、性能调优

4.1 性能调优的层次

进行jvm调优之前,我们假设项目的架构调优和代码调优已经进行过或者是针对当前项目是最优的。这两个是jvm调优的基础,并且架构调优是对系统影响最大的 ,我们不能指望一个系统架构有缺陷或者代码层次优化没有穷尽的应用,通过jvm调优令其达到一个质的飞跃,这是不可能的。其实最有效的优化手段是架构和代码层面的优化,而JVM优化则是最后不得已的手段,也可以说是对服务器配置的最后一次“压榨”。

另外,在调优之前,必须得有明确的性能优化目标, 然后找到其性能瓶颈。之后针对瓶颈的优化,还需要对应用进行压力和基准测试,通过各种监控和统计工具,确认调优后的应用是否已经达到相关目标。

4.1.1 JVM调优前提

遇到以下情况,就需要考虑进行JVM调优了:
  • Heap内存(老年代)持续上涨达到设置的最大内存值;
  • Full GC 次数频繁;
  • GC 停顿时间过长(超过1秒);
  • 应用出现OutOfMemory 等内存异常;
  • 应用中有使用本地缓存且占用大量内存空间;
  • 系统吞吐量与响应性能不高或下降。

4.2 JVM调优流程(VisualVM+gc插件 / gcviewer日志分析工具)

调优的最终目的都是为了令应用程序使用最小的硬件消耗来承载更大的吞吐。jvm的调优也不例外,jvm调优主要是针对垃圾收集器的收集性能优化,令运行在虚拟机上的应用能够使用更少的内存以及延迟获取更大的吞吐量。当然这里的最少是最优的选择,而不是越少越好。

4.2.1 性能定义

要查找和评估器性能瓶颈,首先要知道性能定义,对于jvm调优来说,我们需要知道以下三个定义属性,依作为评估基础:

这三个属性中,其中一个任何一个属性性能的提高,几乎都是以另外一个或者两个属性性能的损失作代价,不可兼得,具体某一个属性或者两个属性的性能对应用来说比较重要,要基于应用的业务需求来确定。

4.2.2 性能调优原则

在调优过程中,我们应该谨记以下3个原则,以便帮助我们更轻松的完成垃圾收集的调优,从而达到应用程序的性能要求。

4.2.3 性能调优流程

以上就是对应用程序进行jvm调优的基本流程,我们可以看到,jvm调优是根据性能测试结果不断优化配置而多次迭代的过程。在达到每一个系统需求指标之前,之前的每个步骤都有可能经历多次迭代。有时候为了达到某一方面的指标,有可能需要对之前的参数进行多次调整,进而需要把之前的所有步骤重新测试一遍。

另外调优一般是从满足程序的内存使用需求开始的,之后是时间延迟的要求,最后才是吞吐量的要求,要基于这个步骤来不断优化,每一个步骤都是进行下一步的基础,不可逆行之。以下我们针对每个步骤进行详细的示例讲解。

在JVM的运行模式方面,我们直接选择server模式,这也是jdk1.6以后官方推荐的模式。

在垃圾收集器方面,我们直接采用了jdk1.6-1.8 中默认的parallel收集器(新生代采用parallelGC,老生代采用parallelOldGC)。

4.2.3.1 确定内存占用
在确定内存占用之前,我们需要知道两个知识点:
  • 应用程序的运行阶段
  • jvm内存分配

1)运行阶段

应用程序的运行阶段,我可以划分为以下三个阶段:

确定内存占用以及活跃数据的大小,我们应该是在程序的稳定阶段来进行确定,而不是在项目起初阶段来进行确定,如何确定,我们先看以下jvm的内存分配。

2)jvm内存分配&参数

jvm堆中主要的空间,就是以上新生代、老生代、永久代组成,整个堆大小=新生代大小 + 老生代大小 + 永久代大小。 具体的对象提升方式,这里不再过多介绍了,我们看下一些jvm命令参数,对堆大小的指定。如果不采用以下参数进行指定的话,虚拟机会自动选择合适的值,同时也会基于系统的开销自动调整。

在设置的时候,如果关注性能开销的话,应尽量把永久代的初始值与最大值设置为同一值,因为永久代的大小调整需要进行FullGC 才能实现。

3)计算活跃数据大小

如前所述,活跃数据应该是基于应用程序稳定阶段时,观察长期存活与对象在java堆中占用的空间大小。

计算活跃数据时应该确保以下条件发生:
  • 测试时,启动参数采用jvm默认参数,不人为设置。
  • 确保Full GC 发生时,应用程序正处于稳定阶段。

采用jvm默认参数启动,是为了观察应用程序在稳定阶段的所需要的内存使用。

如何才算稳定阶段?

一定得需要产生足够的压力,找到应用程序和生产环境高峰符合状态类似的负荷,在此之后达到峰值之后,保持一个稳定的状态,才算是一个稳定阶段。所以要达到稳定阶段,压力测试是必不可少的。

在确定了应用出于稳定阶段的时候,要注意观察应用的GC日志,特别是Full GC 日志。

-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -Xloggc:/Users/wangjiafu/logs/gc.log

必须得有FullGC 日志,如果没有的话,可以采用监控工具强制调用一次,或者采用以下命令,亦可以触发:jmap -histo:live pid

在稳定阶段触发了FullGC我们一般会拿到如下 GC 日志信息:

10.080: 2[GC 3(Allocation Failure) 0.080: 4[DefNew: 56815K->280K(9216K),6 0.0043690 secs] 76815K->6424K(19456K), 80.0044111 secs]9 [Times: user=0.00 sys=0.01, real=0.01 secs]

以上是发生 Minor GC 的 GC 是日志,如果发生 Full GC 呢,格式如下:

10.088: 2[Full GC 3(Allocation Failure) 0.088: 4[Tenured: 50K->210K(10240K), 60.0009420 secs] 74603K->210K(19456K), [Metaspace: 2630K->2630K(1056768K)], 80.0009700 secs]9 [Times: user=0.01 sys=0.00, real=0.02 secs]
两者格式其实差不多,一起来看看,主要以本例触发的 Minor GC 来讲解, 以上日志中标的每一个数字与以下序号一一对应:
  1. 开头的 0.080,0.088 代表了 GC 发生的时间,这个数字的含义是从 Java 虚拟机启动以来经过的秒数;
  2. [GC  或者 [Full GC 说明了这次垃圾收集的停顿类型,注意不是用来区分新生代 GC 还是老年化 GC 的,如果有 Full,说明这次 GC 是发生了 Stop The World 的,如果是调用 System.gc() 所触发的收集,这里会显示 [Full GC(System);
  3. 之后的 Allocation Failure 代表了触发 GC 的原因,在这个程序中我们设置了新生代的大小为 10M(-Xmn10M),Eden:S0:S1 = 8:1:1(-XX:SurvivorRatio=8),也就是说 Eden 区占了 8M, 当分配 allocation4 时,由于将要分配的总大小为 10M,超过了 Eden 区,所以此时会发生 GC;
  4. 接下来的 [DefNew[Tenured[Metaspace 表示 GC 发生的区域,这里显示的区域名与使用的 GC 收集器是密切相关的,在此例中由于新生代我们使用了 Serial 收集器,此收集器新生代名为「Default New Generation」,所以显示的是 [DefNew,如果是 ParNew 收集器,新生代名称就会变为 [ParNew`,意为 「Parallel New Generation」,如果采用 「Parallel Scavenge」收集器,则配套的新生代名称为「PSYoungGen」,老年代与新生代一样,名称也是由收集器决定的;
  5. 再往后 6815K->280K(9216K) 表示 「GC 前该内存区域已使用容量 -> GC 后该内存区域已使用容量(该内存区域总容量)」;
  6. 0.0043690 secs 表示该块内存区域 GC 所占用的时间,单位是秒;
  7. 6815K->6424K(19456K) 表示「GC 前 Java 堆已使用容量 -> GC 后 Java 堆已使用容易(java 堆总容量)」。
  8. 0.0044111 secs 表示整个 GC 执行时间,注意和 6 中 0.0043690 secs 的区别,后者专指相关区域所花的 GC 时间,而前者指的 GC 的整体堆内存变化所花时间(新生代与老生代的的内存整理),所以前者是肯定大于后者的!
  9. 最后一个 [Times: user=0.01 sys=0.00, real=0.02 secs]  这里的 user, sys 和 real 与Linux 的 time 命令所输出的时间一致,分别代表用户态消耗的 CPU 时间,内核态消耗的 CPU 时间,和操作从开始到结束所经过的墙钟时间,墙钟时间包括各种非运算的等待耗时,例如等待磁盘 I/O,等待线程阻塞,而 CPU 时间不包括这些耗时,但当系统有多 CPU 或者多核的话,多线程操作会叠加这些 CPU 时间,所以 user 或 sys 时间是可能超过 real 时间的。

知道了 GC 日志怎么看,我们就可以根据 GC 日志有效定位问题了,如我们发现 Full GC 发生时间过长,则结合我们上文应用中打印的 OOM 日志可能可以快速定位到问题。

从以上gc日志中,我们大概可以分析到,在发生fullGC之时,整个应用的堆占用以及GC时间,当然了,为了更加精确,应该多收集几次,获取一个平均值。或者是采用耗时最长的一次FullGC来进行估算。在上图中,fullGC之后,老年代空间占用在93168kb(约93MB),我们以此定为老年代空间的活跃数据。

其他堆空间的分配,基于以下规则来进行:

基于以上规则和上图中的FullGC信息,我们现在可以规划的该应用堆空间为:
  • java 堆空间: 373Mb (=老年代空间93168kb*4)
  • 新生代空间:140Mb(=老年代空间93168kb*1.5)
  • 永久代空间:5Mb(=永久代空间3135kb*1.5)
  • 老年代空间: 233Mb=堆空间-新生代看空间=373Mb-140Mb
对应的应用启动参数应该为:
java -Xms373m -Xmx373m -Xmn140m -XX:PermSize=5m -XX:MaxPermSize=5m
4.2.3.2 延时调优

在确定了应用程序的活跃数据大小之后,我们需要再进行延迟性调优,因为对于此时堆内存大小,延迟性需求无法达到应用的需要,需要基于应用的情况来进行调试。

在这一步进行期间,我们可能会再次优化堆大小的配置,评估GC的持续时间和频率、以及是否需要切换到不同的垃圾收集器上。

1)系统延时需求

在调优之前,我们需要知道系统的延迟需求是那些,以及对应的延迟可调优指标是那些。

以上中,平均停滞时间和最大停顿时间,对用户体验最为重要,可以多关注。

基于以上的要求,我们需要统计以下数据:

2)优化新生代的大小

比如如上的gc日志中,我们可以看到Minor GC的平均持续时间=0.069秒,MinorGC 的频率为(3.113-0.388)/7=0.389秒一次。

为了降低改变新生代的大小对其他区域的最小影响。在改变新生代空间大小的时候,尽量保持老年代空间的大小。比如此次减少了新生代空间10%的大小,应该保持老年代和永久代的大小不变化,第一步调优后的参数如下变化:

3)优化老年代的大小

同上一步一样,在优化之前,也需要采集gc日志的数据。此次我们关注的是FullGC的持续时间和频率。

上图中,我们可以看到:

没有FullGC日志时,可以通过 对象提升率 计算FullGC的持续时间和频率:比如上述中启动参数中,我们的老年代大小=233Mb。那么需要多久才能填满老年代中这233Mb的空闲空间取决于新生代到老年代的提升率。

每次提升老年代占用量=每次MinorGC 之后 java堆占用情况 减去 MinorGC后新生代的空间占用

对象提升率=平均值(每次提升老年代占用量) / 老年代空间

有了对象提升率,我们就可以算出填充满老年代空间需要多少次minorGC,大概一次fullGC的时间就可以计算出来了。

比如:

上图中:
  • 第一次minor GC 之后,老年代空间:13740kb - 13732kb =8kb
  • 第二次minor GC 之后,老年代空间:22394kb - 17905kb =4489kb
  • 第三次minor GC 之后,老年代空间:34739kb - 17917kb =16822kb
  • 第四次minor GC 之后,老年代空间:48143kb - 17913kb =30230kb
  • 第五次minor GC 之后,老年代空间:62112kb - 17917kb =44195kb
老年代每次minorGC提升率:
  • 4481kb 第二次和第一次minorGC之间
  • 12333kb 第3次和第2次minorGC之间
  • 13408kb 第4次和第3次minorGC之间
  • 13965kb 第5次和第4次minorGC之间
我们可以测算出:
  • 每次minorGC 的平均提升为12211kb,约为12Mb
  • 上图中,平均minorGC的频率为 213ms/次
  • 提升率=12211kb/213ms=57kb/ms
  • 老年代空间233Mb ,占满大概需要233*1024/57=4185ms 约为4.185s。

FullGC的预期最差频率时长可以通过以上两种方式估算出来,可以调整老年代的大小来调整FullGC的频率,当然了,如果FullGC持续时间过长,无法达到应用程序的最差延迟要求,就需要切换垃圾处理器了。具体如何切换,下篇再讲,比如切换为CMS,针对CMS的调优方式又有会细微的差别。

4.2.3.3 吞吐量调优

吞吐量=运行用户代码时间 / (运行用户代码时间+垃圾收集时间);虚拟机总共运行100分钟,其中gc花掉1分钟,则吞吐量就是 99% 。

经过上述漫长 调优过程,最终来到了调优的最后一步,这一步对上述的结果进行吞吐量测试,并进行微调。

吞吐量调优主要是基于应用程序的吞吐量要求而来的,应用程序应该有一个综合的吞吐指标,这个指标基于真个应用的需求和测试而衍生出来的。当有应用程序的吞吐量达到或者超过预期的吞吐目标,整个调优过程就可以圆满结束了。

如果出现调优后依然无法达到应用程序的吞吐目标,需要重新回顾吞吐要求,评估当前吞吐量和目标差距是否巨大,如果在20%左右,可以修改参数,加大内存,再次从头调试,如果巨大就需要从整个应用层面来考虑,设计以及目标是否一致了,重新评估吞吐目标。

对于垃圾收集器来说,提升吞吐量的性能调优的目标就是就是尽可能避免或者很少发生FullGC 或者Stop-The-World压缩式垃圾收集(CMS),因为这两种方式都会造成应用程序吞吐降低。尽量在MinorGC 阶段回收更多的对象,避免对象提升过快到老年代

五、JVM常用设置参数

5.1 栈、堆设置的常用参数

-Xss128k:设置每个线程的堆栈大小为128k;若不设置则为JVM默认值,此时默认可动态扩展。在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。
-Xms :初始堆大小
-Xmx :最大堆大小,当Xms和Xmx设置相同时,堆就无法进行自动扩展。
-XX:NewSize=n :设置年轻代大小
-XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio :配置的是在新生代里面Eden和一个Survivor比例。
-XX:SurvivorRatio=8表示新生代的Eden占8/10,S1和S2各占1/10.
-XX:MaxPermSize=n :设置持久代大小
-XX:MaxTenuringThreshold 用来定义年龄的阈值,达到阈值进入年老代。默认15。
-XX:PretenureSizeThreshold 定义对象大小的阈值,意思是对象大小超过这个值的时候,对直接在old区分配内存。(大对象直接在老年代分配)
-XX:HandlePromotionFailure 设置值是否允许担保失败。jdk1.6后失效。

5.2 收集器设置

-XX:+UseSerialGC :设置串行收集器
-XX:+UseParallelGC :设置并行收集器
-XX:+UseParalledlOldGC :设置并行年老代收集器
-XX:+UseConcMarkSweepGC :设置并发收集器

5.3 垃圾回收统计信息

-XX:+PrintHeapAtGC GC的heap详情
-XX:+PrintGC GC信息
-XX:+PrintGCDetails GC详情
-XX:+PrintGCTimeStamps 打印GC时间信息
-XX:+PrintTenuringDistribution 打印年龄信息等
-XX:+HandlePromotionFailure 老年代分配担保(true or false)

5.4 并行收集器设置

-XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间
-XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)

5.5 并发收集器设置

-XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

相关文章:

【JVM详解五】JVM性能调优

示例: 配置JVM参数运行 #前台运行 java -XX:MetaspaceSize-128m -XX:MaxMetaspaceSize-128m -Xms1024m -Xmx1024m -Xmn256m -Xss256k -XX:SurvivorRatio8 - XX:UseConcMarkSweepGC -jar /jar包路径 #后台运行 nohup java -XX:MetaspaceSize-128m -XX:MaxMetaspaceS…...

2.10日学习总结

题目一&#xff1a; AC代码 #include <stdio.h>#define N 1000000typedef long long l;int main() {int n, m;l s 0;l a[N 1], b[N 1];int i 1, j 1;scanf("%d %d", &n, &m);for (int k 1; k < n; k) {scanf("%lld", &a[k]);…...

疯狂前端面试题(四)

一、Ajax、JSONP、JSON、Fetch 和 Axios 技术详解 1. Ajax&#xff08;异步 JavaScript 和 XML&#xff09; 什么是 Ajax&#xff1f; Ajax 是一种用于在不刷新页面的情况下与服务器进行数据交互的技术。它通过 XMLHttpRequest 对象实现。 优点 - 支持同步和异步请求。 - 能…...

YOLOv11-ultralytics-8.3.67部分代码阅读笔记-metrics.py

metrics.py ultralytics\utils\metrics.py 目录 metrics.py 1.所需的库和模块 2.def bbox_ioa(box1, box2, iouFalse, eps1e-7): 3.def box_iou(box1, box2, eps1e-7): 4.def bbox_iou(box1, box2, xywhTrue, GIoUFalse, DIoUFalse, CIoUFalse, eps1e-7): 5.def mas…...

SuperCopy解除网页禁用复制功能插件安装和使用

点击下载《SuperCopy解除网页禁用复制功能插件》 1. 前言 在当今数字化时代&#xff0c;网络已成为我们获取信息和知识的主要渠道。互联网如同一片浩瀚无垠的知识海洋&#xff0c;蕴藏着无数的资源&#xff0c;从学术论文到生活小窍门&#xff0c;从专业教程到娱乐资讯&#…...

UP-VLA:具身智体的统一理解与预测模型

25年1月来自清华大学和上海姚期智研究院的论文“UP-VLA: A Unified Understanding and Prediction Model for Embodied Agent”。 视觉-语言-动作 (VLA) 模型的最新进展&#xff0c;利用预训练的视觉语言模型 (VLM) 来提高泛化能力。VLM 通常经过视觉语言理解任务的预训练&…...

Unity 基于状态机的逻辑控制详解

状态机是游戏开发中常用的逻辑控制方法&#xff0c;它可以将复杂的逻辑分解成多个独立的状态&#xff0c;并通过状态转移来控制逻辑的执行流程。本文将详细介绍如何在 Unity 中基于状态机实现逻辑控制&#xff0c;并提供技术详解和代码实现。 一、状态机简介 1.1 基本概念 状…...

傅里叶单像素成像技术研究进展

摘要&#xff1a;计算光学成像&#xff0c;通过光学系统和信号处理的有机结合与联合优化实现特定成像特性的成像系统&#xff0c;摆脱了传统成像系统的限制&#xff0c;为光学成像技术添加了浓墨重彩的一笔&#xff0c;并逐步向简单化与智能化的方向发展。单像素成像(Single-Pi…...

IDEA接入DeepSeek

IDEA 目前有多个途径可以接入deepseek&#xff0c;比如CodeGPT或者Continue&#xff0c;这里借助CodeGPT插件接入&#xff0c;CodeGPT目前用的人最多&#xff0c;相对更稳定 一、安装 1.安装CodeGPT idea插件市场找到CodeGPT并安装 2.创建API Key 进入deepseek官网&#xf…...

前端如何判断浏览器 AdBlock/AdBlock Plus(最新版)广告屏蔽插件已开启拦截

2个月前AdBlock/AdBlock Plus疑似升级了一次 因为自己主要负责面对海外的用户项目&#xff0c;发现以前的检测AdBlock/AdBlock Plus开启状态方法已失效了&#xff0c;于是专门研究了一下。并尝试了很多方法。 已失效的老方法 // 定义一个检测 AdBlock 的函数 function chec…...

macOS 上部署 RAGFlow

在 macOS 上从源码部署 RAGFlow-0.14.1&#xff1a;详细指南 一、引言 RAGFlow 作为一款强大的工具&#xff0c;在人工智能领域应用广泛。本文将详细介绍如何在 macOS 系统上从源码部署 RAGFlow 0.14.1 版本&#xff0c;无论是开发人员进行项目实践&#xff0c;还是技术爱好者…...

如何在Kickstart自动化安装完成后ISO内拷贝文件到新系统或者执行命令

如何在Kickstart自动化安装完成后ISO内拷贝文件到新系统或者执行命令 需求 在自动化安装操作系统完成后&#xff0c;需要对操作系统进行配置需要拷贝一些文件到新的操作系统中需要运行一些脚本 问题分析 Linux安装操作系统时&#xff0c;实际上是将ISO镜像文件中的操作系统…...

在服务器部署JVM后,如何评估JVM的工作能力,比如吞吐量

在服务器部署JVM后&#xff0c;评估其工作能力&#xff08;如吞吐量&#xff09;可以通过以下步骤进行&#xff1a; 1. 选择合适的基准测试工具 JMH (Java Microbenchmark Harness)&#xff1a;适合微基准测试&#xff0c;测量特定代码片段的性能。Apache JMeter&#xff1a;…...

攻防世界32 very_easy_sql【SSRF/SQL时间盲注】

不太会&#xff0c;以后慢慢看 被骗了&#xff0c;看见very_easy就点进来了&#xff0c;结果所有sql能试的全试了一点用都没有 打开源代码发现有个use.php 好家伙&#xff0c;这是真的在考sql吗...... 制作gopher协议的脚本&#xff1a; import urllib.parsehost "12…...

STM32G474--Whetstone程序移植(双精度)笔记

1 获取Whetstone程序 Whetstone程序&#xff0c;我用github被墙了&#xff0c;所以用了KK的方式。 获取的程序目录如上所示。 2 新建STM32工程 配置如上&#xff0c;生成工程即可。 3 在生成的工程中添加并修改Whetstone程序 3.1 实现串口打印功能 在生成的usart.c文件中…...

【DeepSeek × Postman】请求回复

新建一个集合 在 Postman 中创建一个测试集合 DeepSeek API Test&#xff0c;并创建一个关联的测试环境 DeepSeek API Env&#xff0c;同时定义两个变量 base_url 和 api_key 的步骤如下&#xff1a; 1. 创建测试集合 DeepSeek API Test 打开 Postman。点击左侧导航栏中的 Co…...

开源身份和访问管理方案之keycloak(一)快速入门

文章目录 什么是IAM什么是keycloakKeycloak 的功能 核心概念client管理 OpenID Connect 客户端 Client Scoperealm roleAssigning role mappings分配角色映射Using default roles使用默认角色Role scope mappings角色范围映射 UsersGroupssessionsEventsKeycloak Policy创建策略…...

基于PaddleOCR的图像文字识别与程序打包方法

目录 一、基本介绍 二、程序实现 1&#xff09;环境配置 2&#xff09;代码实现 3&#xff09;程序运行结果 三、程序打包 1&#xff09;使用pyinstaller打包程序 2&#xff09;添加依赖和模型数据 四、需要注意的问题 五、总结 一、基本介绍 本文主要介绍利用现有开源…...

单片机上SPI和IIC的区别

SPI&#xff08;Serial Peripheral Interface&#xff09;和IC&#xff08;Inter-Integrated Circuit&#xff09;是两种常用的嵌入式外设通信协议&#xff0c;它们各有优缺点&#xff0c;适用于不同的场景。以下是它们的详细对比&#xff1a; — 1. 基本概念 SPI&#xff0…...

Python 字典(一个简单的字典)

在本章中&#xff0c;你将学习能够将相关信息关联起来的Python字典。你将学习如何访问和修改字典中的信息。鉴于字典可存储的信息量几乎不受限制&#xff0c;因此我们会演示如何遍 历字典中的数据。另外&#xff0c;你还将学习存储字典的列表、存储列表的字典和存储字典的字典。…...

一个简单的Windows TCP服务器实现

初始化 WSADATA wsaData; SOCKET serverSocket, clientSocket; struct sockaddr_in serverAddr { 0x00 }; struct sockaddr_in clientAddr { 0x00 }; int clientAddrLen sizeof(clientAddr);if (WSAStartup(MAKEWORD(2, 2), &wsaData) ! 0) {printf("WSAStartup f…...

Node.js笔记入门篇

黑马程序员视频地址&#xff1a; Node.js与Webpack-01.Node.js入门 基本认识 概念 定义&#xff1a;Node.js 是一个免费、开源、跨平台的 JavaScript 运行时环境, 它让开发人员能够创建服务器 Web 应用、命令行工具和脚本 作用&#xff1a;使用Node.js 编写服务器端程序 ✓ …...

EX_25/2/10

epoll实现多路客户端之间的登录注册及消息和文件传输 服务器部分 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include…...

python视频爬虫

文章目录 爬虫的基本步骤一些工具模拟浏览器并监听文件视频爬取易错点一个代码示例参考 爬虫的基本步骤 1.抓包分析&#xff0c;利用浏览器的开发者工具 2.发送请求 3.获取数据 4.解析数据 5.保存数据 一些工具 requests, 用于发送请求&#xff0c;可以通过get&#xff0c;p…...

RbFT:针对RAG中检索缺陷的鲁棒性微调

今天给大家分享一篇最新的RAG论文&#xff1a; 论文题目&#xff1a;Enhancing Retrieval-Augmented Generation: A Study of Best Practices 论文链接&#xff1a;https://arxiv.org/pdf/2501.18365 论文代码&#xff1a;https://github.com/StibiumT16/Robust-Fine-tuning 研…...

证明: 极限的局部有界性

在考研数学中&#xff0c;极限的局部有界性是一个非常重要的概念&#xff0c;尤其是在讨论函数的连续性、可积性和可微性等性质时。局部有界性可以帮助我们理解函数在某些区域内的行为。 定理&#xff1a; 如果 lim ⁡ x → x 0 f ( x ) L \lim_{x \to x_0} f(x) L limx→x0…...

51单片机俄罗斯方块计分函数

/************************************************************************************************************** * 名称&#xff1a;scoring * 功能&#xff1a;计分 * 参数&#xff1a;NULL * 返回&#xff1a;NULL * 备注&#xff1a;采用非阻塞延时 ****************…...

new 以及 call、apply、bind 关键字解析

1.new关键字 自动创建对象&#xff1a;使用new调用构造函数时&#xff0c;会自动创建一个空对象&#xff0c;并将其赋值给this。你不需要显式地使用{}来创建对象。 绑定this到新对象&#xff1a;构造函数内部的this指向新创建的对象&#xff0c;因此可以在构造函数中为新对象添…...

【用Deepseek搭建免费的个人知识库--综合教程(完整版)】第二篇:Ollama服务器

用Deepseek搭建免费的个人知识库–综合教程&#xff08;完整版&#xff09;&#xff1a;第二篇&#xff1a;Ollama服务器部署 OLLAMA服务器的配置在很多网上都已经介绍的非常清楚了&#xff0c;我们的重点不在于那些简单的步骤&#xff0c;而是在需要为下一步做准备的地方更加…...

【图片合并转换PDF】如何将每个文件夹下的图片转化成PDF并合并成一个文件?下面基于C++的方式教你实现

医院在为患者进行诊断和治疗过程中&#xff0c;会产生大量的医学影像图片&#xff0c;如 X 光片、CT 扫描图、MRI 图像等。这些图片通常会按照检查时间或者检查项目存放在不同的文件夹中。为了方便医生查阅和患者病历的长期保存&#xff0c;需要将每个患者文件夹下的图片合并成…...