Java虚拟机必学之四大知识要点,附系列学习资料!

335 阅读9分钟

作者:郑雨迪

来源:极客时间《深入拆解Java虚拟机》


作为一位Java程序员,在尽情享受Java虚拟机带来好处的同时,我们还应该去了解和思考“这些技术特性是如何实现的”,去了解最底层的原理。只有熟悉JVM,你才能在遇到 OutOfMemory等异常时,不会束手无策,不会一脸懵逼地上网找解决办法,最后就算改了几个启动参数解决了问题,也还是云里雾里。


这次,我会从我专栏里提取了学习Java虚拟机的X大知识要点,助力大家深入理解JVM,知其然也知其所以然。 不过你在看知识点之前,最好能问问自己你会怎么回答,再和我提供的内容做对比,这样子提升会比较明显。


第一大知识要点:Java字节码是如何在虚拟机里运行的?


我将以HotSpot虚拟机为例,从虚拟机以及底层硬件两个角度,来分享解析。


1、从虚拟机视角来看

执行Java代码首先需要将它编译而成的class文件加载到Java虚拟机中。加载后的Java类会被存放于方法区中。实际运行时,虚拟机会执行方法区内的代码。

如果你熟悉X86的话,你会发现这和段式内存管理中的代码段类似。而且,Java虚拟机同样也在内存中划分出堆和栈来存储运行时数据。不同的是,Java虚拟机会将栈细分为面向Java方法的Java方法栈,面向用C++写的native方法的本地方法栈,以及存放各个线程执行位置的PC寄存器。

在运行过程中,每当调用进入一个Java方法,Java虚拟机会在当前线程的Java方法栈中生成一个栈帧,用以存放局部变量以及字节码的操作数。这个栈帧的大小是提前计算好的,而且Java虚拟机不要求栈帧在内存空间里连续分布。


当退出当前执行的方法时,不管是正常返回还是异常返回,Java虚拟机均会弹出当前线程的当前栈帧,并将之舍弃。


2、从硬件视角来看

Java字节码无法直接执行。因此,Java虚拟机需要将字节码翻译成机器码。

在HotSpot里面,上述翻译过程有两种形式:第一种是解释执行,相当于同声传译,即每解析一条字节码,便翻译成机器码并执行;第二种是即时编译(Just-In-Time compilation,JIT),则相当于线下翻译,即将整个方法中所包含的字节码统一翻译成机器码后在执行。

前者的优势在于无需等待编译,而后者的优势在于实际运行速度更快。HotSpot默认采用混合模式,综合了解释执行和即时编译两者的优点。它会先解释执行字节码,而后将其中反复执行的热点代码,以方法为单位进行即时编译。


第二大知识要点:Java虚拟机是如何加载Java类的?


Java虚拟机加载Java类的过程可分为加载、链接以及初始化三大步骤。

加载是指查找字节流,并且据此创建类的过程。加载需要借助类加载器,在Java虚拟机中,类加载器使用了双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器。

链接,是指将创建成的类合并至Java虚拟机中,使之能够执行的过程。链接还分验证、准备和解析三个阶段,分别完成“验证被加载类是否满足Java虚拟机约束”,“为被加载类静态字段分配内存”,以及“将被加载类中的符号引用解析成为实际引用”的工作。其中,Java虚拟机规范并不要求解析阶段一定要在链接步骤中完成。

初始化,则是为标记为常量值的字段赋值,以及执行方法的过程。类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化。


第三大知识要点:Java虚拟机是如何进行垃圾回收的?


Java虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象。它从一系列GC Roots出发,边标记边探索所有被引用的对象。为了防止在标记过程中堆栈的状态发生改变,Java虚拟机采取安全点机制来实现Stop-The-World操作,暂停其他非垃圾回收线程。

回收垃圾对象的内存共有三种基础算法,分别为:会造成内存碎片的清除算法、性能开销较大的压缩算法、以及堆使用效率较低的复制算法。

通常来说,Java虚拟机会采用分代回收的思想,将堆划分为新生代和老年代,并且通过在不同代中应用不同的垃圾回收算法。

传统的做法是将新生代再划分为Eden区和两个大小一致的Survivor区。在只针对新生代的Minor GC中,Eden区和非空Survivor区的存活对象会被复制到空的Survivor区中,当Survivor区中的存活对象复制次数超过一定数值时,它将被晋升至老年代。

因为Minor GC只针对新生代进行垃圾回收,所以在枚举GC Roots的时候,它需要考虑从老年代到新生代的引用。为了避免扫描整个老年代,Java虚拟机引入了名为卡表的技术,大致地标出可能存在老年代到新生代的引用的内存区域。

G1垃圾回收器将堆划分为多个等大的区域,每个区域都可以充当Eden区,Survivor区或者老年代区。G1会优先收集垃圾最多的区域,从而最大化垃圾回收的效益。这也是Garbage First名字的由来。

Java 11中引入的实验性垃圾回收器ZGC,仅在扫描GC Roots时请求Stop-The-World,暂停应用线程。因此,它宣称可将GC暂停时间控制在10ms以下。ZGC暂时没有应用分代回收的思路,将整个堆空间看成一块,其代价是垃圾回收CPU消耗较高。


第四大知识要点:Java内存模型是什么?


在现代计算机系统中,代码通常不会按照书写顺序执行。造成这一情况的原因有三个,分别为编译器的重排序,处理器的乱序执行,以及内存系统的重排序。


以内存系统重排序为例,在多处理器体系架构下,每个处理器都可能缓存了一部分数据。由于时刻保持缓存数据与内存数据同步的性能代价太大,因此部分体系架构可能允许缓存数据与内存数据不同步。这对Java程序的影响便是,两个不同的Java线程在同一时间内看到的同一块内存地址中的值可能不同。


Java内存模型是针对上述问题而提出的一套规范,用以允许Java程序员更为细致地定义Java程序的内存行为。它通过定义了一系列的happens-before操作,让应用程序开发者能够轻易地表达不同线程的操作之间的内存可见性。


在遵守Java内存模型的前提下,即时编译器以及底层体系架构能够调整内存访问操作,以达到性能优化的效果。如果开发者没有正确地利用happens-before规则,那么将可能导致数据竞争。


Java内存模型是通过内存屏障来禁止重排序的。对于即时编译器来说,内存屏障将限制它所能做的重排序优化。对于处理器来说,内存屏障会导致缓存的刷新操作。


扩展阅读


我的专栏《深入拆解Java虚拟机》已完结,非常感谢在我专栏完结之前的16000多名订阅用户,在未了解完整内容的情况下,毅然订阅了我的专栏。为不辜负大家的信任,我几乎每篇专栏都会大量阅读HotSpot的源代码,和同事讨论实现背后的设计理念,在这个过程中,我也发现了一些HotSpot中的Bug,或者年久失修的代码,又或者是设计不合理的地方。这大概也能够算作写专栏和我本职工作重叠的地方吧。


专栏虽然到此已经结束了,但是并不代表你对Java虚拟机学习的停止。我想,专栏的内容仅仅是为你打开了JVM学习的大门,里面的风景,还是需要你自己来探索。在文章的后面,我列出了一系列的Java虚拟机技术的相关博客和阅读资料,你仍然可以继续加餐。


你可以关注国内几位Java虚拟机大咖的微信公众号:

  1. R大,个人认为是中文圈子里最了解Java虚拟机设计实现的人,你可以关注他的[知乎账号](https://www.zhihu.com/people/rednaxelafx);

  2. [你假笨](https://open.weixin.qq.com/qr/code?username=lovestblog),原阿里Java虚拟机团队成员,现[PerfMa](http://www.perfma.com/) CEO;

  3. [江南白衣](https://open.weixin.qq.com/qr/code?username=jnby1978),唯品会资深架构师;

  4. [占小狼](https://open.weixin.qq.com/qr/code?username=whywhy_zj),美团基础架构部技术专家;

  5. [杨晓峰](https://open.weixin.qq.com/qr/code?username=gh_9f3b2a4e2a74),前甲骨文首席工程师。


如果英文阅读没问题的话,你可以关注[Cliff Click](http://cliffc.org/blog/)、[Aleksey Shipilv](https://shipilev.net/)(他的[JVM Anatomy Park](https://shipilev.net/jvm-anatomy-park/)十分有趣)和[Nitsan Wakart](http://psy-lob-saw.blogspot.com/)的博客。


你也可以关注[Java Virtual Machine Language Submit](http://openjdk.java.net/projects/mlvm/jvmlangsummit/)和[Oracle Code One](https://www.oracle.com/code-one/index.html)(前身是JavaOne大会)中关于Java虚拟机的演讲,以便掌握Java的最新发展动向。


当然,如果对GraalVM感兴趣的话,你可以订阅我们团队的[博客](https://medium.com/graalvm)。之后我会考虑逐一进行翻译。


至于其他阅读材料,你可以参考R大的这份[书单](https://www.douban.com/doulist/2545443/),或者这个[汇总贴](https://github.com/deephacks/awesome-jvm)。


如果本专栏已经激发了你对Java虚拟机的学习热情,那么我建议你着手阅读HotSpot源代码,并且回馈OpenJDK开源社区。这种回馈并不一定是提交patch,也可以是bug report或者改进建议等等。


道阻且长,努力加餐~!


可以说,Java虚拟机就是每一位Java工程师进阶加薪的利器,你想往上升,你想深入技术,不想一直停留在简单开发,或者你在做Java性能分析、调优工作时,那么,Java虚拟机绝对是一把助力的利剑。


推荐我的完结专栏:《深入拆解Java虚拟机》给你。