公号:码农充电站pro

主页:https://codeshellme.github.io

1,性能优化的步骤

性能优化步骤:

  • 发现问题-性能监控
    • GC 频繁、CPU 负载过高、OOM、内存泄漏、内存负载过高、死锁、程序响应缓慢
    • jvisualvm、jconsole 等工具
  • 排查问题-性能分析
    • 打印 GC 日志,通过 GCviewer 或者 http://gceasy.io 来分析
    • 灵活使用 jstack、jmap、jinfo 等工具
      • 这些命令的字节码文件都在 C:\Program Files\Java\jdk1.8.0_211\lib\tools.jar
    • 使用阿里 arthas、jconsole、jvisualvm 查看 jvm 状态
    • 生成堆 dump 文件,使用 MemoryAnalyzerJprofilerJconsoleJvisualVmjhat 来分析
  • 解决问题-性能调优
    • 适当增加内存,根据业务背景选择垃圾回收器
    • 优化代码,控制内存使用
    • 增加机器,分散节点压力
    • 合理设置线程池线程数量
    • 等。。

内存泄漏的几种情况:

  • 静态集合类:如 HashMap、LinkedList 等
    • 如果这些容器是静态的,则它们的生命周期与 JVM 程序一致,容器中的对象在程序结束前不能被释放
    • 在这里插入图片描述
  • 单例模式:单例的生命周期与 JVM 的生命周期一样长
  • 各种连接:如数据库连接,网络连接、IO 连接等
    • 如果连接没有正常关闭,则造成内存和系统资源浪费
  • 变量不合理的作用域
    • 下图中的 msg 变量只被 receiveMsg 方法用到,可将其移到方法内部
    • 在这里插入图片描述
    • 另外字符串及时置 null,也可使字符串及时被回收

2,常用命令工具

1,jps:查看 Java 进程

jps:查看正在运行的 java 进程

  • jps:显示进程 id 与类名
  • jps -l:显示进程 id 与全类名
  • jps -m:显示进程 id 与类名与程序参数
  • jps -v:显示 JVM 参数
  • jps -q:只显示进程 id
  • 如果启动 Java 进程时使用了 -XX:-UserPerfData 参数,那么 jps 将查看不到该进程

2,jstat:查看 JVM 统计信息

jstat:查看 JVM 统计信息,常用于检查垃圾回收与内存泄漏问题

命令格式:jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]] (使用时要注意参数的顺序)

  • -option
  • -t:输出进程从开始执行到现在的时间(秒)
  • -h<lines>:间隔多少行输出一次标头信息(紧挨着,没空格)
  • vmid:指定进程 id
  • interval:指定输出统计数据的间隔时间,单位毫秒
  • count:指定查询的总次数

查看 option 支持的选项:jstat -options

  • -class:显示 ClassLoader 的相关信息:类的装载、卸载数量、总空间、类装载所消耗的时间等
  • -gc:显示与 GC 相关的堆信息:Eden 区、两个 Survivor 区、老年代、永久代等的容量、已用空间、GC时间合计等信息
    • S0C/S1C:幸存者区大小(单位字节)
    • S0U/S1U:幸存者区已使用的大小
    • EC/EU:Eden区大小 / Eden区已使用的大小
    • OC/OU:老年代大小 / 老年代已使用的大小
      • 如果老年代的使用比例越来越高,而无法降低,则有可能出现内存泄漏
    • MC/MU:方法区大小 / 方法区已使用的大小
    • CCSC/CCSU:压缩类空间大小 / 压缩类已使用的大小
    • YGC/YGCT:进程从启动到目前的 young gc 的次数/消耗时间(秒)
    • FGC/FGCT:进程从启动到目前的 full gc 的次数/消耗时间(秒)
    • GCT:进程从启动到目前的 gc 的消耗时间(秒)
      • 如果 GCT 时间占程序运行时间的 20%,则说明目前堆的压力较大
      • 如果 GCT 时间占程序运行时间的 90%,则随时可能 OOM
  • -gccapacity:显示内容与 -gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间
  • -gcutil:显示内容与 -gc 基本相同,但输出主要关注已使用空间占总空间的百分比
  • -gccause:与 -gcutil 功能一样,但会额外输出导致最后一次或当前正在发生的 GC 产生的原因
  • -gcnew/-gcold:显示新生代/老年代 GC 状况
  • -gcnewcapcity/-gcoldcapacity:与 -gcnew/-gcold 基本相同,输出主要关注使用到的最大、最小空间
  • -gcmetacapacity
  • -compiler:显示 JIT 编译器编译过的方法、耗时等信息
  • -printcompilation:输出已经被 JIT 编译的方法

3,jinfo:查看修改 JVM 配置参数

jinfo:实时查看和修改 JVM 配置参数

并非所有的参数都支持动态修改,只有标记为 manageable 的才能被实时修改

  • java -XX:+PrintFlagsFinal -version | grep manageable
  • java -XX:+PrintFlagsFinal 查看所有 JVM参数的最终值
  • java -XX:+PrintFlagsInitial 查看所有 JVM参数的初始值

jinfo 参数:

  • -sysprops:查看系统属性信息
  • -flags:查看所有赋过值的参数
  • -flag name:查看某个具体的参数的使用用情况
  • -flag +/-name:设置虚拟机参数
  • -flag name=value:设置虚拟机参数

4,jmap:导出内存映像文件

jmap:导出内存映像文件和内存使用情况

基本语法:jmap [option] <pid>

option 选项:

  • -dump:<dump-options>:生成堆转储文件
    • dump:live:只保存堆中的活跃对象
    • dump:fomat=b
    • dump:file=<file_path>
  • -heap:输出整个堆空间的详细信息,包括 GC 的使用,对配置信息,以及内存的使用信息等
    • 在这里插入图片描述
  • -histo[:live]:输出堆中对象的同级信息,包括类、实例数量和合计容量
    • -histo:live 只统计堆中的存活对象
    • 在这里插入图片描述
  • -permstat:以 ClassLoader 为统计口径输出永久代的内存状态信息
  • -finalizerinfo:显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象
  • -F:当虚拟机进程对 -dump 选项没有任何响应时,强制生成 dump 文件

导出内存映像文件的两种方式:

  • 使用 jmap:
    • jmap -dump:format=b,file=<file_name.hprof> <pid>
    • jmap -dump:live,format=b,file=<file_name.hprof> <pid> 生成的文件更小,一般情况使用此命令较多
  • 在进程发生 OOM 时,自动生成堆 dump 文件
    • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=<file_name.hprof>

5,jhat:分析堆转储文件

jhat:堆转储文件分析工具。

  • jhat file_name.hprof

jhat 内置了一个 HTTP 服务器,生成分析结果后,用户可以在浏览器中查看分析结果。jhat 会启动一个 http 服务器,端口是 7000,即 http://localhost:7000,用浏览器访问即可。

该命令在 jdk9 及其之后被移除,官方建议使用 jvisualvm 代替 jhat

6,jstack:查看线程快照

jstack:查看 JVM 中线程快照。常用于分析线程长时间卡顿问题,如线程死锁,死循环,请求外部资源导致长时间等待等问题。

需要关注的几种线程状态:

  • 死锁:Deadlock(重点关注)
  • 等待资源:Waiting on condition(重点关注)
  • 等待获取监视器:Waiting on monitor entry(重点关注)
  • 阻塞:Blocked(重点关注)
  • 执行中:Runnable
  • 暂停:Suspended
  • 对象等待中:TIMED_WATING / WATING
  • 停止:Parked

基本语法jstack [option] <pid>

  • jstack pid :直接查看线程栈信息
  • jstack -F pid :强制输出线程栈信息
  • jstack -l pid :除堆栈外,显示锁的附加信息
  • jstack -m pid :如果调用到本地方法,显示 C/C++ 堆栈信息

7,jcmd:多功能命令行

jcmd 可以用来实现上面除了 jstat 之外所有的功能,比如:导出堆,内存使用,查看进程,导出线程信息,执行 GC,JVM 运行时间等。

命令示例:

# 查看某个 Java 进程的所有支持的命令替换
jcmd <pid> help
---------------
25801:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
VM.classloader_stats
GC.rotate_log
Thread.print					# 打印线程栈
GC.class_stats
GC.class_histogram              # class 直方图
GC.heap_dump					# 导出堆dump文件
GC.finalizer_info
GC.heap_info
GC.run_finalization
GC.run
VM.uptime                       # 显示进程执行的总时间
VM.dynlibs
VM.flags                        # 显示虚拟机参数配置信息
VM.system_properties            # 显示系统属性信息
VM.command_line
VM.version                      # 打印 jdk 版本
help

3,图形化分析工具

JVM 常用图形化分析工具:

  • jdk 自带工具
    • jconsole:基于 JMX
    • jvisualvm
    • jmc
  • 第三方工具
    • MemoryAnalyzer (MAT):主要用于分析堆 dump 文件
    • Jprofiler(商业付费)
    • Arthas(阿里出品-命令行工具)
    • Btrace

4,JVM 运行时参数

1,JVM 参数选项的类型

JVM 参数选项的类型:

  • 标准参数选项:以 - 开头,运行 java -help 可见
    • 参数比较稳定,基本不变动
  • -X 参数选项(非标准参数):以 -X 开头,运行 java -X 可见
    • 参数变动小
    • -Xmixed:JIT 变异模式,混合模式,默认模式
    • -Xint:JIT 变异模式,只是用解释器,所有字节码被解释执行,速度较慢
    • -Xmx:最大堆内存
      • 等价于 -XX:MaxHeapSize
      • 单位 g/G,m/M,k/K
    • -Xms:初始堆内存,最好与 -Xmx 一样,避免扩容带来的损耗
      • 等价于 -XX:InitialHeapSize
    • -Xss:线程栈大小
      • 等价于 -XX:ThreadStackSize
  • -XX 参数选项(非标准参数):以 -XX 开头,运行 -XX:PrintFlagsFinal 显示所有参数
    • 实验性,参数变动大
    • 布尔类型:
      • -XX:+<option>:启用某参数
      • -XX:-<option>:禁用某参数
    • 非布尔类型:-XX:<name>=<value>

2,打印及设置 JVM 参数

参数 说明
-XX:+PrintCommandLineFlags 打印用户手动设置或JVM自动设置的参数
-XX:+PrintFlagsInitial 打印所有 XX 选项的默认值
-XX:+PrintFlagsFinal 打印 XX 选项的生效值
-XX:+PrintVMOptions 打印 JVM 参数

3,设置堆、栈、方法区等大小

是GC 执行垃圾回收的重点区域。在方法结束后,堆中的对象不会马上被回收,而是在垃圾收集的时候才会被回收。

运行时数据区结构图:

在这里插入图片描述

在这里插入图片描述

JVM 堆空间分配:

  • Java 7 及之前:
    • 新生代(Young):Eden、S0(From)、S1(To)
    • 老年代(Old)
    • 永久区(Perm)
  • Java 8 及之后
    • 新生代(Young):IBM研究表明,新生代中的80% 的对象,在经过 GC 之后,都会被回收
      • Eden:对象第一次创建时(new)的区域,如果Eden 放不下,则会直接在 Old 区创建
      • S0(From)
      • S1(To)
        • S0 与 S1 谁空谁是 To
    • 老年代(Old)
    • 元空间(Meta)

堆区中 S1 和 S2 两块区域,同一时刻只会有一块区域使用,另一块是空闲状态。

分代的目的就是为了优化 GC 的性能

在这里插入图片描述

对象从新生代到老年代的过程:

  • new 的对象先放入 Eden 区
  • 当 Eden 区满,再创建对象时,垃圾回收器将对 Eden 区进行垃圾回收(Minor GC);Eden 区中不再被引用的对象将被销毁,Eden 区中剩余的对象移动到 S0 区。新 new 的对象依然放入 Eden 区
  • 再次发生GC 时,S0 中幸存的对象将放入 S1中,
  • 再次发生GC 时,S1 中幸存的对象将放入 S0中,如此 S0 与 S1 交替往复
  • 当达到一定次数后,对象将被放入 Old 区
    • 该次数可通过参数 -XX:MaxTenuringThreshold 设置,默认 15
  • 当 Old 区空间不足时,会发生 Major GC,清理 Old 区对象
    • Major GC 会比 Minor GC 慢 10 倍以上
  • 若发生 Major GC 后,Old 空间依然不足,将发生 OOM 异常

只有当 Eden 区满了才会触发 Minor GC,Minor GC 会将 Eden 区和 From 区一起回收 如果 To 区满(不会触发 GC),会直接将对象放入 Old 区

在这里插入图片描述

发生 GC 时,将 Stop The World

使用 jstat 命令可查看堆中各个区域的空间:

jstat -gc pid

在这里插入图片描述

堆空间分配:

区域 大小比例
新生代Eden1536006
S0(From)256001
S1(To)256001
老年代Old40960016
参数 说明
-Xss/-XX:ThreadStackSize 设置线程栈大小
-Xms/-XX:InitialHeapSize 设置JVM初始堆大小,默认是机器内存的 1/64
-Xmx/-XX:MaxHeapSize 设置JVM最大堆大小,默认是机器内存的 1/4,当实际堆使用量大于此值时,将发生 OOM(一般不设置
-Xmn/-XX:NewSize,-XX:MaxNewSize 设置年轻代初始值/最大值,官方推荐配置为堆大小的3/8一般不设置
-XX:SurvivorRatio 设置年轻代中 Eden区与一个Survior区的比值,默认为 8,8:1:1实际上真正看内存的话是 6:1:1一般不设置
-XX:+UseAdaptiveSizePolicy 自动选择各区大小比例,默认开启 (一般不设置
-XX:NewRatio 设置老年代与年轻代(1个Eden区和2个Survivor区)的比值,默认为 2,2:1一般不设置
-XX:PretenureSizeThreadshould 让大于此值的对象直接分配在老年代,单位字节,只对 Serial、ParNew 收集器有效
-XX:MaxTenuringThreshold 新生代每次MinorGC后,还存活的对象年龄+1,当对象的年龄大于此值时就进入老年代,默认为 15
-XX:+PrintTenuringDistribution 让JVM在每次MinorGC后打印出当前使用的Survivor中对象的年龄分布
-XX:TargeSurvivorRatio 设置MinorGC结束后,Survivor 区域中占用空间的期望比例
-XX:PermSize 设置方法区永久代初始值
-XX:MaxPermSize 设置方法区永久代最大值
-XX:MetaspaceSize 设置方法区元空间初始值
-XX:MaxMetaspaceSize 设置方法区元空间最大值
-XX:+UseCompressedOops 使用压缩对象指针
-XX:+UseCompressedClassPointers 是引用压缩类指针
-XX:CompressedClassSpaceSize 设置Klass Metaspace 的大小,默认 1 G
-XX:MaxDirectMemorySize 指定直接内存容量,默认与堆最大值一样

4,内存溢出相关参数

参数 说明
-XX:+HeapDumpOnOutMemoryError 在内存出现OOM时,生成堆转储文件
-XX:+HeapDumpBeforeFullGC 在出现FullGC时,生成堆转储文件,有几次FullGC,就生成几个文件
-XX:HeapDumpPath 设置堆转储文件路径
-XX:OnOutOfMemoryError 设置一个可执行程序或脚本路径,当发生OOM是,执行它

5,垃圾收集器相关参数

在这里插入图片描述

Java 垃圾收集器:

  • SerialGC:HotSpot 中 Client模式下的默认新生代垃圾收集器
    • 串行收集器,可获得最高的单线程收集效率
    • SerialOldGC:HotSpot 中 Client模式下的默认老年代垃圾收集器
  • ParNewGC:SerialGC 的并行版本(不重要)
  • ParallelGC:并行收集器,主打吞吐量(jdk 8 默认开启)
    • ParallelOldGC:老年代 GC
  • CMS 收集器:并发收集器,主打低延迟(未来将被丢弃)
    • 使用标记清除算法
  • G1 收集器:主打低延迟

如何选择垃圾收集器:

  • 优先调整堆的大小,让JVM自适应完成
  • 如果内存小于 100M,使用串行收集器
  • 如果是单核,单机程序,且没有停顿时间的要求,使用串行收集器
  • 如果是多核CPU,需要高吞吐量,允许停顿时间超过 1 秒,选择并行或者 JVM 自己选择
  • 如果是多核CPU,追求低停顿时间,需快速响应,使用并发收集器,官方推荐 G1,性能高
    • 现在互联网的项目,基本都是 G1

ParallelGC 垃圾收集器相关参数:

在这里插入图片描述

CMS 垃圾收集器相关参数:

在这里插入图片描述

补充参数:

在这里插入图片描述

特别说明:

在这里插入图片描述

G1 垃圾收集器:

在这里插入图片描述

在这里插入图片描述

6,GC 日志相关参数

参数 说明
-verbose:gc/-XX:+PrintGC 输出GC日志信息
-XX:+PrintGCDetails 在发生垃圾回收时打印内存回收详细日志,并在进程退出时输出当前内存各区域的分配情况
-XX:+PrintGCTimeStamps 程序启动到GC发生的时间秒数,需配合 PrintGCDetails 使用
-XX:+PrintGCDateStamps 输出GC发生时的时间戳,需配合 PrintGCDetails 使用
-XX:+PrintHeapAtGC 每次GC前和GC后,都打印堆信息
-Xloggc:<file> 将GC日志写入文件中
-XX:TraceClassLoading 监控类的加载
-XX:PrintGCApplicationStoppedTime 打印GC时线程的停顿时间
-XX:+PrintGCApplicationConcurrentTime 垃圾收集之前,打印应用未中断的执行时间
-XX:+PrintReferenceGC 记录回收了多少种不同引用类型的引用
-XX:+PrintTenuringDistribution 每次MinorGC后打印出当前使用的Survivor中对象的年龄分布
-XX:+UseGCLogFileRotation 启用GC日志文件的自动转储
-XX:NumberOfGCLogFiles GC日志文件的循环数目
-XX:GCLogFileSize 控制GC日志文件的大小

7,其它参数

参数 含义
-XX:+DisableExplicitGC 禁用hotspot 执行 System.gc(),默认禁用
-XX:ReservedCodeCacheSize, -XX:InitialCodeCacheSize 指定代码缓存大小
-XX:+UseCodeCacheFlushing 让JVM放弃一些被编译的代码,避免代码缓存被占满时JVM切换到 interpreted-only 的情况
-XX:+DoEscapeAnalysis 开启逃逸分析
-XX:+UseBiasedLocking 开启偏向锁
-XX:+UseLargePages 开启使用大页面
-XX:+PrintTLAB 打印TLAB的使用情况
-XX:TLABSize 设置TLAB大小

5,GC 日志分析

对于 HotSpot VM,它的GC 按照回收区域又分为两大类:

  • 部分收集(Partial GC):非整堆收集,其中又分为:
    • 新生代收集(Minor GC/Young GC):只是新生代(Eden\S0,S1)的垃圾收集
      • 当 Eden 区满的时候就会进行新生代收集
      • 新生代收集比老年代收集更加简单,快速,频繁
    • 老年代收集(Minor GC/Old GC):只是老年代的垃圾收集
      • 目前,只有 CMS GC 会有单独收集老年代的行为
      • 进行老年代收集之前会先进行一次年轻代收集,原因如下:
      • 一个比较大的对象无法放入新生代,那它自然会往老年代去放,如果老年代也放不下,那会先进行一次新生代收集,
      • 之后尝试往新生代放,如果还是放不下,才会进行老年代的垃圾收集,然后往老年代去放
    • 混合收集(Mixed GC):收集整个新生代以及部分老年代
      • 目前,只有 G1 GC 会有这种行为
  • 整堆收集(Full GC):收集整个 java 堆和方法区
    • 堆包含新生代,老年代,元空间/永久代
    • 哪些情况会触发Full GC:
      • 老年代空间不足
      • 方法区空间不足
      • 调用 System.gc()
      • Minor GC 进入老年代的数据的平均大小大于老年代的可用空间
      • 大对象直接进入老年代,而老年代的空间不足