类加载器

一般来说,java虚拟机使用一个类的方式如下:java源程序在经过java编译器编译之后就被转换成java字节码文件.class,虚拟机加载字节码文件被转换为java.lang.Class的一个实例,每个这样的实例代表一个java类,然后通过这个实例的newInstance()方法生成一个对象。基本上所有类加载器都是java.lang.ClassLoader的实例。
 
java.lang.ClassLoader的主要作用是根据一个给定类的名字,查找该类的字节码文件,并生成一个class实例,它同时也加载java类所需要的一些资源如图像和配置文件。
 
java.lang.ClassLoader类介绍
 
java.lang.ClassLoader与加载类有关的方法
 
| 方法                         | 说明                                                  |
|------------------------------+-------------------------------------------------------|
| getParent()                  | 返回该类加载器的父加载器                              |
| loadClass(String name)       | 加载名称为name的类,返回结果是java.lang.Class的实例   |
| findClass(String name)       | 查找名称为name的类,返回结果是java.lang.Class类的实例 |
| findLoadedClass(String name) | 查找名字为name的已经加载过的类,返回结果同上          |
| defineClass()                | 把字节数组b中的内容转换成java类,返回结果同上         |
| resolveClass(Class<?> c)     | 连接指定的java类                                      |
 
类加载器的树状组织结构
 
java中的类加载器分为两种,一种是系统提供的,另一种是由java应用开发人员编写的。
 
java中系统提供的类加载器有三种:
•引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,加载系统类(通常从jar文件rt.jar中进行加载),它是虚拟机整体中的一部分,通常用原生代码C来实现的,并不继承自java.lang.ClassLoader,使用String.class.getClassLoader()将返回null。
•扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录(jre/lib/ext)。该类加载器在此目录里面查找并加载 Java 类。
•系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
 
扩展类加载器和系统类加载器通常都是由java实现的,它们都是URLClassLoader类的实例。Class.forName()是使用系统类加载器加载的。
 
除了引导类加载器外,每个类加载器都有一个父类加载器, 通过getParent()方法可以得到,类加载器会为它的父类加载器提供一个机会,以便加载任何给定类,只有在其父类加载器加载失败时,它才会加载给定类。
 
类加载器树状组织结构示意图
类加载器

类加载器的代理模式
 
在介绍代理模式背后的动机之前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java 虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才为两个类是相同的。即便是同样的字节代码,被不同的类加载器加载之后所得到的类,也是不同的。比如一个 Java 类 com.example.Sample,编译之后生成了字节代码文件Sample.class。两个不同的类加载器 ClassLoaderA和 ClassLoaderB分别读取了这个Sample.class文件,并定义出两个 java.lang.Class类的实例来表示这个类。这
两个实例是不相同的。对于 Java 虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常 ClassCastException。
 
了解了这一点之后,就可以理解代理模式的设计动机了。代理模式是为了保证 Java 核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object类,也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由 Java 应用自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。通过代理模式,对于 Java 核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的都是同一个版本的 Java 核心库的类,是互相兼容的。
 
网络类加载器
 
Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。通过类加载器可以比较简单的实现这种需求。类 NetworkClassLoader负责通过网络下载 Java 类字节代码并定义出 Java 类。它的实现与 FileSystemClassLoader类似。在通过 NetworkClassLoader加载了某个版本的类之后,一般有两种做法来使用它。第一种做法是使用 Java 反射API。另外一种做法是使用接口。
 
需要注意的是,并不能直接在客户端代码中引用从服务器上下载的类,因为客户端代码的类加载器找不到这些类。使用 Java 反射 API 可以直接调用 Java 类的方法。而使用接口的做法则是把接口的类放在客户端中,从服务器上加载实现此接口的不同版本的类。在客户端通过相同的接口来使用这些实现类。
 
两种加载类的方法以及区别
 
有两种手动加载类的方法:ClassLoader.loadClass()和Class.forName()而Class.forName()实际上也是调用类加载器的loadClass方法。两者也存在一些区别
 
JVM加载类的时候,需要经过三个步骤:装载、连接和初始化。
装载就是找到相应的class文件,读入JVM;
初始化就是对class文件的初始化;
连接分为三步:
+ 验证class文件是否符合规格
+ 准备 为类变量分配内存的同时设置默认初始值
+ 解释 根据loadClass方法第二个参数判定是否需要解释,这里的解释是指根据类中的符号引用查找响应的实体,再把符号引用替换成一个直接引用的过程
 
Class.forName()调用Class.forName(name, initialize, loader);因此Class.forName("classString")等同于Class.forName("classString",true, CALLCLASS.class.getClassLoader())第二个参数为true,设置加载类的时候连接该类
 
用户使用类加载器加载类ClassLoader.loadClass(name),它会默认调用ClassLoader.loadClass(name,false),第二个参数为false,因此通过loadClass加载类的时候并不对该类进行解释,不会初始化该类,而Class类的forName方法则相反,会将Class进行解释和初始化。
原文地址:https://www.cnblogs.com/qianye/p/2786340.html