科普
机器码
- 各种用 二进制编码 方式表示的指令,叫做机器指令码。开始,人们就用它编写程序,这就是机器语言。
- 机器语言虽然能够被计算机理解和接受,但和人们的语言差别太大,不易被人们理解和记忆,并且用它编程容易出差错。
- 用它编写的程序一经输入计算机,CPU 直接读取运行,因此和其他语言编的程序相比,执行速度最快。
- 机器指令与 CPU 紧密相关,所以不同种类的 CPU 所对应的机器指令也就不同。
指令
- 由于机器码是有 0 和 1 组成的二进制序列,可读性实在太差,于是人们发明了指令。
- 指令就是把机器码中特定的 0 和 1 序列,简化成对应的指令(一般为英文简写,如 mov,inc ,add等),可读性稍好
- 由于不同的硬件平台,执行同一个操作,对应的机器码可能不同,所以不同的硬件平台的同一种指令(比如mov),对应的机器码也可能不同。
指令集
-
不同的硬件平台,各自支持的指令,是有差别的。因此每个平台所支持的指令,称之为对应平台的指令集。 如常见的
- x86 指令集,对应的是 x86 架构的平台
- ARM 指令集,对应的是 ARM 架构的平台
汇编语言
- 由于指令的可读性还是太差,于是人们又发明了汇编语言。
- 在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址。
- 在不同的硬件平台,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。
- 由于计算机只认识指令码,所以用汇编语言编写的程序还必须翻译成机器指令码,计算机才能识别和执行。
高级语言
- 为了使计算机用户编程序更容易些,后来就出现了各种高级计算机语言。高级语言比机器语言、汇编语言更接近人的语言
- 当计算机执行高级语言编写的程序时,仍然需要把程序解释和编译成机器的指令码。完成这个过程的程序就叫做解释程序或编译程序。
JVM
-
JVM是
Java Virtual Machine
的缩写,即Java虚拟机。JVM是一个虚构出来的计算机,是一种用于计算设备的规范。-
JVM 是虚构的计算机,与物理机进行对比
- 冯诺依曼计算机模型
- Java虚拟机
- JVM虚拟机类比于物理机的设计:class文件<==>输入设备、CPU指令集<==>输出设备、JVM运行时数据区<==>存储器和控制器以及运算器等
- 冯诺依曼计算机模型
-
引入 JVM 后,Java语言在不同平台上运行时不需要重新编译。Java语言使用Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码字节码,就可以在多种平台上不加修改地运行。
- Java语言:即JVM支持的多种编译语言,这些语言的源文件都可以编译成class文件,然后在JVM中运行。如:Java、Kotlin、Groovy、Scala等
-
JVM 能够把class文件翻译成不同平台的CPU指令集 ,也是Write Once Run Anywhere的保证
-
-
JVM既然是一种规范,那么就有对于该规范的不同的实现。不同公司针对 JVM 有许多各自的产品,称为 JVM Products。
-
常用的JVM Products有:sun公司的HotSpot和JRockit、阿里的TaobaoVM、IBM公司的 J9VM、Zual公司的Zing
-
可以通过
Java -version
命令查看所使用的JVM
-
-
JDK、JRE、JVM关联关系
- JRE(Java Runtime Enviroment)是 Java 的运行环境,是运行 Java 程序所必须环境的集合,包含 JVM 标准实现及 Java 核心类库
- JDK(Java Development Kit)是 Java 开发工具包,包括了 Java 运行环境(JRE),一堆 Java 工具 tools.jar 和 Java 标准类库 (rt.jar)
-
HotSpot结构图
-
相关文档:
class文件
-
JVM 中运行的是Java语言经过编译后的class文件,那么针对于class文件结构进行一番探究
-
编写
User.java
文件public class User { private String name = "Tom"; private Integer age; private String address; public static Integer calculate(Integer v1, Integer v2) { v1 = 1; return v1 + v2; } public static void main(String[] args) { System.out.println(calculate(1, 2)); } }
-
经过
Javac User.java
命令编译之后产生User.class
文件,通过工具Sublime Text 打开之后实际上是16进制文件cafe babe 0000 0037 0032 0a00 0a00 1a08 001b 0900 0900 1c0a 001d 001e 0a00 1d00 1f09 0020 0021 0a00 0900 220a 0023 0024 0700 2507 0026 0100 046e 616d 6501 0012 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 0100 0361 6765 0100 134c 6a61 7661 2f6c 616e 672f 496e 7465 6765 723b 0100 0761 6464 7265 7373 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 0963 616c 6375 6c61 7465 0100 3b28 4c6a 6176 612f 6c61 6e67 2f49 6e74 6567 6572 3b4c 6a61 7661 2f6c 616e 672f 496e 7465 6765 723b 294c 6a61 7661 2f6c 616e 672f 496e 7465 6765 723b 0100 046d 6169 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 0100 0a53 6f75 7263 6546 696c 6501 0009 5573 6572 2e6a 6176 610c 0010 0011 0100 0354 6f6d 0c00 0b00 0c07 0027 0c00 2800 290c 002a 002b 0700 2c0c 002d 002e 0c00 1400 1507 002f 0c00 3000 3101 0004 5573 6572 0100 106a 6176 612f 6c61 6e67 2f4f 626a 6563 7401 0011 6a61 7661 2f6c 616e 672f 496e 7465 6765 7201 0007 7661 6c75 654f 6601 0016 2849 294c 6a61 7661 2f6c 616e 672f 496e 7465 6765 723b 0100 0869 6e74 5661 6c75 6501 0003 2829 4901 0010 6a61 7661 2f6c 616e 672f 5379 7374 656d 0100 036f 7574 0100 154c 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d3b 0100 136a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 0100 0770 7269 6e74 6c6e 0100 1528 4c6a 6176 612f 6c61 6e67 2f4f 626a 6563 743b 2956 0021 0009 000a 0000 0003 0002 000b 000c 0000 0002 000d 000e 0000 0002 000f 000c 0000 0003 0001 0010 0011 0001 0012 0000 0027 0002 0001 0000 000b 2ab7 0001 2a12 02b5 0003 b100 0000 0100 1300 0000 0a00 0200 0000 0100 0400 0300 0900 1400 1500 0100 1200 0000 2e00 0200 0200 0000 1204 b800 044b 2ab6 0005 2bb6 0005 60b8 0004 b000 0000 0100 1300 0000 0a00 0200 0000 0900 0500 0a00 0900 1600 1700 0100 1200 0000 2e00 0300 0100 0000 12b2 0006 04b8 0004 05b8 0004 b800 07b6 0008 b100 0000 0100 1300 0000 0a00 0200 0000 0f00 1100 1000 0100 1800 0000 0200 19
-
内容代表的含义可以通过官网文档进行查看翻译,对应的文档地址:Chapter 4. The class File Format (oracle.com)
-
通过章节《4.1. The ClassFile Structure》可以看到Class结构:
-
根据官网文档尝试解析Class文件:
-
首先是
u4 magic;
其中u4
代表无符号的四位。对应User.class
文件中的cafe babe
。结合文档描述的内容也就是说标准的class文件,是以0xCAFEBABE
开头,相当于一个校验位。 -
然后是
u2 minor_version;u2 major_version
其中u2
代表无符号的两位。结合文档描述,这两个字段是class文件编译所使用的版本。对应User.class
文件中的0000 0037
。0037
由16进制转为10进制后是55,55对应的Java版本是11 -
然后是
u2 constant_pool_count;
其中u2
代表无符号的两位。结合文档描述,该字段是constant_pool中的常量数量+1。对应User.class
文件中的0032
,转为10进制之后是50,代表有50-1=49
个常量(常量=字面量+final修饰)。 -
然后是
cp_info constant_pool[constant_pool_count-1]
,代表常量信息,具体内容通过结构说明文档解析即可 -
解析后整理之后含义如下
ClassFile { u4 magic; // 标准的class对应的值固定为cafe babe u2 minor_version; u2 major_version; // 对应User.class文件中的0000 0037,转为10进制后是55,对应的java版本是11 u2 constant_pool_count; // 常量数量 cp_info constant_pool[constant_pool_count-1];// 常量的值 u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
-
-
-
java文件在经过Javac编译之后变为class文件。实际 上是通过编译原理,将源码文件按照class文件的结构翻译成了class文件。
- javac编译,即从源码文件经过编译到生成字节码文件的过程。java=>编译=>class
- java文件=> 词法分析=> tokens流=> 语法分析=> 语法树/抽象语法树=> 语义分析=> 注解抽象语法树=> 字节码生成器=> class文件
-
class文件在 JVM 编译之后,最终会变成汇编指令交给计算机执行,可以通过命令查看汇编信息
# 令反汇编class文件,将生成的汇编信息生成到`User.txt`文件中 javap -c -p -v D:\code\java\test\src\User.class > D:\code\java\test\src\User.txt
类加载机制
-
将class文件交给 JVM 去运行,这个过程叫做类加载。参考官网文档:Chapter 5. Loading, Linking, and Initializing (oracle.com)
-
类加载过程:
-
Loading(装载)
- class文件是存储到磁盘上的,要找到class文件的全限定名,获取文件的二进制流,然后装载到内存中,装载过程使用到类加载器ClassLoader
- 将二进制流中的静态存储结构转化为运行时数据结构(静态存储结构指的是class文件的十六进制代表的内容实际是在硬盘上的)
-
Linking(链接)。链接又分为三步:
- verify:验证class文件的格式,保证加载类的正确性
- prepareing:为类的静态变量分配内存,并初始化为默认值(例如代码:private static int a = 10 ; 在此阶段会为a分配内存并且赋值为0)
- resolve:将运行时常量池中的符号引用转为直接引用(class文件中的十六进制数据就是符号引用,直接引用指的是在内存中的地址如:0x001-0x005)
-
Initializing(初始化):
- 对类的静态变量、静态代码块内容执行初始化操作。(例如代码:private static int a = 10 ; 在此阶段会将a赋值为10)
-
经过Loading、Linking、Initializing之后,成功将class文件放到运行时数据区
常量池指的是class文件结构中的constant_pool,是在硬盘上的
运行时常量池指的是,将class文件结构中的constant_pool放到内存上的状态
-
-
类加载时机
- 一个类在什么时候开始被加载,Java虚拟机规范中并没有进行强制约束,而是交给了虚拟机自己去自由实现。HotSpot虚拟机是按需加载,在需要用到该类的时候加载这个类
- 类的初始化是在加载的时候完成的
-
类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例
-
针对不同类型的class文件,由不同的ClassLoader进行加载,如下:
-
类加载器使用双亲委派模式,解决不同包下存在相同全路径名称文件的问题
- 检查某个类是否已经加载 自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加 载,就视为已加载此类,保证此类只被加载一次。
- 加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。
-
使用自定义类加载器可以实现如下功能:
- 执行代码前自动验证数字签名;
- 根据用户提供的密码解密代码,从而可以实现代码混淆器来避免反编译class文件;
- 根据应用需求把其他数据以字节码的形式加载到应用中
-
-
只有被同一个类加载器实例加载并且文件名相同的class文件才被认为是同一个class.