工欲善其事必先利其器,要了解JVM运行情况,必须用工具获取数据才能发现和诊断问题。 让JVM这个黑盒变成我们可以认识的白盒。
名称 | 作用 | 基本命令 |
jps | 显示指定系统内所有的HotSpot虚拟机进程 | jps -l |
jstat | 用于收集Hotspot虚拟机各方面的运行数据 | jstat[option vmid[interval[s|ms][count]]] 进程2764,gc 情况 |
jinfo | 显示虚拟机配置信息 | jinfo-flag CMSInitiatingOccupancyFraction 1444 |
jmap | 生成虚拟机的内存转储快照 | jmap[option]vmid |
jhat | 用户分析heapdump文件,它会建立一个HTTP/HTML服务器,让用户可以在浏览器上查看分析结果 | |
jstack | 显示虚拟机的线程快照 | jstack[option]vmid jstack -l 3500 |
JConsole | Java监视与管理控制台 | |
VisualVM | 多合一故障处理工具 |
优化策略主要包括JVM垃圾收集器选择、内存分配、高效编译、代码层面优化。
应用场景中主要问题表现为内存溢出、CPU负载高、内存使用过高、系统响应慢。
安全点:程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。
安全区域:在一段代码片段之中,引用关系不会发生变化,在这个区域中的任意地方开始GC都是安全的。
在线程执行到SafeRegion中的代码时,首先标识自己已经进入了SafeRegion,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为SafeRegion状态的线程了。在线程要离开SafeRegion时,它要检查系统是否已经完成了根节点枚举(或者是整个GC过程),如果完成了,那线程就继续执行,否则它就必须等待直到收到可以安全离开SafeRegion的信号为止。
永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
无用的类条件:
在大量使用反射、动态代理、CGLib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。
在Java语言中,可作为GC Roots的对象包括下面几种:
如果GC Roots的对象长期不回收,会导致内存泄漏。
名称 | 算法 | 工作区域 | 方式 | 目标 | 场景 |
Serial | 复制算法 | 新生代 | 串行 | 响应速度优先 | 单CPU环境下的Client模式 |
ParNew | 复制算法 | 新生代 | 并行 | 响应速度优先 | 多CPU环境时在Server模式下与CMS配合 |
Parallel Scavenge | 复制算法 | 新生代 | 并行 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
Serial Old | 标记-整理 | 老年代 | 串行 | 响应速度优先 | 单CPU环境下的Client模式、CMS的后备预案 |
Parallel Old | 标记-整理 | 老年代 | 并行 | 吞吐量优先 | 在后台运算而不需要太多交互的任务 |
CMS | 标记-清除 | 老年代 | 并发 | 停顿时间 | 集中在互联网站或B/S系统服务端上的Java应用 重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验 |
G1 | 标记-整理 | 整堆 | 并行 并发 |
停顿时间 时间可预测 |
面向服务端应用,将来替换CMS |
吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即
吞吐量 = 运行用户代码时间 /(运行用户代码时间 + 垃圾收集时间)。
假设虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
Java11 ZGC
ZGC全称是Z Garbage Collector,是一款可伸缩(scalable)的低延迟(low latency garbage)、并发(concurrent)垃圾回收器,旨在实现以下几个目标:
在不同的场景(高并发低延迟、高吞吐量计算型)选择不同的收集器是必然的优化手段,个中参数需要在实战中不断摸索。
编译过程:
Java语法糖
条件编译
在Java语言之中并没有使用预处理器。
根据布尔常量值的真假,编译器将会把分支中不成立的代码块消除掉,这一工作将在编译器解除语法糖阶段(com.sun.tools.javac.comp.Lower类中)完成。
编译对象与触发条件
栈上替换:OSR编译(On Stack Replacement ),即方法栈帧还在栈上,方法就被替换了。
基于计数器的热点探测:虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”。
热点探测方法:
热点代码的探测参数修改,可以优化一部分代码执行时间。
编译过程
Client Compiler
Server Compiler
Server Compiler的寄存器分配器是一个全局图着色分配器,它可以充分利用某些处理器架构(如RISC)上的大寄存器集合。以即时编译的标准来看,Server Compiler无疑是比较缓慢的,但它的编译速度依然远远超过传统的静态优化编译器,而且它相对于Client Compiler编译输出的代码质量有所提高,可以减少本地代码的执行时间,从而抵消了额外的编译时间开销,所以也有很多非服务端的应用选择使用Server模式的虚拟机运行。
编译器优化技术
消除操作
方法内联
把目标方法的代码“复制”到发起调用的方法之中,避免发生真实的方法调用。
类型继承关系分析(Class Hierarchy Analysis,CHA):基于整个应用程序的类型分析技术,它用于确定在目前已加载的类中,某个接口是否有多于一种的实现,某个类是否存在子类、子类是否为抽象类等信息。
守护内联:如果遇到虚方法,则会向CHA查询此方法在当前程序下是否有多个目标版本可供选择,如果查询结果只有一个版本,那也可以进行内联,不过这种内联就属于激进优化,需要预留一个“逃生门”(Guard条件不成立时的SlowPath)
内联缓存:在未发生方法调用之前,内联缓存状态为空,当第一次调用发生后,缓存记录下方法接收者的版本信息,并且每次进行方法调用时都比较接收者版本,如果以后进来的每次调用的方法接收者版本都是一样的,那这个内联还可以一直用下去。如果发生了方法接收者不一致的情况,就说明程序真正使用了虚方法的多态特性,这时才会取消内联,查找虚方法表进行方法分派。
逃逸分析
当一个对象在方法中被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,称为方法逃逸。甚至还有可能被外部线程访问到,譬如赋值给类变量或可以在其他线程中访问的实例变量,称为线程逃逸。
参考:
Java——七种垃圾收集器+JDK11最新ZGC
Java Class 文件结构