遵循冯诺依曼计算机结构
计算机处理数据过程
本质上就是CPU取数据指令然后返回
CPU=存储器+运算器+控制器
.
机器语言
我们把CPU能够直接认识的数据指令,称为机器语言,也就是010101001这种形式
不同厂商的 CPU
不同 CPU 使用的 CPU 指令集是不一样的,这就会有不兼容的问题;而且要是直接操作 01 这种形式的,非常麻烦并且容易出错,硬件资源管理起来也不方便。
操作系统
官方文档
JDK Reference
JDK8 JVM Reference
package com.example.jvm.clazz;
class Person {
private String name = "Jack";
private int age;
private final double salary = 100;
private static String address;
private final static String hobby = "Programming";
private static Object obj = new Object();
public void say() {
System.out.println("person say...");
}
public static int calc(int op1, int op2) {
op1 = 3;
int result = op1 + op2;
Object obj = new Object();
return result;
}
public static void main(String[] args) {
calc(1, 2);
}
}
编译生成 class 文件:javac -g:vars Person.java
vim Person.class
前期编译
源文件Person.java
vim Person.class
,然后输入:%!xxd
就是以16进制显示class文件了,内容如下:
Tips
linux下查看二进制文件
以十六进制格式输出:
od [选项] 文件
od -d 文件 十进制输出
-o 文件 八进制输出
-x 文件 十六进制输出
xxd 文件 输出十六进制
在vi命令状态下:
:%!xxd :%!od 将当前文本转化为16进制格式
:%!xxd -c 12 每行显示12个字节
:%!xxd -r 将当前文本转化回文本格式
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
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];
}
分析
u4
magic;
The magic
item supplies the magic number identifying the class
file format; it has the value 0xCAFEBABE
.
提供了一个魔法值来声明类文件格式,对应值为cafe babe
u2
minor_version;u2
major_version;0000 0034
16进制的,对应10进制:16*3+4=52,标识 JDK8
u2
constant_pool_count;The value of the constant_pool_count
item is equal to the number of entries in the constant_pool
table plus one. A constant_pool
index is considered valid if it is greater than zero and less than constant_pool_count
, with the exception for constants of type long
and double
noted in §4.4.5.
003f
表示有 3*16+15=62 个元素在常量池中(下标区间[1,constant_pool_count
-1])。
cp_info
constant_pool[constant_pool_count-1];
The constant_pool
is a table of structures (§4.4) representing various string constants, class and interface names, field names, and other constants that are referred to within the ClassFile
structure and its substructures. The format of each constant_pool
table entry is indicated by its first "tag" byte.
The constant_pool
table is indexed from 1 to constant_pool_count
- 1.
常量池主要存储两方面内容
:字面量(Literal)和符号引用(Symbolic References)
字面量
:文本字符串,final修饰等符号引用
:类和接口的全限定名、字段名称和描述符、方法名称和描述符javap -v -p Person.class > vp.txt 反编译验证字节码和指令信息
// 打开 vp.txt
Classfile /Users/xiazhaoyang/Ashe/workspace/architectrue-adventure/code-examples/jdk-jvm-analysis/src/main/java/com/example/jvm/clazz/Person.class
Last modified 2020-3-31; size 982 bytes
MD5 checksum de6394397e12fac0f518b4de12f6cef9 // 魔法值
class com.example.jvm.clazz.Person
minor version: 0
major version: 52 // JDK 版本号
flags: ACC_SUPER
Constant pool: // 常量池
#1 = Methodref #10.#43 // java/lang/Object."<init>":()V
#2 = String #44 // Jack
#3 = Fieldref #13.#45 // com/example/jvm/clazz/Person.name:Ljava/lang/String;
#4 = Double 100.0d
#6 = Fieldref #13.#46 // com/example/jvm/clazz/Person.salary:D
#7 = Fieldref #47.#48 // java/lang/System.out:Ljava/io/PrintStream;
#8 = String #49 // person say...
#9 = Methodref #50.#51 // java/io/PrintStream.println:(Ljava/lang/String;)V
#10 = Class #52 // java/lang/Object
#11 = Methodref #13.#53 // com/example/jvm/clazz/Person.calc:(II)I
#12 = Fieldref #13.#54 // com/example/jvm/clazz/Person.obj:Ljava/lang/Object;
#13 = Class #55 // com/example/jvm/clazz/Person
#14 = Utf8 name
#15 = Utf8 Ljava/lang/String;
#16 = Utf8 age
#17 = Utf8 I
#18 = Utf8 salary
#19 = Utf8 D
#20 = Utf8 ConstantValue
#21 = Utf8 address
#22 = Utf8 hobby
#23 = String #56 // Programming
#24 = Utf8 obj
#25 = Utf8 Ljava/lang/Object;
#26 = Utf8 <init>
#27 = Utf8 ()V
#28 = Utf8 Code
#29 = Utf8 LocalVariableTable
#30 = Utf8 this
#31 = Utf8 Lcom/example/jvm/clazz/Person;
#32 = Utf8 say
#33 = Utf8 calc
#34 = Utf8 (II)I
#35 = Utf8 op1
#36 = Utf8 op2
#37 = Utf8 result
#38 = Utf8 main
#39 = Utf8 ([Ljava/lang/String;)V
#40 = Utf8 args
#41 = Utf8 [Ljava/lang/String;
#42 = Utf8 <clinit>
#43 = NameAndType #26:#27 // "<init>":()V
#44 = Utf8 Jack
#45 = NameAndType #14:#15 // name:Ljava/lang/String;
#46 = NameAndType #18:#19 // salary:D
#47 = Class #57 // java/lang/System
#48 = NameAndType #58:#59 // out:Ljava/io/PrintStream;
#49 = Utf8 person say...
#50 = Class #60 // java/io/PrintStream
#51 = NameAndType #61:#62 // println:(Ljava/lang/String;)V
#52 = Utf8 java/lang/Object
#53 = NameAndType #33:#34 // calc:(II)I
#54 = NameAndType #24:#25 // obj:Ljava/lang/Object;
#55 = Utf8 com/example/jvm/clazz/Person
#56 = Utf8 Programming
#57 = Utf8 java/lang/System
#58 = Utf8 out
#59 = Utf8 Ljava/io/PrintStream;
#60 = Utf8 java/io/PrintStream
#61 = Utf8 println
#62 = Utf8 (Ljava/lang/String;)V
// 字段表集合
{
private java.lang.String name;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE
private int age;
descriptor: I
flags: ACC_PRIVATE
private final double salary;
descriptor: D
flags: ACC_PRIVATE, ACC_FINAL
ConstantValue: double 100.0d
private static java.lang.String address;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC
private static final java.lang.String hobby;
descriptor: Ljava/lang/String;
flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL
ConstantValue: String Programming
private static java.lang.Object obj;
descriptor: Ljava/lang/Object;
flags: ACC_PRIVATE, ACC_STATIC
// 方法表集合
com.example.jvm.clazz.Person();
descriptor: ()V
flags:
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #2 // String Jack
7: putfield #3 // Field name:Ljava/lang/String;
10: aload_0
11: ldc2_w #4 // double 100.0d
14: putfield #6 // Field salary:D
17: return
LocalVariableTable:
Start Length Slot Name Signature
0 18 0 this Lcom/example/jvm/clazz/Person;
public void say();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #8 // String person say...
5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/example/jvm/clazz/Person;
public static int calc(int, int);
descriptor: (II)I
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=4, args_size=2
0: iconst_3
1: istore_0
2: iload_0
3: iload_1
4: iadd
5: istore_2
6: new #10 // class java/lang/Object
9: dup
10: invokespecial #1 // Method java/lang/Object."<init>":()V
13: astore_3
14: iload_2
15: ireturn
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 op1 I
0 16 1 op2 I
6 10 2 result I
14 2 3 obj Ljava/lang/Object;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: iconst_1
1: iconst_2
2: invokestatic #11 // Method calc:(II)I
5: pop
6: return
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 args [Ljava/lang/String;
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: new #10 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: putstatic #12 // Field obj:Ljava/lang/Object;
10: return
}
上面分析到常量池中常量的数量是62,接下来我们来具体分析一下这62个常量:
cp_info {
u1 tag;
u1 info[];
}
常量池标识符
Constant Type | Value |
---|---|
CONSTANT_Class | 7 |
CONSTANT_Fieldref | 9 |
CONSTANT_Methodref | 10 |
CONSTANT_InterfaceMethodref | 11 |
CONSTANT_String | 8 |
CONSTANT_Integer | 3 |
CONSTANT_Float | 4 |
CONSTANT_Long | 5 |
CONSTANT_Double | 6 |
CONSTANT_NameAndType | 12 |
CONSTANT_Utf8 | 1 |
CONSTANT_MethodHandle | 15 |
CONSTANT_MethodType | 16 |
CONSTANT_InvokeDynamic | 18 |
常量池中的每一个元素必须以 1 byte
的标识符号作为开始,以此来表示它的具体 cp_info
类型。上表中列出的为官方声明合法的标识符。
00000000: cafe babe 0000 0034 003f -> 0a00 0a00 2b08 .......4.?....+.
00000010: 002c 0900 0d00 2d06 4059 0000 0000 0000 .,....-.@Y......
0a
,表示10,对应 CONSTANT_Methodref
,表示这是一个方法引用。CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
往下数一个 u1
: 00 标示起始位,对应其具体的 cp_info 类型
往下数两个 u2
00 0a
对应 10, 代表的是class_index,表示该方法所属的类在常量池中的索引
00 2b
对应 32+11=43,代表的是name_and_type_index,表示该方法的名称和类型的索引
#1 = Methodref #10.#43 // java/lang/Object."<init>":()V
CONSTANT_String
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
根据之前的规律,第一个 u1 表示类型,往后数一个 u2 表示对应存储指向的索引位置
00000000: cafe babe 0000 0034 003f 0a00 0a00 2b08 .......4.?....+.
00000010: -> 002c 0900 0d00 2d06 4059 0000 0000 0000 .,....-.@Y......
002c
= 32 + 12 = 44;
#2 = String #44 // Jack
后续的依此类推,简而言之,Java 通过这种方式来表述一个类文件的结构。
所谓类加载机制就是
查找和导入class文件
Class 对象封装了类在方法区内的数据结构,并且向 Java 程序员提供了访问方法区内的数据结构的接口。在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口:
保证被加载类的正确性
为类的静态变量分配内存,并将其初始化为默认值
graph TD
A[方法区]
A-->A1[静态变量的初始化 int age=10]
A1 --> B[堆]
public class Demo1 {
private static int i;
public static void main(String[] args) {
// 正常打印出0,因为静态变量i在准备阶段会有默认值0
System.out.println(i);
}
}
public class Demo2 {
public static void main(String[] args) {
// 编译通不过,因为局部变量没有赋值不能被使用 int i;
System.out.println(i);
}
}
将前文分析的类文件结构中的符号引用转换为直接引用
符号引用就是一组符号来描述目标,可以是任何字面量。
直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
对类的静态变量,静态代码块执行初始化操作
graph TD
A[方法区]
A-->A1[将类中的符号引用转换为直接引用]
A1 --> B[堆]
在装载 (Load) 阶段,其中第(1)步:通过类的全限定名获取其定义的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载Class 文件的。
不是ClassLoader子类
。public class ClassLoaderTest {
public static void main(String[] args) {
// App ClassLoader
System.out.println(new ClassLoaderTest().getClass().getClassLoader());
// Ext ClassLoader
System.out.println(new ClassLoaderTest().getClass().getClassLoader().getParent());
// Bootstrap ClassLoader
System.out.println(new ClassLoaderTest().getClass().getClassLoader().getParent().getParent());
System.out.println(new String().getClass().getClassLoader());
}
}
输出
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@59690aa4
null
null
自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加 载,就视为已加载此类,保证此类只所有ClassLoader加载一次。
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 如果父加载器存在,委托给父类加载器加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
JVM 文件结构是理解 JVM 运行时数据区和内存模型的必备知识,简单总结下: