前言

视频教程:https://www.bilibili.com/video/BV1Dz4y1A7FB?p=2&vd_source=85ac5ee1b07df12a44b648a8751d30f6

相关知识点

JVM的生命周期

1
2
3
4
5
6
7
- 启动
Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定。
- 退出
某线程调用Runtime类或System类的exit方法,或 Runtime类的halt方法,并且Java安全管理器也允许这次exit或halt操作。
程序正常执行结束
程序在执行过程中遇到了异常或错误而异常终止
由于操作系统出现错误而导致Java虚拟机进程终止

什么是JVM

1
2
3
4
- 虚拟机
虚拟的物理机,指以软件的方式模拟具有完整硬件系统功能。
- JVM
JVM是可运行字节码的虚拟机。JVM屏蔽了与具体平台相关的信息,只与“Class 文件”这种特定的二进制文件格式所关联。

JVM架构图/体系结构

image-20240323120702047

1
2
3
4
5
6
- 架构
最上层:javac编译器将编译好的字节码class文件,通过java类装载器执行机制,把对象或class文件存放在jvm划分内存区域。
中间层:称为Runtime Data Area,主要是在Java代码运行时用于存放数据的,从左至右为方法区(永久代、元数据区)、堆(共享,GC回收对象区域)、栈、程序计数器、寄存器、本地方法栈(私有)。
最下层:解释器、JIT(just in time)编译器和 GC(Garbage Collection,垃圾回收器)
- 相关知识
类加载、内存结构和分配、垃圾回收、性能监控、性能优化

image-20240323120416657

1
HotSpot虚拟机体系结构(如上图,图要会画)

Class文件里面是什么

1
源代码经过编译器编译之后便会生成一个字节码文件,是一种二进制的类文件,它的内容是JVM的指令,而不像CC++经由编译器直接生成机器码。

什么是编译器

1
JVM的编译器:将源代码编译成字节码(前端编译器)

JAVA编译器的编译步骤

1
词法解析、语法解析、语义解析、生成字节码

哪些类型对应有Class对象

1
2
3
4
5
6
7
1class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
2interface:接口
3)[]:数组
4enum:枚举
5annotation:注解@interface
6primitive type:基本数据类型
7void

JAVA半编译半解释型语言

1
2
3
JAVA的执行引擎包括:解释器和JIT
解释器:将字节码文件逐行解释成机器码
JIT:直接将热点代码编译成本地机器指令

Class文件结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
魔数:每个 Class 文件开头的4个字节的无符号整数称为魔数。它的唯一作用是确定这个文件是否为一个能被虚拟机接受的有效合法的Class文件。魔数值固定为0xCAFEBABE。不会改变。

Class文件版本:保证高版本的JVM可执行低版本的Class文件

常量池:存放所有常量。准确说存放编译时期生成的各种字面量和符号引用,在类加载后进入方法区的运行时常量池中。

访问标识(或标志):用于识别一些类或者接口层次的访问信息。包括:这个 Class 是类还是接口;是否定义为 public 类型;是否定义为 abstract 类型;如果是类的话,是否被声明为 final 等。

类索引,父类索引,接口索引集合:指定类类别、父类类别、实现的接口,确定类的全限定名。

字段表集合:描述接口或类中声明的变量。

方法表集合:描述了每个方法的签名。

属性表集合:class文件所携带的辅助信息。

常量池表

  • 常量池是一种表结构。主要存放两大类常量:字面量和符号引用。
  • 主要包含class文件结构及其子结构中引用的所有字符串常量、类或接口名、字段名和其他常量。
  • 第1个字节作为类型标记,用于确定该项的格式,这个字节称为tag byte (标记字节、标签字节)。

image-20240323171147458

  • 这14种表(或者常量项结构)的共同点是:表开始的第一位是一个u1类型的标志位(tag),代表当前这个常量项使用的是哪种表结构,即哪种常量类型。
  • 在常量池列表中,CONSTANT_Utf8_info常量项是一种使用改进过的UTF-8编码格式来存储诸如文字字符串、类或者接口的全限定名、字段或者方法的简单名称以及描述符等常量字符串信息。
  • 这14种常量项结构还有一个特点是,其中13个常量项占用的字节固定,只有CONSTANT_Utf8_info占用字节不固定,其大小由length决定。为什么呢?因为从常量池存放的内容可知,其存放的是字面量和符号引用,最终这些内容都会是一个字符串,这些字符串的大小是在编写程序时才确定,比如你定义一个类,类名可以取长取短,所以在没编译前,大小不固定,编译后,通过utf-8编码,就可以知道其长度。

符号引用、直接引用的区别

1
2
3
4
5
	Java代码在进行Javac编译的时候,并不像C和C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态链接。也就是说,在最初生成的Class文件中不会保存各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。  

符号引用和直接引用的区别与关联:
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。
直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了。

访问标识

image-20240323172441691

类索引、父类索引、接口索引集合

image-20240323172748310

  • this_class(类索引)
  • super_class (父类索引)
  • interfaces(接口索引)
  • interfaces_count (接口计数器)
  • interfaces [](接口索引集合)

面试题

println的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Father {
int x = 10;
public Father() {
this.print();
x = 20;
}
public void print() {
System.out.println("Father.x = " + x);
}
}

class Son extends Father {
int x = 30;
public Son() {
this.print();
x = 40;
}
public void print() {
System.out.println("Son.x = " + x);

}
}

public class SonTest {
public static void main(String[] args) {
Father f = new Son();
//Father f = new Father();
//Son f = new Son();
System.out.println(f.x);
}
}

输出:

Son.x = 0
Son.x = 30
20

分析:父类f指向子类Son的引用。顺序:先初始化父类,再初始化子类。

①即父类的普通代码块和普通变量:x = 30

②父类的构造方法。 ==父类的构造方法的this指向Son,调用子类的print方法,但子类的x没有赋值,默认是0,所以打印Son.x = 0。并将父类的x赋值为20==

③子类普通代码块和普通变量。子类x=30

④子类的构造方法。==子类x=30,调用子类的print方法,打印Son.x = 30。并将子类的x赋值为40==

⑤执行main中的f.x。==f类型是父类,所以取父类中的x,为20。打印20==

相关知识点:子类初始化的顺序、多态

子类初始化的顺序:

① 父类静态代码块和静态变量。

② ==子类静态代码块和静态变量。==

③ 父类普通代码块和普通变量。

④ 父类构造方法。

⑤ 子类普通代码块和普通变量。

⑥ 子类构造方法。

多态:当出现 父类 指向 子类的对象时 (向上转型)

①父类和子类出现重名属性:向上转型的时候代表父类,所以当父类和子类出现同名的属性,对象使用的是父类的。(调用属性的时候看的是左边定义的类型)

②父类和子类出现重名方法:看的是实际指向的子类对象在堆内存中的内容。(看右边实际创建的对象

③可以调用父类特有的属性或方法,不能调用子类特有的属性或方法

字节码指令都有哪些

1
2
3
4
5
6
7
8
9
加载与存储指令:xload<n>、xstore<n>等
算术指令:iadd、submuldiv
类型转换指令:i2l、l2i、i2b、b2i等
对象的创建与访问指令:new、getstatic、getfield等
方法调用与返回指令:invokevirtual、invokeinterface、invokespecial、invokestatic 、invokedynamic、ireturn、lreturn、freturn、dreturn和areturn
操作数栈管理指令:pop,pop2
控制转移指令:ifeq, iflt, ifle, ifne, ifgt, ifge
异常处理指令:athrow
同步控制指令:ACC_SYNCHRONIZED 、monitorenter 、monitorexit

方法调用指令

  • invokevirtual指令用于调用对象的实例方法,根据对象的实际类型进行分派(虚方法分派),支持多态。这也是Java语言中最常见的方法分派方式。
  • invokeinterface指令用于调用接口方法,它会在运行时搜索由特定对象所实现的这个接口方法,并找出适合的方法进行调用。
  • invokespecial指令用于调用一些需要特殊处理的实例方法,包括实例初始化方法(构造器)、私有方法和父类方法。这些方法都是静态类型绑定的,不会在调用时进行动态派发。
  • invokestatic指令用于调用命名类中的类方法(static方法)。这是静态绑定的。
  • invokedynamic:调用动态绑定的方法,这个是JDK 1.7后新加入的指令。用于在运行时动态解析出调用点限定符所引用的方法,并执行该方法。前面4条调用指令的分派逻辑都固化在 java 虚拟机内部,而 invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

字节码指令相关面试

1
2
int a = 1;JVM如何取得a的值(圆通)
Integer x = 5;int y = 5;比较 x == y 都经过哪些步骤?(百度)

Java虚拟机中,数据类型可以分为哪几类? (阿里)

1
2
3
4
1.数据类型可以分为两种:基本类型和引用类型,基本类型的变量持有原始值,而引用类型的变量持有引用值。
2. Java语言中的所有基本类型同样也都是Java虚拟机中的基本类型。但是boolean有点特别,虽然Java虚拟机也把boolean看做基本类型,但是指令集对boolean只有很有限的支持,当编译器把Java源代码编译为字节码时,它会用int或者byte来表示boolean。在Java虚拟机中,false是由整数零来表示的,所有非零整数都表示true,涉及boolean值的操作则会使用int。另外,boolean数组是当做byte数组来访问的。
3. Java虚拟机还有一个只在内部使用的基本类型:returnAddress,Java程序员不能使用这个类型,这个基本类型被用来实现Java程序中的finally子句。该类型是jsr, ret以及jsr_w指令需要使用到的,它的值是JVM指令的操作码的指针。returnAddress类型不是简单意义上的数值,不属于任何一种基本类型,并且它的值是不能被运行中的程序所修改的。
4. Java虚拟机的引用类型被统称为“引用(reference)”,有三种引用类型:类类型、接口类型、以及数组类型,它们的值都是对动态创建对象的引用。类类型的值是对类实例的引用;数组类型的值是对数组对象的引用,在Java虚拟机中,数组是个真正的对象;而接口类型的值,则是对实现了该接口的某个类实例的引用。还有一种特殊的引用值是null,它表示该引用变量没有引用任何对象。

image-20240323175013401

为什么不把基本类型放堆中呢? (阿里)

1
2
3
首先是栈、堆的特点不同。(堆比栈要大,但是栈比堆的运算速度要快。)
将复杂数据类型放在堆中的目的是为了不影响栈的效率,而是通过引用的方式去堆中查找。(八大基本类型的大小创建时候已经确立大小。三大引用类型创建时候无法确定大小)
简单数据类型比较稳定,并且它只占据很小的内存,将它放在空间小、运算速度快的栈中,能够提高效率。

Java中的参数传递是传值呢?还是传引用? (阿里)

1
值传递

Java中有没有指针的概念? (阿里)

1
没有