• 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)); }
  • 相关阅读:
    Java反射机制的简单应用
    UI组件之AdapterView及其子类关系,Adapter接口及事实上现类关系
    CSDN日报20170406 ——《代码非常烂,所以离职。》
    Swift环境下实现UILabel居上 居中 居下对齐
    在EA中将画出的ER图转换成SQL脚本
    hdu2236
    glm编译错误问题解决 formal parameter with __declspec(align(&#39;16&#39;)) won&#39;t be aligned
    CSS中的相关概念
    javascript jquery 推断对象为空的方式
    swift 给导航添加item,实现界面的跳转
  • 原文地址:https://www.cnblogs.com/yn-huang/p/10758498.html
Copyright © 2020-2023  润新知