类加载器

类加载器

类加载器按照层次,从顶层到底层,分为以下三种:

类型及使用

类型

(1)启动类加载器/引导类加载器(Bootstrap ClassLoader)

1.这个类加载器使用c/c++语言实现的,嵌套在JVM内部
2.它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
3.并不继承自java.lang.ClassLoader,没有父加载器。
4.加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
5.处于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类

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

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

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

这个加载器是ClassLoader中getSystemClassLoader()方法的返回值,所以一般也称它为系统类加载器。它负责加载用户类路径(Classpath)上所指定的类库,可直接使用这个加载器,如果应用程序没有自定义自己的类加载器,一般情况下这个就是程序中默认的类加载器
1.java语言编写,由sun.misc.Launcher$AppClassLoader实现
2.派生于ClassLoader类
3.父类加载器为扩展类加载器
4.它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
5.该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
6.通过ClassLader#getSystemClassLoader()方法可以获取到该类加载器

运行:

public static void main(String[] args){
       System.out.println("hello java");
       System.out.println("==输出classLoader对象==");
       //1、如何获取classLoader对象 获取当前系统类加载器==就是当前工程类的加载器,即应用程序类加载器
       ClassLoader systemClassLoader =  ClassLoader.getSystemClassLoader();
       System.out.println(systemClassLoader);
       //2、想办法获取扩展类加载器
       //2.1、systemClassloader对象
       ClassLoader parentclassloader = systemClassLoader.getParent();
       System.out.println(parentclassloader);
       //3、引导类加载器
       ClassLoader parent=  parentclassloader.getParent();
       System.out.println(parent);
  }

运行结果:

hello java
==输出classLoader对象==
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1540e19d
null

结论:

1、可以获得扩展类加载器和应用程序类加载器,三个类加载器有父子关系

2、bootstarp这个顶层加载器,无法在程序中获取到,只能得到null
原因:因为当前bootstarp之外还需要进行c和c++,所以不让应用这进行获取,如果程序员获取到当前对象,认为不安全

关系图:

img

 

使用

 

 

 

 

 

 

 

 

 

 

打印结果

 

流程图

 

 

引导类加载器

 

 

 

类加载器调用配置文件

public class ClassLoaderTest {
@Test
   public void test04() throws Exception {
       //对于类加载器而言,加载过后的位置位于target/classes位置
       //ClassLoaderTest.class是调用当前ClassLoaderTest这个类,使用getClassLoader()方法来获取当前类的类加载器,getResourceAsStream()用来读取文件
       InputStream in  =  ClassLoaderTest.class.getClassLoader().getResourceAsStream("sys2.properties");
       Properties p =new Properties();
       p.load(in);

       System.out.println(p);
       in.close();
  }
   @Test
   public void test05() throws Exception {
       //对于类加载器而言,加载过后的位置位于target/classes位置,所以要继续访问则自行加路径
       InputStream in  =  ClassLoaderTest.class.getClassLoader().getResourceAsStream("com\\dl\\tt\\sys3.properties");
       Properties p =new Properties();
       p.load(in);

       System.out.println(p);
       in.close();
  }
}

 

Class类的作用

用来做什么?为什么会有这个类?

 

A.java==A.class
B.java==B.class
C.java==C.class
具有相同属性和行为的事物 叫类
将A.class .class .class看作三个对象
这个类封装了 属性,方法,构造方法

 

 

Class这个如何抽取

 

当前我是java虚拟机
需要将A.calss和User.calss类型注册得到虚拟机

当前这个Class类型的对象如何获取?

三种方式获取到当前Class对象

 

情况一:java虚拟机已经启动,但是我写的是后写的

@Test
   public void test01() throws ClassNotFoundException {
       //当前系统中使用A User这个类
       //告诉与1个全路径:包名字和类名字
       //这个forName()到当前编译路径翔安区找全路经,如果找到,那么注册新的类型
   //Class.forName作用:主要功能Class.forName(xxx.xx.xx)返回的是一个类。Class.forName(xxx.xx.xx)的作用是要求JVM查找并加载指定的类,也就是说JVM会执行该类的静态代码段。
       Class<?> cls = Class.forName("com.dl.tt.bean.User");
       System.out.println(cls);
  }

 

情况二:当前这个类已经注册成功,如何获取到当前类型对象

@Test
   public void test02(){
       Class<?> cls = String.class;//字节码对象
       System.out.println(cls);
  }

//或者写成
   @Test
   public void test04(){
       Class<String> cls1= String.class;
       System.out.println(cls1);
  }
情况三:已经知道一个对象,如何获取当前类型对象

@Test
   public void test03(){
       //已经在java虚拟机中存在一个对象
       String str = new String("abc");
       //通过对象获取类型
       Class<?> cls = str.getClass();
       System.out.println(cls);
  }

 

 

JVM的类加载机制主要有3种

1、全盘负责:所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入。 2、双亲委派:所谓的双亲委派,则是先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父加载器,依次递归,如果父加载器可以完成类加载任务,就成功返回;只有父加载器无法完成此加载任务时,才自己去加载。 3、缓存机制:缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中。这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因。

 

双亲委派:

如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器区执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父加载器无法完成此加载任务,子加载器才会尝试自己去加载,如果均加载失败,就会抛出ClassNotFoundException异常,这就是双亲委派模式。即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了了时,儿子自己才想办法去完成。

双亲委派优点:

1、安全,可避免用户自己编写的类动态替换Java的核心类,如java.lang.String。,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
2、避免全限定命名的类重复加载(使用了findLoadClass()判断当前类是否已加载)。Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
 

 

posted @ 2023-12-20 19:40  夜柠檬  阅读(64)  评论(0)    收藏  举报