Material Render Phone

Tomxin7

“工欲善其事,必先利其器”,作为Java核心之一的JVM,是一个Java开发者必须要去理解的部分,在校招的时候,走马观花式的过了一遍,很多东西没有深入理解,所以打算再看一遍《深入理解JAVA虚拟机》,把其中重要的部分整理出来,记录在博客之中。

类加载机制

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制,采用懒加载机制。

1、类加载时机

图片

初始化:
1、遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令的最常见的Java代码场景是:使用new关键字实例化对象的时候,读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候,以及调用一个类的静态方法的时候。
2、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。
3、当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
4、当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

Parent.java

public class Parent {
  static {
    System.out.println("parent 初始化了...");
  }
}

Child.java

public class Child extends Parent{
  static {
    System.out.println("child 初始化...");
  }
  
  public static void main(String[] args){
    
  }
}

结果
图片

不被初始化的例子:
1、通过子类引用父类的静态字段,子类不会被初始化
2、通过数组定义来引用类
3、调用类的常量

1、通过子类引用父类的静态字段,子类不会被初始化

public class Main{
  public static void main(String[] args){
    System.out.println(Child.num)
  }
}

结果子类没有被初始化
图片

2、通过数组定义来引用类

public class Main{
  public static void main(String[] args){
    Child[] c = new Child[10];
  }
}

结果
图片
通过数组定义来引用类不会被初始化

2、加载过程—加载

  1. 通过一个类的全限定名来获取定义此类的二进制流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的Class对象,作为这个类的各种数据的访问入口。hotspot虚拟机中class是放在方法区,而不是堆中。

3、加载过程—验证

验证阶段作用是保证Class文件的字节流包含的信息符合JVM规范,不会给JVM造成危害。如果验证失败,就会抛出一个java.lang.VerifyError异常或其子类异常。验证过程分为四个阶段:
* 文件格式验证:验证字节流文件是否符合Class文件格式的规范,并且能被当前虚拟机正确的处理。
* 元数据验证:是对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言的规范。
* 字节码验证:主要是进行数据流和控制流的分析,保证被校验类的方法在运行时不会危害虚拟机。
* 符号引用验证:符号引用验证发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在解析阶段中发生。

4、加载过程—准备

准备阶段正式为类变量分配内存并设置变量的初始值(默认值),这些变量使用的内存都将在方法区中进行分配。
默认值:
int 0
Boolean false
Float 0.0
Char ‘0’
抽象数据类型 null

class hello{
  public static int a = 10;//这里并不是初始化为10,而是其默认值0,但是如果final修饰的类变量将会赋值成真实的值。
}

5、加载过程—解析

解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。
类或者接口的解析
字段解析
类方法解析
接口方法解析

6、初始化—解析

初始化是类加载的最后一步
初始化是执行()方法的过程

转载声明:写作不易,商业转载请联系作者获得授权,非商业转载请注明出处,并附上原文链接,感谢!