作者:郑雨迪

 

“有代码的地方,就有江湖。”

 

程序员,就是“一人,一键,二机”行走其间的孤独剑客。我们游走代码江湖,弹指间,便可掀起一场风雨变革。而在江湖中狂荡,必然要练就绝世武功,则需要内外兼备:精妙的招式,加之深厚的内功。武功的基础是内功,一个内功低的人招式再奇妙,也打不过一个内功深厚之人。同样两者也是相辅相成,内功深厚,原来的一招一式威力也会倍增。

 

对于开发者来说,其道理也是一样。流行的框架越来越多,封装也越来越完善,各种框架可以搞定一切。初级程序员只要熟悉基本的使用方法,几乎不用关注底层的实现,便可以快速地开发上线。但对于想要进阶的你来说,更要注重内功,比如算法、设计模式、底层原理等等。只有把基础打扎实,才能知其然知其所以然,出现Bug能快速发现问题本质。

 

我在Java虚拟机性能优化方面有着多年的研究,深知Spring全家桶是精妙的招式,JVM就是内功心法很重要的一块。线上出现性能问题,JVM调优更是不可回避的问题。但又因Java虚拟机封装得太好,让我们几乎感觉不到它的存在,可学习Java虚拟机对于高级程序员来说,其重要性是不言而喻的。我司在面试高级开发的时候,JVM相关知识也必定是考核的标准之一。

 

下面这篇文章集锦了阿里、美团、Oracle等大厂的JVM考点,你看看是否会能答得上来?

 

  1. 什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

  2. Java代码是怎么运行的?

  3. Java虚拟机是如何加载Java类的?

  4. JVM运行内存的分类

  5. 如何监控和诊断JVM堆内和堆外内存使用?

  6. Java四引用是什么?

  7. 如何理解JVM内置的编译或GC日志?

  8. JVM的永久代中会发生垃圾回收么?

  9. Java中的两种异常类型是什么?他们有什么区别?

  10. JVM是如何实现同步的?

  11. Java内在模型是什么?

  12. 即使编译器有哪些优化?

  13. 在什么情况下重复读写操作会被优化?

  14. 什么样的垃圾才被回收?

  15. 什么时候会导致垃圾回收?

  16. 如何利用JFR和JMC监控Java程序?

  17. 如何利用Unsafe API 绕开 JVM的控制?

  18. 如何利用字节码注入为已有代码加料?

……

 

根据我专栏的内容,我挑选了几个问题进行解答,希望能对大家面试起到帮助。

 

1、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

 

Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。

 

Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

 

2、Java代码是怎么运行的?

 

这个问题可以分三块来回答:

  1. 为什么Java要在虚拟机里运行?

  2. Java虚拟机具体是怎样运行Java字节码的?

  3. Java虚拟机的运行效率究竟是怎么样的?

 

Java之所以要在虚拟机中运行,是因为它提供了可移植性。一旦Java代码被编译为Java字节码,便可以在不同平台上的Java虚拟机实现上运行。此外,虚拟机还提供了一个代码托管的环境,代替我们处理部分冗长而且容易出错的事务,例如内存管理。

 

Java虚拟机将运行时内存区域划分为五个部分,分别为方法区、堆、PC寄存器、Java方法栈和本地方法栈。Java程序编译而成的class文件,需要先加载至方法区中,方能在Java虚拟机中运行。

 

JVM知识点总览:高级Java工程师面试必备_加载

为了提高运行效率,标准JDK中的HotSpot虚拟机采用的是一种混合执行的策略。首先,它会解释执行Java字节码,然后会将其中反复执行的热点代码,以方法为单位进行即时编译,翻译成机器码后直接运行在底层硬件之上。HotSpot装载了多个不同的即时编译器,以便在编译时间和生成代码的执行效率之间做取舍。

JVM知识点总览:高级Java工程师面试必备_字节码_02

3、Java虚拟机是如何加载Java类的?

 

Java虚拟机将字节流转化为Java类的过程,可分为加载、链接以及初始化三大步骤。也可以用盖房子来类比Java虚拟机中的类加载。

 

加载是指查找字节流,并且据此创建类的过程。以盖房子为例,村里的Tony要盖个房子,那么按照流程他得先找个建筑师,跟他说想要设计一个房型,比如说“一房、一厅、四卫”。这里的房型相当于类,而建筑师,就相当于类加载器。村里有许多建筑师,他们等级森严,但有着共同的祖师爷,叫启动类加载器(boot class loader)。

 

加载需要借助类加载器,在Java虚拟机中,类加载器使用了双亲委派模型,即接收到加载请求时,会先将请求转发给父类加载器。

 

链接,是指将创建成的类合并至Java虚拟机中,使之能够执行的过程。链接还分验证、准备和解析三个阶段。其中,解析阶段为非必须的。

 

初始化,则是为标记为常量值的字段赋值,以及执行<clinit>方法的过程。类的初始化仅会被执行一次,这个特性被用来实现单例的延迟初始化。这放在我们盖房子的例子中就是,只有当房子装修过后,Tony才能真正地住进去。

 

想了解更多JVM内容,可订阅我的《深入拆解Java虚拟机》专栏。

 

 

4、如何监控和诊断JVM堆内和堆外内存使用?

了解 JVM 内存的方法有很多,具体能力范围也有区别,简单总结如下:

 

可以使用综合性的图形化工具,如 JConsole、VisualVM(注意,从 Oracle JDK 9 开始,VisualVM 已经不再包含在 JDK 安装包中)等。这些工具具体使用起来相对比较直观,直接连接到 Java 进程,然后就可以在图形化界面里掌握内存使用情况。

以 JConsole 为例,其内存页面可以显示常见的堆内存和各种堆外部分使用状态。

 

也可以使用命令行工具进行运行时查询,如 jstat 和 jmap 等工具都提供了一些选项,可以查看堆、方法区等使用数据。

 

或者,也可以使用 jmap 等提供的命令,生成堆转储(Heap Dump)文件,然后利用 jhat 或 Eclipse MAT 等堆转储分析工具进行详细分析。

 

如果你使用的是 Tomcat、Weblogic 等 Java EE 服务器,这些服务器同样提供了内存管理相关的功能。

 

另外,从某种程度上来说,GC 日志等输出,同样包含着丰富的信息。

 

这里有一个相对特殊的部分,就是是堆外内存中的直接内存,前面的工具基本不适用,可以使用 JDK 自带的 Native Memory Tracking(NMT)特性,它会从 JVM 本地内存分配的角度进行解读。

 

5、JVM的永久代中会发生垃圾回收么?

垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,会触发完全垃圾回收(Full GC)。如果你仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。这就是为什么正确的永久代大小对避免Full GC是非常重要的原因。

 

(注:Java8中已经移除了永久代,新加了一个叫做元数据区的native内存区) 异常处理

 

6、在Java中,对象什么时候可以被垃圾回收?

当对象对当前使用这个对象的应用程序变得不可触及的时候,这个对象就可以被回收了。

 

7、Java中的两种异常类型是什么?他们有什么区别?

Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用throws语句在方法或者是构造函数上声明。这里有Java异常处理的一些小建议。

 

8、JVM垃圾回收算法

标记-清除算法:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。

 

复制算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当一块内存用完了,将还存另外一块上面,然后在把已使用过的内存空间一次清理掉。

 

标记-整理算法:标记过程与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所一端移动,然后直接清理掉端边界以外的内存。

 

分代收集算法:一般是把Java堆分为新生代和老年代,根据各个年代的特点采用最适当的收集算法。新生代都发现有大批对象死去,选用复制算法。老年代中因为对象存活率高,必须使用“标记-清理”或“标记-整理”算法来进行回收。

 

更新了几篇文章后,大家留言也很踊跃,有些留言也是蛮精彩的,现分享给大家,一起共同进步。高手在人间,我只是个抛砖引玉之人。

 

JVM知识点总览:高级Java工程师面试必备_java虚拟机_03