JVM底层认知
前言
学习JVM专题,写文章记录笔记,以便于之后的复习与巩固
一、Klass模型
Klass模型类的继承结构
由图可以得出,通过继承关系可以得出,类的元信息存储在元空间中,Klass分为InstanceKlass与ArrayKlass。
普通的java类在JVM中对应的是InstanceKlass类的实例。InstanceKlass的三个子类如下:
- InstanceMirrorKlass(镜像类):用于表示java.lang.class,java代码中获取到的Class对象,实际上就是这个C++类的实例,存储在堆区中
- InstanceRefKlass:是用于java.lang.ref.Reference的子类
- InstanceClassLoaderKlass:用于遍历某个加载器加载的类
java中的数组不是静态数据类型,而是动态的数据类型,即在运行期才会生成对象。java数组的元信息用ArrayKlass的子类来表示。
- TypeArrayKlass:表示java中基本类型数据组的数据结构。newarray 创建一个指定原始类型(八大基本数据类型)的数组,并将其引用压入栈顶。
- ObjArrayKlass:表示java中引用类型数组的数据结构。anewarray创建一个引用型(如类,接口,数组)的数组,并将其引用值压入栈顶
二、类加载过程
类加载生命周期分为七个步骤
类加载过程为前五个阶段
加载
- 通过类的全限定名(包+类名)获取存储该类的Class文件(不指名从哪里获取)
- 解析成运行时数据,即InstanceKlass实例,存放于方法区
- 在堆区生成该类的Class对象,即InstanceMirrorKlass实例
什么时候加载?
在主动使用时就会加载:
- new、getstatic、putstatic、invokestatic
- 反射
- 初始化一个类的子类会去加载其父类
- 启动类(main函数所在类)
预加载:包装类、String、Thread
加载时因为没有指明必须从哪获取class文件,便有了多种加载的方式
- 从压缩包中读取,如jar、war
- 从网络中获取,如Web Applet
- 动态生成,如动态代理、CGLIB
- 由其他文件生成,如JSP
- 从数据库读取
- 从加密文件中读取
验证
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备
这一步为静态变量分配内存,赋值初值。
对于实例变量没有赋值初值这一说法,产生实例后给以赋值。
如果被final修饰,在编译时会给属性添加ConstantValue属性,准备阶段直接完成赋值,即无赋初值这一说。
解析
将常量池中的符号引用转为直接引用(间接引用 -> 直接引用),解析后的信息存放于ConstantPoolCache类实例中。
1、类或接口的解析
2、字段解析
3、方法解析
4、接口方法解析
初始化
执行静态代码块,完成静态变量的赋值。
静态字段、静态代码段,字节码层面会生成clinit方法
方法中语句的先后顺序与代码的编写顺序一至
三、类加载案例
案例一:
public class JvmClassTest1 {
public static void main(String[] args) {
System.out.println(Jvm_1_Test_B.Str);
}
}
class Jvm_1_Test_A{
public static String Str = "Str A";
static {
System.out.println("A Static Block");
}
}
class Jvm_1_Test_B extends Jvm_1_Test_A{
static {
System.out.println("B Static Block");
}
}
输出结果:
A Static Block
Str A
懒加载,根本就没有使用到Jvm_1_Test_B 。
str是类Jvm_1_Test_A的静态属性,可以看到不会存储到子类Jvm_1_Test_B的镜像类(InstanceMirrorKlass)中
可以猜得到,通过子类Jvm_1_Test_B访问父类Jvm_1_Test_A的静态字段有两种实现方式:
1、先去Jvm_1_Test_B的镜像类中去取,如果有直接返回;如果没有,会沿着继承链将请求往上抛。很明显,这种算法的性能随继承链的death而上升,算法复杂度为O(n)
2、借助另外的数据结构实现,使用K-V的格式存储,查询性能为O(1)
案例二:
public class JvmClassTest1 {
public static void main(String[] args) {
System.out.println(new Jvm_1_Test_B().Str);
}
}
class Jvm_1_Test_A{
public static String Str = "Str A";
static {
System.out.println("A Static Block");
}
}
class Jvm_1_Test_B extends Jvm_1_Test_A{
static {
System.out.println("B Static Block");
}
}
输出结果:
A Static Block
B Static Block
Str A
案例三:
public class JvmClassTest1 {
public static void main(String[] args) {
System.out.println(Jvm_1_Test_B.Str);
}
}
class Jvm_1_Test_A{
public static String Str = "Str A";
static {
System.out.println("A Static Block");
}
}
class Jvm_1_Test_B extends Jvm_1_Test_A{
public static String Str = "Str B";
static {
System.out.println("B Static Block");
}
}
输出结果:
A Static Block
B Static Block
Str B
案例四:
public class JvmClassTest2 {
public static void main(String[] args) {
Jvm_2_Test_A testA = Jvm_2_Test_A.getInstance_A();
System.out.println(Jvm_2_Test_A.intvalue1);
System.out.println(Jvm_2_Test_A.intvalue2);
}
}
class Jvm_2_Test_A{
public static int intvalue1;
public static int intvalue2 = 1;
public static Jvm_2_Test_A instance_A = new Jvm_2_Test_A();
Jvm_2_Test_A() {
intvalue1++;
intvalue2++;
}
public static Jvm_2_Test_A getInstance_A(){
return instance_A;
}
}
输出结果:
1
2
案例五:
public class JvmClassTest2 {
public static void main(String[] args) {
Jvm_2_Test_A testA = Jvm_2_Test_A.getInstance_A();
System.out.println(Jvm_2_Test_A.intvalue1);
System.out.println(Jvm_2_Test_A.intvalue2);
}
}
class Jvm_2_Test_A{
public static int intvalue1;
public static int intvalue2 = 1;
public static Jvm_2_Test_A instance_A = new Jvm_2_Test_A();
Jvm_2_Test_A() {
intvalue1++;
intvalue2++;
}
public static Jvm_2_Test_A getInstance_A(){
return instance_A;
}
}
输出结果:
1
1
案例六:
public class JvmClassTest3 {
public static void main(String[] args) {
Jvm_3_Test_A[] arr = new Jvm_3_Test_A[1];
}
}
class Jvm_3_Test_A{
static {
System.out.println("A Static Block");
}
}
输出结果:
无输出
这只是定义了一个数据类型而已,故不会有输出。
案例七:
public class JvmClassTest4 {
public static void main(String[] args) {
System.out.println(Jvm_4_Test_A.Str);
}
}
class Jvm_4_Test_A{
public static final String Str = "Str A";
static {
System.out.println("A Static Block");
}
}
输出结果:
Str A
这个是将Str写入了Jvm_4_Test_A的常量池中
案例八:
public class JvmClassTest5 {
public static void main(String[] args) {
System.out.println(Jvm_5_Test_A.Str);
}
}
class Jvm_5_Test_A{
public static final String Str = UUID.randomUUID().toString();
static {
System.out.println("A Static Block");
}
}
输出结果:
A Static Block
d536f6be-f66b-45e4-97be-2b8c479ccb4d
这里的str虽然是final修饰,但是uuid是动态生成的,没办法写到常量池中。
案例九:
public class JvmClassTest6 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = forName("JVM.ClassLoadTest.Jvm_6_Test_A");
}
}
class Jvm_6_Test_A{
static {
System.out.println("A Static Block");
}
}
输出结果:
A Static Block