Java虚拟机:类加载过程

在冯诺依曼的计算机模型中,任何程序都要加载到内存才能与CPU进行交流。字节码.class文件同样需要加载到内存中,才可以实例化。

其中,类加载器ClassLoader的使命就是加载.class文件到内存中。

在加载类时,使用的是Parents Delegation Model,即双亲委派模型

Java类加载器是一个运行时核心基础设施模块,如下图,主要是在启动之初进行类的加载(Load)、链接(Link)、初始化(Init)

第一步,Load阶段。

读取.class文件流,并转换为特定的数据结构,初步校验cafe babe魔法数、常量池、文件长度、是否有父类等,然后创建对应类的java.lang.Class实例。

第二步,Link阶段。

包括验证、准备和解析三个步骤。

  • 验证:是更详细地校验,比如final是否合规、类型是否正确、静态变量是否合理等。
  • 准备:为静态变量分配内存,设定默认初始值。
  • 解析:解析类和方法之间相互引用正确性,完成内存布局。

第三步,Init阶段。

执行类构造器的<clinit>方法。

 

类加载器

类加载器类似于原始部落结构,存在权利等级制度。

最高一层是Bootstrap ClassLoader,它是在JVM启动时创建的,是最根基的类加载器,负责装载最核心的Java类,比如Object、System、String等。由C++编写。

第二层是Extension ClassLoader(JDK9之后变为了Platform ClassLoader,平台类加载器)。用以加载扩展的系统类,比如XML、加密、压缩等相关类。Java编写。

第三层是Application ClassLoader,应用类加载器,主要是加载用户自定义classpath下的类。

 

双亲委派模型

结合上图,我们来看一下ClassLoader的loadClass()方法:

 1 /**
 2     @Param resolve 是否链接
 3     @Param name 类名
 4 */
 5 protected Class<?> loadClass(String name, boolean resolve)
 6         throws ClassNotFoundException
 7 {
 8     synchronized (getClassLoadingLock(name)) {
 9         // 首先检查该类是否已经被加载过
10         Class<?> c = findLoadedClass(name);
11         if (c == null) {
12             long t0 = System.nanoTime();
13             try {
14                 // 如果父加载器不为空,就用它的父加载器去加载该类
15                 if (parent != null) {
16                     c = parent.loadClass(name, false);
17                 } else {
18                     // 到这里的话就是bootstrapClassLoader
19                     // 这是一个本地方法,使用bootstrapClassLoader去加载该类
20                     // 如果加载不了,就返回空
21                     c = findBootstrapClassOrNull(name);
22                 }
23             } catch (ClassNotFoundException e) {
24                
25             }
26 
27             // 如果返回空了,证明其父类加载器不能加载该类
28             if (c == null) {
29 
30                 long t1 = System.nanoTime();
31                 // 使用自己进行加载
32                 c = findClass(name);
33 
34                 // this is the defining class loader; record the stats
35                 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
36                 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
37                 sun.misc.PerfCounter.getFindClasses().increment();
38             }
39         }
40         // 如果resove为true,就链接
41         if (resolve) {
42             resolveClass(c);
43         }
44         return c;
45     }
46 }

 

低层次的当前类加载器,不能覆盖更高层次类加载器已经加载过的类。

如果底层的类加载器想要加载一个未知类,要非常有礼貌的向上级询问:”请问,这个类已经加载了吗?“,被询问的高层类首先要回答自己一个问题:我是否加载过此类?如果答案为否,那么又向上询问,直到Bootstrap ClassLoader为止,如果它也未加载过并且并加载,就会逐层交给下一层类加载器,反应在源码中就是第28行逐层递归退出。

 

我们看一下Bootstrap所有已经加载的类库:

URL[] urls = Launcher.getBootstrapClassPath().getURLs();
Arrays.asList(urls).forEach(System.out::println);

 

结果:

file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/resources.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/rt.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/sunrsasign.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jsse.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jce.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/charsets.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/lib/jfr.jar
file:/D:/Development%20Tools/Java/jdk1.8.0_201/jre/classes

 

Bootstrap的加载路径是可以追加的,不建议修改或删除原有的加载路径。通过-Xbootclasspath/a:参数可以增加类加载路径。

如果想在启动时观察加载了哪个jar包中的类,可以增加-XX:+TraceClassLoading参数,此参数在解决类冲突时非常实用。

 

什么时候需要自定义类加载器?

(1)隔离加载类。在某些框架内进行中间件与应用的模块隔离,把类加载到不同的环境。

(2)修改类加载方式。出了Bootstrap外,其他的加载并非都要引入。

(3)扩展加载源。比如从数据库、网络进行加载。

(4)防止源码泄露。Java代码容易被编译和篡改,可以进行编译加密。那么类加载器也需要自定义。

实现自定义类加载器的步骤:继承ClassLoader,重写findClass()方法,调用defineClass()方法。下面是一个例子:

public class MyClassLoader extends ClassLoader {

    private String path;
    private String name;

    public MyClassLoader(String path, String name) {
        this.path = path;
        this.name = name;
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] data = null;
        try {
            data = getClassBytes(name);
            return defineClass(name, data, 0, data.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

// 获取字节码流
private byte[] getClassBytes(String className) throws IOException { InputStream in = null; ByteArrayOutputStream baos = null; try { int len = 0; byte[] bytes = new byte[1024]; in = new FileInputStream(new File(path + className + ".class")); baos = new ByteArrayOutputStream(); while ((len = in.read(bytes)) != -1) { baos.write(bytes, 0, len); baos.flush(); } } catch (Exception e) { }finally { baos.close(); in.close(); } return baos.toByteArray(); } } public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { MyClassLoader classLoader = new MyClassLoader("E:\\","my classloader"); Class clz = classLoader.findClass("Dog"); Field[] fields = clz.getDeclaredFields(); System.out.println(Arrays.toString(fields)); }

 

posted @ 2018-04-20 17:10  yn_huang  阅读(188)  评论(0编辑  收藏  举报