JVM(三)

JVM介绍

执行引擎

执行引擎包含解释器、jit编译器、垃圾回收器,所以java又称为是半编译半解释的语言.执行引擎的任务就是将字节码指令解释/编译为应用平台上的本地机器指令,才能让Java程序运行起来.

解释器

JVM启动时,会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容编译为对应平台的本地机器指令执行.(重点:不需要等待编译,立即执行,但执行需要逐行解释)

JIT编译器

JVM将字节码直接编译成和本地机器平台相关的机器语言,加载热点代码.(重点:有个编译的过程需要等待,但执行速度快)

为什么要有编译器还要有解释器?

因为解释器需要每一行指令都去翻译,这样效率非常低下。只能是那些一开始已经确定好的程序,当程序启动的时候,解释器可以马上使用,省去编译的时间,立即执行。而编译器,编译的时间远远大于解释器,虽然可以很好避免函数被解释执行,而是将整个函数体编译成为机器码,每次函数执行时,只执行编译后的机器码即可,这种方式可以使执行效率大幅度提升。所以各有利弊,在不同的场景可以使用。

设置程序执行方法

默认情况下JVM采用解释器和即时编译器并存的架构,当然也可以根据具体的应用场景,通过配置命令的方式指定JVM完全采用解释器执行,还是完全采用即时编译器执行.
-Xint: 完全采用解释器模式
-Xcomp: 完全采用即时编译器模式执行程序
-Xmixed: 默认模式
如图:
JVM(三)_第1张图片

热点探测

JVM会根据代码被调用的频率来判断这块代码或这行代码是否是热点代码,如果达到一定阈值则会被JIT编译器探测到,然后将其直接编译为对应平台的本地机器指令,以此提升java程序的执行性能.
可以通过配置指令方式 -XX:CompileThreshold=10000 设置对应阈值,默认值为10000,如图:
image.png

热度衰减

方法调用计数器统计并不是方法被调用的绝对次数,而是一个相对的执行频率,即一段时间之内方法被调用的次数.当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半,这个过程称为方法调用计数器热度衰减,而这段时间就称为该方法统计的半衰周期.
可以通过 -XX:-UseCounterDecay 来关闭热度衰减,默认是开启的.
半衰周期(CounterHalfLifeTime)没有提供指令方式进行设置.查询jdk源码(https://github.com/openjdk/jdk/blob/jdk8-b120/hotspot/src/share/vm/runtime/globals.hpp),该参数为开发参数,默认值为30,单位为秒.如图:
JVM(三)_第2张图片

垃圾收集器

jdk8有Serial收集器、Parallel收集器、CMS收集器、G1收集器.衡量垃圾收集器的三项重要指标:内存占用、吞吐量和延迟.三者共同构成了一个不可能三角.一般来说低延迟的垃圾收集器的吞吐量比较低,高吞吐的垃圾收集器的延迟时间比较高.

按年轻代与老年代使用的垃圾回收器区分:

年轻代: serial,parnew,parallel scavenge.
老年代: serial old,cms,parallel old.

按延迟和吞吐量区分:

低延迟优先:cms,g1
吞吐量优先:parallel old

按单线程和多线程区分:

单线程:serial,serial old
多线程:parnew,cms,parallel scavenge,parallel old,g1

按年轻代和老年代组合使用

如图:红色虚线部分为jdk8已废弃,jdk9已去除,绿色虚线部分jdk14为已废弃,CMS在jdk14中已删除.
JVM(三)_第3张图片

Serial回收器

Serial收集器运行示意图:
JVM(三)_第4张图片

介绍

(1)Serial收集器是最基础、历史最悠久的收集器,曾经(在JDK 1.3.1之前)是HotSpot虚拟机新生代收集器的唯一选择.
(2)大家只看名字就能够猜到,这个收集器是一个单线程工作的收集器,它在进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束.

适用场景

小型物理机,运行在客户端模式下的虚拟机,Serial收集器是个不错的选择,由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程收集效率.
可以通过 -XX:+UseSerialGC 开启使用Serial收集器,如图:
JVM(三)_第5张图片

Parallel回收器

Parallel收集器运行示意图:
JVM(三)_第6张图片

介绍

(1) Parallel与Serial相比,它是一款多线程工作的垃圾收集器,可以充分的利用cpu资源.
(2) 与Parnew相比,它的关注目标是达到一个可控制的吞吐量(吞吐量 = 用户应用程序运行的时间 / (应用程序运行的时间 + 垃圾回收的时间)).
(3) Parallel Scavenge和Parallel Old是jdk8默认的垃圾收集器.

适用场景

如果对于收集器运作不太了解,手工优化存在困难的话,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成也许是一个很不错的选择。
只需要把基本的内存数据设置好(如-Xmx设置最大堆),然后使用-XX:MaxGCPauseMillis参数(更关注最大停顿时间)或-XX:GCTimeRatio(更关注吞吐量)参数给虚拟机设立一个优化目标,那具体细节参数的调节工作就由虚拟机完成了。
可以通过 -XX:+UseParallelGC或-XX:+UseParallelOldGC使用Parallel收集器.

CMS回收器

CMS(Concurrent Mark Sweep)收集器运行示意图:
JVM(三)_第7张图片

介绍

(1) CMS收集器是以获取最短停顿时间为目标的收集器.
(2) CMS收集器是基于标记-清除算法实现的,整个过程可以分为四个步骤:
1)初始标记(CMS initial mark)
有STW;初始标记仅仅只是标记一下GCRoots能直接关联到的对象,速度很快.
2)并发标记(CMS concurrent mark)
并发标记阶段就是从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与GC线程一起并发运行.
3)重新标记(CMS remark)
有STW;重新标记阶段是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录.
4)并发清除(CMS concurrent sweep)
清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发执行的.
STW(stop the world)介绍:表示在垃圾收集器在此工作期间,其他业务线程都会暂停工作,应用程序会有卡顿.

适用场景

应用程序比较注重用户的使用体验,CMS是以获取最短回收停顿时间为目标.
可以通过 -XX:+UseConcMarkSweepGC 使用CMS收集器,它是和Parnew收集器组合使用的,如图:
image.png
image.png

CMS缺点

(1) CMS收集器无法处理“浮动垃圾”(Floating Garbage),有可能出现“Con-current Mode Failure”失败而导致另一次Full GC,即让Serial Old收集器来处理.
(2) 标记清除会产生大量空间碎片.空间碎片过多,将会给大对象的分配带来麻烦.

G1回收器

G1收集器内存示意图:
JVM(三)_第8张图片

介绍

(1) G1开创的基于Region的堆内存布局是它能够实现垃圾优先回收目标的关键.G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间.
(2) G1(Garbage First)收集器会跟踪各个Region里面的垃圾堆积的大小,维护一个优先级列表,根据用户设定允许的收集停顿时间(使用 -XX:MaxGCPauseMillis 配置停顿时间,默认200毫秒),优先处理垃圾堆积较大的区域.
(3) 根据上面介绍,G1收集器和CMS收集器一样,它也是以低延迟优先的收集器

适用场景

在小内存应用上CMS的表现会优于G1,但是在大内存应用上G1大多能发挥其优势,当然这也不是绝对的,不同应用需要实际测试才能得出最合适的结论.

总结

只有最合适的垃圾收集器,没有最好的垃圾收集器.这也是HotSpot虚拟机提供实现那么多种不同收集器的原因.垃圾收集器调优是重点也是难点,需要开发者积累大量JVM底层原理并结合实际应用程序情况作出相对应的参数调整.

JVM总结

HotSpot的JVM文章就介绍到这了,大家有什么问题需要探讨的可以留言评论!谢谢

[下一篇 介绍java关键字]

你可能感兴趣的:(java)