JVM类加载机制讲解

前言

  如果您觉得有用的话,记得给博主点个赞,评论,收藏一键三连啊,写作不易啊^ _ ^。
  而且听说点赞的人每天的运气都不会太差,实在白嫖的话,那欢迎常来啊!!!


JVM类加载机制讲解

一、什么是JVM类加载机制?

概述:java JVM 把【描述类的数据】从class文件中加载到内存,并对该数据进行验证,转换解析和初始化,最终形成可以
【被虚拟机直接使用的java类型】,这个过程我们成为【JVM的类加载机制】


二、类加载的时机

【加载】的五个阶段:

加载、验证、准备、解析、初始化

类从【加载】【卸载】的整个生命周期:

加载、验证、准备、解析、初始化、使用、卸载,其中验证、准备、解析可以把他们统称为连接:

即生命整个生命周期:

【加载】 --> 【连接】 -->【初始化】 --> 【使用】 --> 【卸载】


三、类加载的五个阶段

1、加载:

1)通过一个类的全限定定名来获取【定义此类的二进制字节流】:
2)将这个字节流中所代表的【静态存储结构】:转化为【方法区中的运行时数据结构】:
3)在内存中生成一个【代表这个类的 java.lang.Class 对象】:,作为方法区这个类的各种数据的入口。

2、验证:

确保 Class 文件的字节流中包含的信息是否符合【当前虚拟机】:的要求,保证这些信息被当做代码运行后,不会对JVM造成自身安全。

3、准备:

在方法区中分配这些变量所使用的内存空间。

4、解析:

虚拟机将常量池中的符号引用替换为直接引用的过程。

5、初始化:

执行类中定义的 Java 程序代码。
注::初始化阶段,主要通过类构造器来实现,即初始化阶段是执行类构造器方法的过程:


四、什么类型的数据不会初始化? ------- 论点

结论:

所有【引用类型】外加【常量】的方式都不会触发初始化,我们称之为【被动引用】。

论证前准备:

什么是引用类型,什么是基础类型:

在Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始值,即:他代表的值就是数值本身;而引用类型的变量保存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表示的地址的位置。
【基础类型】:byte,short,int,long,char,float,double,Boolean
【引用类型】:类类型,接口类型和数组。

论证:

例子1:使用类字段

原因:通过子类引用父类的静态字段,不会导致子类初始化,对于静态字段,只有直接定于这个字段的类才会初始化。

public class SuperClass {
    
    
       static {
    
    
              System.out.println("This is SuperClass !!!");
       }
       public static int index = 55555;
}
public class NodeClass extends SuperClass{
    
    
       static {
    
    
              System.out.println("This is NodeClass");
       }
}

使用:

public static void main(String[] args) {
    
    
   System.out.println(NodeClass.index);
}

效果:父类初始化,子类并没有初始化
在这里插入图片描述

例子2:通过数组定义来引用类,不会触发此类的初始化

原因:数组是引用类型

正常初始化:

    public static void main(String[] args) {
    
    
      NodeClass test = new NodeClass();
   }

效果:正常初始化
在这里插入图片描述
用数组初始化:

   public static void main(String[] args) {
    
    
      NodeClass[] superClass = new NodeClass[10];
   }

效果:通过数组定义引用类,不会触发此类的初始化

在这里插入图片描述

例子3:常量测试,不会触发定义常量所在类的初始化

原因:在源码阶段,确实引用常量类ConstClass中的HELLO_WORLD,但实际上在编译阶段通过常量传播优化,
已经将此常量的值"hello world !!!" 直接存在ConstClass类的常量池中,因此ConstClass类对HELLO_WORLD中的引用,
其实都转化成类对自身【常量池】的引用了。

public class ConstClass {
    
    
       static {
    
    
              System.out.println("This is ConseClass");
       }
       public static final String HELLO_WORLD  = "hello world !!!";
       public static String index = "Hello World !!!";
}

类调非常量:

public static void main(String[] args) {
    
    
  System.out.println(ConstClass.index);
}

效果:正常初始化
在这里插入图片描述
类调常量:

public static void main(String[] args) {
    
    
  System.out.println(ConstClass.HELLO_WORLD);
}

效果:不会触发定义常量所在类的初始化
在这里插入图片描述


五、类加载器

1、【类加载器】用处:

对于任何一个类,都必须由【加载它的类加载器】【类本身】一起共同确认其在java JVM中的【唯一性】

2、【类加载器】使用原则:

类加载器是在双亲委派模型的基础上实现的。

3、【类加载器】特性:

每个类加载器,都拥有一个【独立的类名称空间】,即如果比较两个类是否相等,只有在这两个类在都是由同一个类加载器加载而来的前提下,才有意义。


六、JVM提供了三种类加载器,分别为:

1)启动类加载器(Bootstrap ClassLoader)

负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被
虚拟机认可(按文件名识别,如 rt.jar)的类。

2)扩展类加载器(Extension ClassLoader)

负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类
库。

3)应用程序类加载器(Application ClassLoader):

负责加载用户路径(classpath)上的类库。
JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader 实现自定义的类加载器。


七、双亲委派

双亲委派模型:
在这里插入图片描述
概述:

当一个类收到了类加载器的请求时,他首先不会尝试自己去加载这个类,而是把这个请求委派给【父类加载器】去完成,每一个层次的【类加载器】都是如此,因此所有的加载请求都应该传送到最顶层的【启动类加载器】其中, 只有当【当父类加载器】反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的 Class)【子类加载器】才会尝试自己去加载
采用双亲委派的一个好处是比如加载位于 rt.jar 包中的类 java.lang.Object,不管是哪个加载 器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载 器最终得到的都是同样一个 Object 对象。

好处:

1)保证java类型体系中最基础的行为
2)双亲委派模型对于保证java程序的稳定运作极为重要


八、打破双亲委派例子

OSGI实现的模块化部署打破双亲委派模型
即在OSGI环境下,类加载器不在是双亲委派模型推荐的【树状结构】,而是进一步发展为更加复杂的【网状结构】

猜你喜欢

转载自blog.csdn.net/weixin_38316697/article/details/114891102