【深入理解JVM(五)】:性能优化(上)

JVM 系列文章目录

第一篇:内存区域与内存异常
第二篇:对象揭秘与堆内存分配策略
第三篇:垃圾回收
第四篇:类加载机制
第五篇:性能优化(上)



一、内存溢出(out of memory)和内存泄漏(memory leak)

  内存溢出(out of memory)和内存泄漏(memory leak)我在第一篇第三节(内存异常)中已经讲过,这里简单回顾下:

  • 内存溢出(out of memory):程序在申请内存时,没有足够的内存空间;
  • 内存泄漏(memory leak):程序在申请内存后,无法释放已申请的内存空间。

  乍一看概念好像差不多,那么他们有什么区别呢?内存溢出说白了就是内存不够,内存泄漏是本来应该回收的内存没有释放掉,一次内存泄露可能对程序运行没有明显的影响,但随着时间的积累最终会导致内存溢出。所以,内存泄漏是 JVM 性能优化的一项重要工程并且是一个力气活,那么哪些情况会导致内存泄漏呢?

  • 长生命周期对象持有短生命周期对象的引用:这个比较好理解,举个极端点的例子,当一个实例对象被静态变量引用,那么只要 JVM 不重启,这个实例对象永远回收不掉;
  • 连接未关闭:比如数据库连接、网络连接和IO连接等;
  • 变量作用域不合理:比如一个变量的定义的作用范围大于了其使用范围;
  • 内部类持有外部类:如果一个外部类的实例对象的方法返回了一个内部类的实例对象,这个内部类对象被长期引用了,即使那个外部类实例对象不再被使用,但由于内部类持有外部类的实例对象,这个外部类对象将不会被垃圾回收;
  • Hash值改变 :当一个对象被存储进 HashSet 集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段了。否则,对象修改后的哈希值与最初存储进 HashSet 集合中时的哈希值就不同了,在这种情况下,即使在 contains 方法使用该对象的当前引用作为的参数去HashSet 集合中检索对象,也将返回找不到对象的结果,这也会导致无法从 HashSet 集合中单独删除当前对象。

二、内存泄漏分析

1、浅堆(Shallow Heap)是和深堆(Retained Heap)

  内存溢出分析之前,首先要了解下浅堆和深堆:

  • 浅堆:一个对象所消耗的内存。
  • 深堆:个对象被 GC 回收后,可以真实释放的内存大小。举个例子,对象 A 引用了 C、D 和 E,对象 B 引用了 E。那么对象 A 的浅堆大小只是 A 本身,而如果 A 被回收,那么 C 和 D 都会被回收,所以 A 的深堆大小为 A+C+D 之和,同时由于对象 E 还可以通过对象 B 访问到,因此不在对象 A 的深堆范围内。
    在这里插入图片描述

2、MAT

  MAT 是一款非常强大的内存分析工具,在Eclipse中有相应的插件,同时也有单独的安装包。在进行内存分析时,只要获得了反映当前设备内存映像的快照文件,通过 MAT 打开就可以直观地看到当前的内存信息。

2.1、Overview 页面

  MAT 打开一个快照文件后一般会进入如下的 overview 界面,或者直接进入 leak suspect 界面:
  
在这里插入图片描述
  

2.2、Histogram 界面

  以直方图的方式来显示当前内存使用情况可能更加适合较为复杂的内存泄漏分析,它默认直接显示当前内存中各种类型对象的数量及这些对象的浅堆和深堆。
  
在这里插入图片描述
  

2.3、Dorminator Tree 界面

  支配树,可以直观地反映一个对象的深堆情况,当一个对象的深堆远远大于浅堆时,说明该对象持有大量引用对象,存在内存泄露风险。
  
在这里插入图片描述
  

2.3、Leak Suspects 界面

  泄漏猜想,通过类似于前面的综合分析,给出可能存在泄露的地方:
  
在这里插入图片描述
  
  点击 Details 可以查看详细信息:
  
在这里插入图片描述
  
点击类名会弹出很多自定义选项,包括 Histogram 界面和 Dorminator Tree 界面,选中类名鼠标右键点击也会弹出。

2.3.1、List Objects

  有两个选项:with incoming refrences 和 with outcoming refreences,分别会罗列出选定对象在引用树中的父节点和子节点,可以作为为什么当前对象还保留在内存中的基本依据。在平时的排查过程中,此功能还是比较常用,因为可以顺着罗列出的引用路径一步步分析内存泄漏的具体原因。
  
在这里插入图片描述
  

2.3.2、Show objects by class

  和 List Objects 相似,也可以罗列出当前选中对象在引用树种的上下级关系,只是这个选项会将结果中类型相同的对象进行归类。
  
在这里插入图片描述
  

2.3.3、Merge Shortest Path To GC Roots

  这是快速分析的一个常用功能,它能够从当前内存映像中找到一条指定对象所在的到 GC Root 的最短路径。这个功能还附带了其他几个选项,这几个选项分别指明了计算最短路径的时候是否是需要排除弱引用、软引用及影子引用等。
  
在这里插入图片描述
  

2.3.4、Show Retained Set

  这个功能简单来说就是显示选定对象对应在 Dominator Tree 中的子节点集合,所有这些子节点所占空间大小对应于它的深堆。可以用来判断当一个对象被回收的话有多少其他类型的对象会被同时回收掉。
  
在这里插入图片描述
  

三、JDK为我们提供的工具

  
在这里插入图片描述
  

1、命令行工具

1.1、jps

  列出当前机器上正在运行的虚拟机进程,jps 从操作系统的临时目录上去找(所以有一些信息可能显示不全)。
-q:仅仅显示进程
-m:输出主函数传入的参数
-l::输出应用程序主类完整 package 名称或 jar 完整名称.
-v:列出 jvm 参数,-Xms -Xmx -Xss 是启动程序指定的 jvm 参数

1.2、jstat

  用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT 编译等运行数据,在没有 GUI 图形界面,只提供了纯文本控制台环境的服务器上,它将是运行期定位虚拟机性能问题的首选工具。
-class (类加载器)
-compiler (JIT)
-gc (GC 堆状态)
-gccapacity (各区大小)
-gccause (最近一次 GC 统计和原因)
-gcnew (新区统计)
-gcnewcapacity (新区大小)
-gcold (老区统计)
-gcoldcapacity (老区大小)
-gcutil (GC 统计汇总)
-printcompilation (HotSpot 编译统计)
  
  我个人挺喜欢用这个命令,因为它能实时监控,假设需要每 250 毫秒查询一次进程 7110 垃圾收集状况,一共查询 10 次,那命令应当是:jstat -gc 7110 250 10。
  
在这里插入图片描述

  • S0C : 年轻代中 S0 区的容量(kb)
  • S1C : 年轻代中 S1 区的容量(kb)
  • S0U : 年轻代中 S0 区 目前已使用的容量(kb)
  • S1U : 年轻代中 S1 区目前已使用的容量(kb)
  • EC : eden 区的容量(kb)
  • EU : eden 区目前已使用的容量(kb)
  • OC : 老年代的容量(kb)
  • OU : 老年代目前已使用的容量(kb)
  • MC : 元数据区的容量(kb)
  • MU : 元数据区目前已使用的容量(kb)
  • CCSC : 压缩类空间大小(kb)
  • CCSU : 压缩类空间使用大小(kb)
  • YGC : 从应用程序启动到采集时年轻代中 gc 次数
  • YGCT : 从应用程序启动到采集时年轻代中 gc 所用时间(秒)
  • FGC : 从应用程序启动到采集时老年代中 gc 次数
  • FGCT : 从应用程序启动到采集时老年代 gc 所用的时间(秒)
  • GCT : 从应用程序启动到采集时 gc 所用的总时间(秒)

1.3、jinfo

  查看和修改虚拟机的参数。
jinfo –sysprops 可以查看由 System.getProperties() 取得的参数
jinfo –flag 未被显式指定的参数的系统默认值 jinfo –flags(注意 s)显示虚拟机的参数
jinfo –flag +[ 参 数 ] 可 以 增 加 参 数 , 但 是 仅 限 于 由 java -XX:+PrintFlagsFinal –version 查 询 出 来 且为 manageable 的参数
jinfo –flag -[参数] 可以修改参数

1.4、jmap

-heap 堆内存信息
-dump:live,format=b,file=heap.bin pid 用于生成堆转储快照
-histo:live 查看每个类的实例、空间占用统计 采用jmap -histo pid>a.log日志将其保存

1.5、jstack

  用于生成虚拟机当前时刻的线程快照。线程快照就是当前虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主 要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等都是导致线程长时间停顿的常见原因。 在代码中可以用 java.lang.Thread 类的 getAllStackTraces() 方法用于获取虚拟机中所有线程的 StackTraceElement 对象。使用这个方法可以通过简单的几行代码就完成 jstack 的大部分功能,在实际项目中不妨调用这个方法做个管理员页面,可以随时使用浏览器来查看线程堆栈。

-F:当正常输出的请求不被响应时,强制输出线程堆栈
-m:如果调用到本地方法的话,可以显示 C/C++ 的堆栈
-l:除堆栈外,显示关于锁的附加信息,在发生死锁时可以用 jstack -l pid 来观察锁持有情况

我们经常会用 jstack 来分析 CPU 过高的问题,步骤如下:

  1. 通过top -Hp pid 可以查看该进程下,各个线程的cpu使用情况;
  2. jstack pid 命令来查看当前java进程的堆栈状态,可用 jstack pid >/tmp/log.txt 将堆栈信息输入到具体文件,方便查看;
  3. 将1中查到的占用 CPU 高的线程号转成16进制,因为堆栈信息打印的是16进制,就能通过线程号查看堆栈信息了。

在这里插入图片描述

2、可视化工具

2.1、jconsole

  一种基于JMX的可视化监视、管理工具,基本功能包括:概述、内存、线程、类、VM概要、MBean。在 jdk 的 bin 目录下,双击就能启动。
  
在这里插入图片描述
在这里插入图片描述

2.2、VisualVM

  一款功能非常强大的运行监视和故障处理程序,它不仅能监视程序的各种信息(CPU、GC、堆、栈、线程等),还能分析内存快照。也在 jdk 的 bin 目录下,双击就能启动。
  
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

砍光二叉树

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值