类加载器自学笔记

一、介绍

java类通过编译形成字节码文件,存放在磁盘中。通过类加载器加载到java虚拟机内存中。

二、类的加载、连接和初始化

当java查询需要某个类时,虚拟机会保证这个类已经被加载、连接和初始化。连接又包含验证、准备和解析。

1、类的加载

通过类的完全限定名(包名和类名)查找此类的字节码文件,把类的.class文件中的二进制数据读入到内存中,并存放在运行时数据区的方法区内。然后利用字节码创建一个class对象,用来封装类在方法区的数据结构并存放在堆内。这个过程由类加载器完成。

2、连接

验证:确保被加载器的正确性。class文件的字节流中包含的信息符合当前虚拟机要求,不会危害虚拟机自身安全。

准备:为类的静态变量分配内存,并将其初始化为默认值。此阶段仅仅为静态变量分配内存,并且设置该变量的初始值。(比如static int num=5,这里只将num初始化为0,5的值会在初始化时赋值)。对于final static修饰的变量,编译的时候就会分配了,也不会分配实例变量的内存。

解析:把类中的符号引用转换为直接引用。符号引用就是一组符号来描述目标,而直接引用就是直接指向目标的指针、相对应偏移量或一个间接定位到目标的句柄。

3、初始化

类加载最后阶段,若该类具有父类,则先对父类进行初始化,执行静态代码和静态代码块,成员变量也将被初始化。

三、类加载器

类的加载由类加载器完成的。类加载器可以分为两种:第一种是java虚拟机自带的类加载器,分别为启动类加载器、扩展类加载器和系统类加载器。第二中是用户自定义的类加载器,是java.lang.ClassLoader的子类实例。

1、虚拟机内置类加载器

1.1根类加载器(Bootstrap)

根类加载器是最底层的类加载器,是虚拟机的一部分,它是由c++语言实现的,且没有父加载器,也没有继承java.lang.ClassLoader类。它主要负责加载系统属性"sun.boot.class.path"指定的路径的核心类库(即<JAVA_HOME>\jre\lib),出于安全考虑,根类加载器只加载java、javax、sun开头的类

1.2扩展类加载器(Extension)

扩展类加载器是指由sun公司实现的sun.misc.Launcher$ExtClassLoader类(JDK9是jdk.internal.loader.ClassLoader$PlatformClassLoader类),它是java语言编写,父类加载器是根类加载器。负责加载<JAVA_HOME>\jre\lib\ext目录下的类库或者系统变量“java.ext.dirs”指定目录的类库。

1.3系统类加载器(System)

系统类加载器也称为应用类加载器,也是纯java类,是原sun公司实现的sun.misc.Launcher$AppClassLoder类(JDK9是jdk.internal.loader.ClassLoaders$AppClassLoader)。它的父类加载器是扩展类加载器。它负责从classpath环境变量或者系统属性java.class.path所指定的目录中加载类。它是用户自定义的类加载器的默认父类加载器。一般情况下,该类加载器是程序中默认的类加载器,可以通过ClassLoader.getSystemClassLoader()直接获得。

1.4小结

在程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,同时我们还可以自定义类加载器。需要注意的是,java虚拟机对class文件采用的按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,java虚拟机采用的是双亲委派模式,即把加载类的请求交由父类加载器处理。

2、双亲委派模式

除了根类加载器之外,其他的类加载器都需要有自己的父加载器。从jdk1.2开始,类的加载器过程采用双亲委派模式,这种机制能够很好的保护java程序的安全。除了虚拟机自带的根类加载器之外,其他的类加载器都有唯一的父加载器。如果需要classLoader加载一个类时,该classLoader先委托自己的父类加载器先去加载这个类,若这个类能够加载,则由父加载器加载,否则才有classLoader自己加载这个类。即每个类加载器都很懒,加载类时都先让父类加载器去尝试加载,一直到根加载器,加载不到时自己才去加载。真正加载类的加载器成为启动加载器。双亲委派机制的父子关系并非面向对象程序设计中的继承关系,而是通过使用组合模式来复用父加载器代码,如图:

好处:

可以避免类的重复加载,当父类加载器已经加载该类时,就没有必要子加载器加载;

考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Object的类,通过双亲委派模式传递到启动类加载器,而启动类加载器在核心javaAPI发现这个名字的类,发现该类已被加载,并不会重新加载网络传递过来的java.lang.Objec,而直接返回已加载过的Object.class,这样便可以防止核心API库被随意篡改。

3、ClassLoader

所有的类加载器(除了根类加载器)都必须继承java.lang.ClassLoader。它是一个抽象类,主要的方法如下:

3.1loadClass

在ClassLoader的源码中,有一个方法loadClass(String name,boolean resolve),这里就是双亲委派模式的代码实现。从源码可以观察到它的执行顺序。需要注意的是,只有父加载器加载不到类时,会调用findClass方法进行类的查找,所以,在定义自己的类加载器时,不要覆盖该方法,而应该覆盖findClass方法

执行顺序:

3.2findClass

在自定义类加载器时,一般我们需要覆盖这个方法,且ClassLoader中给出了一个默认的错误实现。

3.3defineClass

该方法的签名如下。用来将byte字节解析成虚拟机能够识别的Class对象。defineClass()方法通常与findClass()方法一起使用。在自定义类加载器时,会直接覆盖ClassLoader的findClass()方法获取要加载的字节码,然后调用defineClass()方法生产class对象

3.4resolveClass

连接指定的类。类加载器可以使用此方法来连接类。

4、URLClassLoader

在java.net包中,jdk提供了一个更加易用的类加载器URLClassLoader,它扩展了ClassLoader,能够从本地或者网络上指定的位置加载类。我们可以使用该类作为自定义的类加载器使用。

构造方法:

public URLClassLoader(URL[] urls):指定要加载的类所在的URL地址,父类加载器默认为系统类加载器。

public URLClassLoader(URL[] urls,ClassLoader parent):指定要加载的类所在的URL地址,并指定父类加载器。

5、自定义类加载器

只需要继承ClassLoader类,并覆盖findClass方法即可

5.1文件类加载器

测试:

5.2网络类加载器

5.3热部署类加载器

当我们调用loadClass方法加载时,会采用双亲委派模式,即如果类已经被加载,就从缓存中获取,不会重新加载。如果同一个class被同一个类加载器多次加载,则会报错。因此,我们要实现热部署让同一个class文件被不同的类加载器重复加载,应该使用findClass方法,避开双亲委托模式,从而实现同一个类被多次加载,实现热部署。

6、类的显式与隐式加载

类的加载方式是指虚拟机将class文件加载到内存的方式。

显示加载是指在java代码中通过调用ClassLoader加载class对象,比如Class.forName(String name);this.getClass().getClassLoader().loadClass()加载类。

隐式加载指不需要在java代码中明确调用加载的代码,而是通过虚拟机自动加载到内存中。比如在加载某个class时,该class引用到另外一个类的对象,那么这个对象的字节码文件就会被虚拟机自动加载到内存中。

7、线程上下文类加载器

在java中存在许多的服务提供者接口DPI,全称Service Provider Interface,是java提供的一套用来被第三方实现或者扩展的API,这些接口一般由第三方提供实现,常见的SPI有JDBC、JNDI等。这些SPI的接口属于核心类库,一般存在rt.jar包中,由根类加载器加载。而第三方实现的代码一般作为依赖jar包存在classpath路径下,由于SPI接口中的代码需要加载具体的第三方实现类并调用其相关方法,SPI的接口类是由根加载器加载的,根加载器无法直接加载位于classpath下的具体实现类。由于双亲委派模式的存在,根加载器无法反向委托系统加载器加载SPI的实现类。在这种情况下,java提供了线程上下文加载器用于解决这个问题。

线程上下文加载器可以通过java.lang.Thread的getContextClassLoader()来获取,或者通过setContextClassLoader(ClassLoader cl)来设置线程的上下文类加载器。如果没有手动设置上下文类加载器,线程将继承其父线程的上下文类加载器,初始线程的上下文类加载器是系统类加载器,在线程中运行的代码可以通过此类加载器来加载类或资源。

发布了171 篇原创文章 · 获赞 5 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/QilanAllen/article/details/105341433
今日推荐