一、JVM如何加载一个类?

最直观简单的方式就是通过图片来学习
类加载.png
下面我们来通过三道面试题来针对性学习下:

面试题常见的问题

1:这个class文件,谁来负责加载到内存中?

类加载器

  1. 类加载器负责从文件系统或者网络中加载Class文件,Class文件在开头(16进制)有特定的文件标记符 CA FE BA BE
  2. 类加载器(ClassLoader)只负责Class文件的加载,至于它是否可以运行,则由执行引(Execution Engine)决定。

(举个有趣的栗子:你朋友[ClassLoader]给你(Execution Engine)介绍对象,能不能成功看你自己)

2:class文件,存在内存哪个位置?

类加载器从class文件中抽取类信息并放在了方法区中,类信息:方法代码、变量名、方法名、访问权限、返回值等等。

3:Class对象存储在哪里?

这里说的是Class对象,不是class文件。
每当加载器从class文件中加载一个类的时候,都会加载类型信息到方法区中,同时生成一个Class对象,Class对象new多个对象实例。
Class对象存在堆内存当中。

二、一个类进入jvm之后,经历了什么?

先思考另外一个问题:一个类什么时候进入jvm?
3个时间节点加载

  1. 虚拟机启动时,执行main()方法的时候
  2. new对象的时候
  3. 读取静态字段或静态方法的时候

类从被加载道虚拟机内存中开始,到GC卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。
其中,加载、验证、准备、初始化、卸载这个五个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始。
另外这7个阶段通常是互相交叉的混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。
如下图所示:
类加载.drawio.png

三、面试题:jvm如何初始化一个类

在学习初始化之前,我们先要学会看类的字节码,在idea安装jclasslib插件

类的初始化

先安装一个工具jclasslib,在idea的插件市场可以直接安装。
jclasslib是一个可视化的class文件工具,它提供了工具库,供开发人员查看class文件的内容。

步骤1:先安装jclasslib插件

步骤2:查看字节码

代码如下图所示:
image.png
image.png
image.png
image.png
image.png
我们可以看到一个以及

方法:

用于对象实例初始化时被调用到。每次new对象的时候,都会调用该方法。

方法:

用于静态变量或静态块的初始化。当类class初始化的时候,调用该方法。

思考一个问题(这是前面的问题):
一个类什么时候被类加载器加载进jvm?也就是类什么时候被初始化,什么时候会执行方法?

  1. 虚拟机启动时,执行main()方法的时候。
  2. new对象的时候。
  3. 读取静态字段或静态方法的时候(初始化静态变量以及静态块的时候)

也就是说在编译阶段(使用编译器编译的时候,不是把class编译给机器,这两个编译阶段是要区分开来的),仅仅把java文件编译成class文件的时候,并没有执行class的初始化方法,这一点是要知道的。
只有使用到这个class的时候才会真正去加载这个类到jvm,存放在方法区中,并生成class对象的时候执行。

笔试题:

运行下面代码,输出的结果是?

  1. package com.oyb.jvm.test01;
  2. public class Singleton {
  3. private static Singleton instance = new Singleton();
  4. public static int x = 0;
  5. public static int y;
  6. private Singleton() {
  7. x ++;
  8. y ++;
  9. }
  10. public static Singleton getInstance() {
  11. return instance;
  12. }
  13. public static void main(String[] args) {
  14. Singleton singleton = getInstance();
  15. System.out.println(x);
  16. System.out.println(y);
  17. }
  18. }

输出的结果是:
image.png
分析:
我们知道在执行main方法的时候,类加载器就会加载这个Singleton这个class文件,并初始化这个class对象。
一个类被加载进jvm中之后,有一个阶段叫做准备的阶段,在准备的阶段,为类变量赋值上默认值。
此时,instance = null; x = 0; y =0;
之后进入初始化这个class的阶段,会执行方法。
在执行方法的时,如果有赋值操作,就会为变量赋上显式的值,如果没有赋值操作,就会使用默认值。比如:instance = new Signleton();就是显式赋值。
执行到instance = new Signleton();这行指令的时候,就会调用Singleton的构造方法,就是会调用方法,构造函数中做了两件事,分别将x++; y++;由于x和y都有默认值0了;自增之后都变成了1;所以这个执行完构造函数之后,x=1;y=1。这时候就执行完方法了,继续执行未执行完的方法。
这时候,就会执行下一行指令:x = 0;这时候就是显式赋值了,把x的值变成了0;而y没有被显式赋值,就会使用默认值,也就是前面自增之后的1;
最后输出的结果就是x=0;y = 1;

  1. public class Singleton {
  2. private static Singleton instance = new Singleton();
  3. public static int x = 0;
  4. public static int y;
  5. private Singleton() {
  6. x ++;
  7. y ++;
  8. }
  9. }

经典笔试题:剖析类的初始化顺序

  1. public class Father {
  2. static{
  3. System.out.println("Initialize class father");
  4. }
  5. }
  6. public class Son extends Father {
  7. public static int width = 60;
  8. static{
  9. System.out.println("Initialize class son");
  10. width = 30;
  11. }
  12. public Son() {
  13. System.out.println("Son init ... ");
  14. }
  15. }
  16. public class Main {
  17. static {
  18. System.out.println("Main static init ...");
  19. }
  20. public static void main(String[] args) {
  21. System.out.println("Main main begin");
  22. Son a = new Son();
  23. System.out.println(Son.width);
  24. Son b = new Son();
  25. }
  26. }

输出的结果如下: