JVM系列四:JVM的类加载器

一、JVM的类加载器

1.1、类加载器的继承体系

   类加载器ClassLoader负责加载class文件,class文件在文件开头有特定的文件标识,将class文件字节码内容加载到内存中,并将这些内容转换成方法区中的运行时数据结构,ClassLoader只负责class文件的加载,至于它是否可以运行,则由执行引擎Excution Engine决定。

 

 1.2、类加载器的继承体系

 1.3、类加载器的分类

1.3.1、引导类(启动)加载器

  • Java的核心类库都是通过启动类加载器加载的,比如String类;
  • Bootstrap ClassLoader;
  • 这个类加载器是使用C/C++语言实现的,嵌套在JVM内部;
  • 它用来加载Java的核心类库(${JAVA_HOME}/jre/lib/rt.jar、resources.jar 或者sun.boot.class.path路劲下的内容),用于提供JVM自身需要的类;
  • 没有继承java.lang.ClassLoader,没有父加载器;
  • 加载扩展类和应用程序类加载器,并指定它们的父加载器;
  • 处于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类;

1.3.2、扩展类加载器

  • Extension ClassLoader
  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现;
  • 派生于ClassLoader类;
  • 父加载器为引导类加载器;
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载;  

1.3.3、系统类加载器

  • APPClassLoader
  • Java语言编写,由sun.misc.Launcher$AppClassLoader实现;
  • 派生于ClassLoader;
  • 父类加载器为扩展类加载器;
  • 负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库;
  • 该加载器是程序中默认的类加载器,一般来说,Java应用的类都是由它来加载完成;
  • 通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器;

1.3.4、自定义类加载器

  • 在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式;
  • 为什么需要自定义类加载器?
    • 隔离加载类;
    • 修改类的加载方式;
    • 扩展加载源;
    • 防止源码泄露;    

1.4、双亲委派机制

1.4.1、工作原理

  • 如果一个类加载器收到了类加载的请求,它并不会自己先去加载,而是把这个请求委托给其父类的加载器去执行;
  • 如果父类的加载器还存在父类加载器,则进一步向上委托,依次递归,直至请求到达最顶层的启动类加载器;
  • 如果父类加载器可以完成类的加载任务,那么成功返回;如果父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派机制;    

 

1.4.2、好处

  • 避免类的重复加载;
  • 保护程序安全,防止核心API被随意篡改;

1.4.3、案例演示

1 package java.lang;
2 
3 public class String {
4     public static void main(String[] args) {
5         System.out.println("Hello World!");
6     }
7 }

 

 

 

 

1.5、沙箱安全机制

  自定义java.lang.String类,但是在加载自定义的String类的时候,会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件(rt.jar/java/lang/String.class),报错的信息说没有main方法,就是因为加载的是rt.jar包中的String类,

这样可以保证对Java核心源码的保护,这就是沙箱安全机制

1.6、类加载的过程

 

1.6.1、加载

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

补充

  • 加载.class文件的方式?
    • 从本地系统中直接加载;
    • 通过网络获取,典型场景:Web Applet;
    • 从zip压缩包中读取,成为日后jar、war格式的基础;
    • 运行时计算生成,使用最多的是动态代理技术;
    • 由其他文件生成,典型场景:JSP应用;
    • 从专有数据库中提取.class文件,比较少见;
    • 从加密文件中获取,典型的防Class文件被反编译的保护措施。

1.6.2、链接

  • 验证(Verify)
    • 目的在于确保Class文件的字节流中包含的信息符合当前虚拟机的要求,保证被加载的类的正确性,不会危害虚拟机自身的安全;
    • 主要包括四种验证:文件格式验证、元数据验证、字节码验证、符号引用验证;
  • 准备(Prepare)  

    • 为类变量分配内存并且设置该变量的默认初始值,即零值;
    • 这里不包含用final修饰的static,因为被final修饰的变量在编译的时候就分配了,准备阶段会显示初始化;
    • 这里不会为实例变量分配初始化,类变量(static修饰的变量)会分配在方法区中,而实例变量会随着对象一起分配到Java堆内存中;
  • 解析(Resolve)  

    • 将常量池内的符号引用转换为直接引用的过程;
    • 事实上,解析操作往往会伴随着JVM在执行完初始化之后再执行;
    • 符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《Java虚拟机规范》的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或者一个间接定位到目标的句柄。
    • 解析动作主要针对类、接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTRANT_Class_info、CONSTRANT_Fieldref_info、CONSTRANT_Methodref_info等。  

1.6.3、初始化

  • 初始化阶段就是执行类构造器方法<clinit>()的过程;
  • 此方法不需要定义,是javac编译器自动搜集类中的所有变量的赋值动作和静态代码快中的语句合并而来;
  • 构造器方法中指令按语句在源文件中出现的顺序执行;
  • <clinit>()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>());
  • 若该类具有父类,JVM会保证子类的<clinit()>执行前,父类的<clinit>()已经执行完毕;
  • 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁;  

 1.7、获取类的加载器案例演示

 1 package com.atguigu.jvm;
 2 
 3 class Car {
 4     private String name;
 5     private String brand;
 6     private String description;
 7     private Double money;
 8 }
 9 
10 /**
11  * 获取类的加载器
12  */
13 public class ClassLoaderDemo {
14     public static void main(String[] args) throws ClassNotFoundException {
15         //获取类加载器的第一种方式
16         Car c1 = new Car();
17         ClassLoader classLoader1 = c1.getClass().getClassLoader();
18         System.out.println("classLoader1 = " + classLoader1);
19 
20         //获取类加载器的第二种方式
21         ClassLoader classLoader2 = Class.forName("com.atguigu.jvm.Car").getClassLoader();
22         System.out.println("classLoader2 = " + classLoader2);
23 
24         //获取类加载器的第三种方式
25         ClassLoader classLoader3 = Car.class.getClassLoader();
26         System.out.println("classLoader3 = " + classLoader3);
27 
28         System.out.println("===============");
29         Object obj = new Object();
30         System.out.println("Object类的根加载器:" + obj.getClass().getClassLoader());
31         System.out.println("自定义类的加载器:" + c1.getClass().getClassLoader());
32         System.out.println("自定义类的加载器(父亲):" + c1.getClass().getClassLoader().getParent());
33         System.out.println("自定义类的加载器(爷爷):" + c1.getClass().getClassLoader().getParent().getParent());
34 
35     }
36 }

 

 

 

 

 

 

 

posted @ 2021-12-29 18:07  谁怕?一蓑烟雨任平生  阅读(66)  评论(0)    收藏  举报