Java在真正需要使用一个类时才会去加载类,而不是在启动程序时就载入所有的类,因为大多数使用者都只使用到程序的部分资源,在需要某些功能时再载入某些资源,可以让系统资源运用的更高效。
类的加载指的是将类的 .class 文件中的二进制数据读入到 内存 中,将其放在Jvm的 方法区 内,然后在 堆 区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的 Class对象 ,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。
int ,
long ,
float
等等),即使是数组都有与之关联的 Class 类的对象。
Class对象是由Jvm自动生成的,每当一个类被载入时,Jvm就自动为其生成一个Class对象
通过Object的 getClass() 获取每一个 实例 对应的Class对象
String name = "hello";
Class stringClass = name.getClass();
System.out.println("类的名称:" + stringClass.getName());
System.out.println("是否为接口:" + stringClass.isInterface());
System.out.println("是否为基本类型:" + stringClass.isPrimitive());
System.out.println("是否为数组:" + stringClass.isArray());
System.out.println("父类名称:" + stringClass.getSuperclass().getName());
复制代码
类的名称:java.lang.String 是否为接口:false 是否为基本类型:false 是否为数组:false 父类名称:java.lang.Object 复制代码
你也可以直接使用一下方式来获取String类的Class对象
Class stringClass = String.class; 复制代码
在一些应用中,你无法事先知道使用者将载入什么类别,你可以使用Class的静态方法 forName() 来动态加载类别
Class c = Class.forName(args[0]);
System.out.println("类的名称:" + c.getName());
System.out.println("是否为接口:" + c.isInterface());
System.out.println("是否为基本类型:" + c.isPrimitive());
System.out.println("是否为数组:" + c.isArray());
System.out.println("父类名称:" + c.getSuperclass().getName());
复制代码
$ java Demo1 java.util.Scanner 类的名称:java.util.Scanner 是否为接口:false 是否为基本类型:false 是否为数组:false 父类名称:java.lang.Object 复制代码
Class.forName()有两个版本,上面的版本只指定了全限定类名,而另一个版本可以让你指定类名,载入时是否执行静态代码块,执行类加载器( ClassLoader )
static Class forName(String name, boolean initialize, ClassLoader loader) 复制代码
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// Class.forName() 加载类 默认会执行初始化块
Class.forName("Test2");
// Class.forName() 加载类 第二个参数 可以控制是否执行初始化块
Class.forName("Test2", false, loader);
class Test2 {
static {
System.out.println("静态初始化块执行了!");
}
}
复制代码
Class对象表示所载入的类别,获取Class对象后,你就可以获取类别相关的信息,入 package , constructor , field , method 等信息。 而每一种信息,都有相对应的类别
Class c = Class.forName(args[0]);
System.out.println("包信息package:" + c.getPackage());
System.out.println("类修饰符modifier:" + c.getModifiers());
System.out.println("构造方法constructor:");
Arrays.stream(c.getDeclaredConstructors()).forEach(System.out::println);
System.out.println("成员变量fields:");
Arrays.stream(c.getDeclaredFields()).forEach(System.out::println);
复制代码
$ java Demo1 java.util.ArrayList 包信息package:package java.util 类修饰符modifier:1 构造方法constructor: public java.util.ArrayList(java.util.Collection) public java.util.ArrayList() public java.util.ArrayList(int) 成员变量fields: private static final long java.util.ArrayList.serialVersionUID private static final int java.util.ArrayList.DEFAULT_CAPACITY private static final java.lang.Object[] java.util.ArrayList.EMPTY_ELEMENTDATA private static final java.lang.Object[] java.util.ArrayList.DEFAULTCAPACITY_EMPTY_ELEMENTDATA transient java.lang.Object[] java.util.ArrayList.elementData private int java.util.ArrayList.size private static final int java.util.ArrayList.MAX_ARRAY_SIZE 复制代码
Java在需要使用类的时候才会将类载入,Java中类的载入是由 Class Loader 来实现的.
当你尝试执行 java xxx 命令时,java会尝试找到 JRE 的安装目录,然后寻找 jvm.dll ,接着启动JVM并进行初始化操作,接着产生 BootstrapLoader , Bootstrap Loader 会载入 Extended Loader , 并设定 Extended Loader 的parent 为 BootstrapLoader , 接着 Bootstrap Loader 会载入 Application Loader , 并将 Application Loader 的parent 设定为 Extended Loader
BootstrapLoader 搜寻 sun.boot.library.path 中指定的类, 你可以使用 System.getProperty("sun.boot.library.path") 来获取
/Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib 复制代码
Extended Loader ( sun.misc.Launcher$ExtClassLoader ) 是由Java编写的,会搜寻系统参数 java.ext.dirs 中指定的类别,可以通过 System.getProperty("java.ext.dirs") 来获取
/Users/dsying/Library/Java/Extensions: /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext: /Library/Java/Extensions: /Network/Library/Java/Extensions: /System/Library/Java/Extensions:/usr/lib/java 复制代码
Application Loader ( sun.misc.Launcher$AppClassLoader ) 是由Java编写的,会搜寻系统参数 java.class.path 中指定的类别,可以通过 System.getProperty("java.class.path") 来获取, 在使用 java xxx 命令执行 .class 字节码文件时,可以通过 -cp 参数设定 classpath
java –cp ./classes SomeClass 复制代码
ClassLoader loader Thread.currentThread().getContextClassLoader(); // sun.misc.Launcher$AppClassLoader@18b4aac2 应用类加载器 System.out.println(loader); // sun.misc.Launcher$ExtClassLoader@610455d6 扩展类加载器 System.out.println(loader.getParent()); // Bootstrap ClassLoader 启动类加载器(用C语言实现,所以此处返回null) System.out.println(loader.getParent().getParent()); 复制代码
sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@610455d6 null 复制代码
Class.forName() ClassLoader.loadClass()
Class.forName() ClassLoader.loadClass() Class.forName(name, initialize, loader)
全盘负责 父类委托 缓存机制
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
AppClassLoader 加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器 ExtClassLoader 去完成。 ExtClassLoader 加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给 BootStrapClassLoader 去完成。 BootStrapClassLoader 加载失败(例如在 $JAVA_HOME/jre/lib 里未查找到该class),会使用 ExtClassLoader 来尝试加载; ExtClassLoader 也加载失败,则会使用 AppClassLoader 来加载,如果 AppClassLoader 也加载失败,则会报出异常 ClassNotFoundException 。 public Class<?> loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 首先判断该类型是否已经被加载
Class c = findLoadedClass(name);
if (c == null) {
//如果没有被加载,就委托给父类加载或者委派给启动类加载器加载
try {
if (parent != null) {
//如果存在父类加载器,就委派给父类加载器加载
c = parent.loadClass(name, false);
} else {
//如果不存在父类加载器,就检查是否是由启动类加载器加载的类,通过调用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
//如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
复制代码
双亲委派模型意义:
自定义类加载器一般都是继承自 ClassLoader 类,从上面对 loadClass 方法来分析来看,我们只需要重写 findClass 方法即可。下面我们通过一个示例来演示自定义类加载器的流程:
package com.github.hcsp.classloader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extends ClassLoader {
// 存放字节码文件的目录
private final File bytecodeFileDirectory;
public MyClassLoader(File bytecodeFileDirectory) {
this.bytecodeFileDirectory = bytecodeFileDirectory;
}
// 还记得类加载器是做什么的么?
// "从外部系统中,加载一个类的定义(即Class对象)"
// 请实现一个自定义的类加载器,将当前目录中的字节码文件加载成为Class对象
// 提示,一般来说,要实现自定义的类加载器,你需要覆盖以下方法,完成:
//
// 1.如果类名对应的字节码文件存在,则将它读取成为字节数组
// 1.1 调用ClassLoader.defineClass()方法将字节数组转化为Class对象
// 2.如果类名对应的字节码文件不存在,则抛出ClassNotFoundException
//
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getByteArrayFromFile(name);
if (classData == null) {
throw new ClassNotFoundException();
}
return defineClass(name, classData, 0, classData.length);
}
byte[] getByteArrayFromFile(String className) throws ClassNotFoundException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
File file = new File(bytecodeFileDirectory, className + ".class");
int len = 0;
try {
byte[] bufferSize = new byte[1024];
FileInputStream fis = new FileInputStream(file);
while ((len = fis.read(bufferSize)) != -1) {
bos.write(bufferSize, 0, len);
}
} catch (FileNotFoundException e) {
throw new ClassNotFoundException();
} catch (IOException e) {
e.printStackTrace();
}
return bos.toByteArray();
}
public static void main(String[] args) throws Exception {
File projectRoot = new File(System.getProperty("basedir", System.getProperty("user.dir")));
MyClassLoader myClassLoader = new MyClassLoader(projectRoot);
Class testClass = myClassLoader.loadClass("com.github.hcsp.MyTestClass");
Object testClassInstance = testClass.getConstructor().newInstance();
String message = (String) testClass.getMethod("sayHello").invoke(testClassInstance);
System.out.println(message);
}
}
复制代码
自定义类加载器的 核心 在于 对字节码文件的获取 ,如果是加密的字节码则需要在该类中对文件进行解密。由于这里只是演示,我并未对class文件进行加密,因此没有解密的过程.
你可以使用Class的 newInstance() 方法来实例化
Class c = Class.forName(className); Object obj = c.newInstance(); 复制代码
使用反射可以取回类中的方法,方法对应的类为 java.lang.reflect.Method , 你可以使用它的 invoke() 方法来调用指定的方法
Class testClass = myClassLoader.loadClass("com.github.hcsp.MyTestClass");
Object testClassInstance = testClass.getConstructor().newInstance();
String message = (String) testClass.getMethod("sayHello").invoke(testClassInstance);
复制代码