Java面试干货之字节码(面试系列)

560 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

黄沙百战穿金甲,不破楼兰终不还。

哎哎哎...这...我需要了解这个吗?

???

相信很多人会有这种疑问,毕竟在实际工作中可能一点都用不到,面试呢,也几乎不会问到。 但是!还是多了解一点嘛,毕竟这东西如果面试问到你,然而你连JVM执行的什么东西都不知道,怎么好意思说你了解JVM呢?

卑微

字节码

首先CPU是最基础的底层硬件,而机器码是离CPU指令集最近的编码,是CPU可以直接解读的指令,因此机器码肯定是与底层硬件系统耦合的。但如果某个程序因为不同的硬件平台需要编写多套代码,那岂不是令人崩溃?

崩溃

Java的使命就是一次编写,到处执行,这里的到处是指在不同的操作系统、不同的硬件平台上,均可以不用修改代码都可以顺畅的运行。

拽

那Java如何实现跨平台的呢?接下来这句话是我在一本书上看到的,说是计算机工程领域的任何问题都可以通过增加一个中间层来解决。那中间码,也就是字节码(Bytecode)应运而生。

简单来说Java的所有指令有200个左右,一个字节(8位)可以存256种不同的指令信息,一个这样的字节成为字节码。这里说一下JVM处理字节码的三种方式吧

1.解释执行:字节码进入JVM后,JVM一边扫描一边翻译(这里我理解的翻译是翻译成机器码,后续如果哪位大佬觉得不对劲可以在评论区指出),JVM扫描翻译一句计算机就执行一句,这样JVM就屏蔽了对底层操作系统的依赖。

2.编译执行:如果是热点代码,JVM会执行JIT动态的编译为机器码,这样会提高执行的效率。当然这样做也会有相应的时间和空间上的开销,对于一般的Java方法,编译后代码的大小相对于字节码的大小,膨胀比达到10x是很正常的,所以只有对执行频繁的代码才值得编译。

3.JIT编译与解释混合执行:顾名思义,也就是以上两种执行方式混合执行,其优势在于解释器在启动时先解释执行,省去编译时间。随着时间推进,JVM通过热点代码统计分析,识别高频的方法调用、循环体、公共模块等,基于强大的JIT动态编译技术,将代码转换为机器码,直接交给CPU执行。

这也就解释了为什么有些JVM会选择不总是做JIT编译,而是选择用解释器+JIT编译器的混合执行引擎。为什么说有些呢?因为JVM的提供商有很多,比如Sun、IBM、BEA等。现在目前OpenJDK使用的主流JVM是Oracle公司的HotSpot JVM,它也是采用的解释器加编译器混合执行的模式。

呃呃呃,好像有点跑题了...

???

接下来我们来看一下字节码长什么样子

跑题

简单说一下吧,起始的四个字节比较特殊,绿色框的cafe babe是Gosling(Java之父)定义的一个魔法数,就是字面意思,Coffee Baby(咖啡宝贝),至于为啥这么叫,可能跟Java的咖啡标志性图标有点什么关系吧...咳咳...言归正传,它的作用是标志改文件是一个Java类文件,如果没有识别到,那就说明是其他文件或者文件受损了,无法进行加载。而红色框代表的呢就是当前的版本号,0x37的十进制是55,是JDK11的内部版本号。

最后列举一些主要的字节码指令,这些指令会编译成为字节码的一个字节:

加载或存储指令:在某个栈帧中,通过该指令操作数据在虚拟机栈的局部变量表与操作栈之间来回传输,常见指令有:

1.将局部变量加载到操作栈中:如ILOAD(将int类型的局部变量压入栈)、ALOAD(将对象引用的局部变量压入栈)等。  

2.从操作栈顶存储到局部变量表:如ISTORE、ASTORE等。

3.将常量加载到操作栈顶,这是极为高频使用的指令:如ICONST、BIPUSH、SIPUSH、LDC等。

ICONST 加载的是-1~5的数(ICONST与BIPUSH的加载界限)。

BIPUSH,即Byte Immediate PUSH,加载-128~127之间的数。

SIPUSH,即Short Immediate PUSH,加载-32768~32767之间的数。

LDC,即Load Constant,在-2147483648~2147483647或者是字符串时,JVM采用LDC指令压入栈中。

运算指令:对两个操作栈帧上的指进行运算,并把结果写入操作栈顶,如IADD、IMUL等。

类型转换指令:显示转换两种不同的数值类型。如I2L、D2F等。

对象创建与访问指令:根据类进行对象的创建、初始化、方法调用相关指令,常见指令如下: 1.创建对象指令:如NEW、NEWARRAY等。 2.访问属性指令:如GEIFIELD、PUTFIELD、GETSTATIC等。 3.检查实例类型指令:如INSTANCEOF、CHECKCAST等。

操作栈管理指令:JVM提供了直接控制操作栈的指令,常见指令如下: 1.出栈操作:如POP即一个元素,POP2即两个元素。 2.复制栈顶元素并压入栈:如DUP。

方法调用和返回指令:有INVOKEVIRTUAL、INVOKESPCIAL、INVOKESTATIC、RETURN指令等。

同步指令:JVM使用方法结构中的ACC_SYNCHRONIZED标志同步方法,指令集中有MONITORENTER和MONITOREXIT支持synchronized语义。 字节码中,除了字节码指令以外,还包含一些额外的信息,如字节码与源码行号之间的对应关系,方便调式代码时候能正确的定位到所在的代码行,还有存储当前方法中使用到的局部变量表。

怎么样?是不是觉得很短很简单?其实作为Java开发人员,简单了解到字节码这种JVM的基础的东西,也不是需要花很长的时间,就可能上班路上或者回家路上花个一两分钟看一下,几天就能够完全记住了,那以后不是又有点小东西可以作为融入大佬的谈资了呢?

开心

后记

从幼儿园到小学, 从小学到初中, 初中到高中, 高中到大学, 学习从来都不是一朝一夕的事, 课堂上明白了, 没有课后练习的巩固,没有考前的复习, 甚至连最简单基础的公式都不记得, 坚持学习,贵在坚持, 长沙百战穿金甲,不破楼兰终不还。 与君共勉。

同时也希望大家可以关注我的公众号【类似程序员】,会定期与大家分享我的一些想法与学习感悟,谢谢各位!