当运行Java程序时,首先运行JVM(java虚拟机),然后再把java class加载到JVM里头运行。类装载就是寻找一个类或接口的字节码文件并通过解析该字节码来构造代表这个类或接口的class对象的过程。类装载器把一个类装入Java虚拟机要经过三个步骤成:装载、链接和初始化,其中链接又可以分成校验、准备和解析三步,除了解析外,其它步骤是严格按照顺序完成的,各个步骤的主要工作如下:
1. 装载:查找和导入类或接口的字节码; 2. 链接:执行校验、准备和解析,其中解析是可选的; 校验:检查导入的类或接口的二进制数据的正确性; 准备:给类的静态变量分配并初始化存储空间; 解析:将符号引用转成直接引用; 3. 初始化:激活类的静态变量的初始化Java代码和静态Java代码块。
负责加载java class的这部分就叫做类加载器(Class Loader)。
一旦被引用的类型被装载了,虚拟机仔细检查它的二进制数据。如果类型是一个类,并且不是java.lang.Object,虚拟机根据类的数据得到它的超类的全限定名。虚拟机接着会查看超类是否已经被装载。如果没有,先装载超类,然后虚拟机再次检查它的二进制数据来找它的超类。一直重复到超类为Object为止。
当一个类被虚拟机装载后,通过递归,所有该类的超类和超接口也都被成功装载了,虚拟机就会创建新的Class实例来代表这个类。如果定义类型的字节是由用户自定义的类装载器确定或者生成,然后传递到difineClass()方法,defineClass()方法会在这个时候返回这个新的Class实例。或者用户自定义的类装载器通过findSystemClass()调用委派启动类装载器来装载,findSystemClass()会在这个时候返回Class实例。直到返回了Class实例,loadClass()方法才会返回这个Class实例给调用者。
JVM中默认的三个ClassLoader为:
Bootstrap ClassLoader、Extension ClassLoader和App ClassLoader。
Bootstrap是用C++编写的,是JVM自带的类装载器,由JVM启动,用来装载核心类库(JRE\lib\rt.jar、sunrsasign.jar、charsets.jar等)。App ClassLoader和Ext ClassLoader是用Java语言编写的,由Bootstrap ClassLoader加载。App ClassLoader的Parent是Extension ClassLoader;Extension ClassLoader的Parent为Bootstrap ClassLoader。Extension ClassLoader负责加载扩展的Java class(例如所有javax.*开头的类和存放在JRE的ext目录下的类),App ClassLoader负责加载CLASSPATH指定路径下的类。
public class TestLoaderExtends { public static void main(String[] args) { Class<?> c = TestLoaderExtends.class; ClassLoader loader = c.getClassLoader(); System.out.println(loader); //sun.misc.Launcher$AppClassLoader@19821f System.out.println(loader.getParent()); //sun.misc.Launcher$ExtClassLoader@addbf1 System.out.println(loader.getParent().getParent()); //null } }
Java应用程序从某个类的main()方法开始执行,其他的类在需要的时候才动态连接,如果一个类在所有操作中都没有被用到,这个类就不会被加载。java动态载入class的两种方式为初始化的时候隐式装入和通过java.lang.Class的forName()方法或java.lang.ClassLoader的loadClass()方法显示装入。
public static Class<?> forName(String className) 从装载当前对象实例所在的ClassLoader中装载类,其中的className为类的全限定名。调用此方法等效于: Class.forName(className, true, currentLoader) 其中 currentLoader 表示当前类的定义类加载器。
public static Class<?> forName(String name, boolean initialize,ClassLoader loader)
如果initialize参数为true,类型会在forName()方法返回之前连接并初始化;如果initialize为false,类型会被装载,可能会被连接但是不会被forName()方法明确地初始化。然而,如果该类型在调用forName()之前已经被初始化了,及时initialize的值为false,返回的类型也已经被初始化了。
Class<?> loadClass(String name) 使用指定的二进制名称来加载类。
ClassLoader的实例(MyClassLoader)调用loadClass方法,是指从当前ClassLoader(MyClassLoader)实例中调用类(A),而这个实例与装载当前所在类实例(TestClient)的Classloader(AppClassLoader)也许不是同一个.
例如用户定义了ClassLoader的子类MyClassLoader。TestClient.class通过AppClassLoader装载。当在TestClient的某个方法中使用MyClassLoader的loadClass方法来装入某个类A时,类A是通过MyClassLoader装载的,而TestClient实例的ClassLoader是AppClassLoader,两者不是同一个。
当我们希望在装入一个class文件的时候做一些特定的初始化工作时,可以通过这种自定义类加载器的方式,调用loadClass()方法来实现。
Java提供了抽象类ClassLoader,所有用户自定义类装载器都实例化自ClassLoader的子类。
public abstract class ClassLoader extends Object
ClassLoader 类是一个抽象类。每个Class对象都包含一个对定义它的ClassLoader的引用。System Class Loader是一个特殊的用户自定义类装载器,由JVM的实现者提供,在编程者不特别指定装载器的情况下默认装载用户类。系统类装载器可以通过ClassLoader.getSystemClassLoader()方法得到。
然而,有些类可能并非源自一个文件;它们可能源自其他来源(如网络),也可能是由应用程序构造的。defineClass方法将一个byte数组转换为Class类的实例。这种新定义的类的实例可以使用Class.newInstance来创建。
例如,应用程序可以创建一个网络类加载器,从服务器中下载类文件。示例代码如下所示:
ClassLoader loader = new NetworkClassLoader(host, port); Object main = loader.loadClass("Main", true).newInstance();
网络类加载器子类必须定义方法 findClass 和 loadClassData,以实现从网络加载类。下载组成该类的字节后,它应该使用方法defineClass来创建类实例。示例实现如下(详见类OnePointOneClassLoader):
class NetworkClassLoader extends ClassLoader { String host; int port; public Class findClass(String name) { byte[] b = loadClassData(name); return defineClass(name, b, 0, b.length); } private byte[] loadClassData(String name) { // load the class data from the connection . . . } }
ClassLoader类的构造方法
protected ClassLoader() 使用方法 getSystemClassLoader() 返回的 ClassLoader 创建一个新的类加载器,将该加载器作为父类加载器。 protected ClassLoader(ClassLoader parent) 使用指定的、用于委托操作的父类加载器创建新的类加载器。
ClassLoader类的常用方法
(1).loadClass Class<?> loadClass(String name) 使用指定的二进制名称来加载类。 protected Class<?> loadClass(String name, boolean resolve) 使用指定的二进制名称来加载类。 name参数指定了JVM需要的类的名称,该名称为类的全县定名;resolve参数表示是否分析这个类。在初始化类之前,应考虑类分析,并不是所有的类都需要分析,如果JVM只需要知道该类是否存在或找出该类的超类,那么就不需要分析。这个方法是ClassLoader的入口点。 此方法的默认实现将按以下顺序搜索类: a.调用findLoadedClass(String)来检查是否已经加载类。 b.在父类加载器上调用loadClass方法。如果父类加载器为null,则使用虚拟机的内置类加载器。 c.调用findClass(String)方法查找类。 (2).defineClass protected Class<?> defineClass(String name, byte[] b, int off, int len) 将一个byte数组转换为Class类的实例。 这个方法接受类文件的字节数组并把它转换成Class对象。字节数组可以是从本地文件系统或网络装入的数据。它把字节码分析成运行时数据结构、校验有效性等等。 (3).findClass protected Class<?> findClass(String name) 使用指定的二进制名称查找类。 protected Class<?> findSystemClass(String name) 此方法通过系统类加载(getSystemClassLoader())来加载该类。返回的 Class 对象具有多个与之相关联的 ClassLoader。 protected Class<?>findLoadedClass(String name) 如果 Java 虚拟机已将此加载器记录为具有给定二进制名称的某个类的启动加载器,则返回该二进制名称的类。 (4).resolveClass protected voidresolveClass(Classc)方法解析装入的类 如果该类已经被解析过那么将不做处理。当调用loadClass方法时,通过它的resolve 参数决定是否要进行解析。
在1.2版本之前自定义类加载器必须创建java.lang.ClassLoader的子类并实现loadClass(),loadClass()方法的工作方式:
(1).查看请求的类型是否已经被装载了(findLoadedClass()),如果是直接返回这个已经被装载的类型的Class实例。
(2).否则,委派到这个类加载器的双亲加载器,如果一个双亲返回了一个Class实例,则将该实例直接返回。
(3).否则,调用findClass()。findClass()会试图寻找或者生成一个字节数组,内容采用Java class格式。如果成功,findClass()将这个字节传递个difineClass()。defineClass()将导入该字节数组,并返回一个Class实例。如果findClass()返回了一个Class实例,loadClass()就把这个Class实例返回。
(4).否则,findClass()抛出某些异常来终止处理,loadClass()抛出同样的异常并终止。
package cloader; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import com.hansky.core.io.ByteOutputStream; public class OnePointOneClassLoader extends ClassLoader { private String basePath ; public OnePointOneClassLoader (String basePath) { this.basePath = basePath; } @Override public synchronized Class<?> loadClass(String className) throws ClassNotFoundException { return this.loadClass(className, true); } @Override public synchronized Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class<?> c = findLoadedClass(className); //判断当前类是否已经被加载 if (null != c) { System.out.println(className + " has been loaded ."); return c; } try{ c = super.findSystemClass(className); return c; } catch (ClassNotFoundException e) { } byte [] bytes = getByteArrayFromBasePath(className); if (null == bytes) { System.out.println("Class format error " + className); return null; } c = defineClass(className, bytes, 0, bytes.length); if (null == c) { throw new ClassFormatError("Class Format error !"); } if (resolve) { resolveClass(c); } return c; } private byte [] getByteArrayFromBasePath (String name) { String fileAbsPath = this.basePath + File.separatorChar + name.replace('.', File.separatorChar) + ".class"; FileInputStream fis = null; try { fis = new FileInputStream(fileAbsPath); } catch (FileNotFoundException e) { e.printStackTrace(); } BufferedInputStream bis = new BufferedInputStream(fis); ByteOutputStream bos = new ByteOutputStream(); int c = -1; try { while ((c = bis.read())!= -1) { bos.write(c); } } catch (IOException e) { e.printStackTrace(); } return bos.toByteArray(); } public static void main(String[] args) { OnePointOneClassLoader loader = new OnePointOneClassLoader("d:\\test"); Class<?> c = null; try { c = loader.loadClass("cloader.Hello"); } catch (ClassNotFoundException e) { e.printStackTrace(); } try { IGreet greet = (IGreet) c.newInstance(); greet.sayHello(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
虽然在版本1.2中仍然可以生成ClassLoader的子类并覆盖loadClass()方法,但是建议采用生成ClassLoader的子类并实现findClass()方法的方式。findClass()方法的工作方式为:
(1).接受一个全限定名作为唯一参数,试图查找或生成一个字节数组,内容是Java class文件格式。如果findClass()无法确定或者生成字节数组,它抛出ClassNotFoundException异常并终止。
(2).否则,findClass()调用defineClass(),把所需要的类型名称、字节数组等传递给defineClass()方法。如果defineClass()返回了一个代表这个类型的Class实例,findClass()简单地将这个Class实例返回给调用者。
(3).否则,fineClass()抛出某些异常来终止处理,findClass()抛出同样的异常并终止。
package cloader; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import com.hansky.core.io.ByteOutputStream; public class OnePointTwoClassLoader extends ClassLoader { private String basePath ; public OnePointTwoClassLoader (String basePath) { this.basePath = basePath; } @Override protected Class<?> findClass(String className) throws ClassNotFoundException { byte [] bytes = getByteArrayFromBasePath(className); if (null == bytes) { throw new ClassNotFoundException(); } return defineClass(className, bytes, 0, bytes.length); } private byte [] getByteArrayFromBasePath (String className) { //同上 } public static void main(String[] args) { //同上 } }