NightPxy 个人技术博客

java-类加载

Posted on By NightPxy

##

类加载步骤

装载

装载阶段,在装载阶段会在方法区中生成一个代表这个类的java.lang.Class的类元数据
这里的装载不一定是必须是class文件,无论是war之类的压缩包,甚至运行时生成的动态代理对象都会有装载阶段产生类元数据

验证

验证字节流是否有危害,版本是否兼容等等相关验证步骤,确保这个class是看起来可用的

准备

准备正式为方法区上的类元数据分配内存,并设置静态变量的默认值
注意这个默认值是类型默认值而不是用户指定的值

解析

解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程
符号引用和直接引用

  • 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中
  • 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在

初始化

初始化是类的最后一个阶段,到这里用户代码才会真正起效 初始化的阶段是执行类构造器client方法.(这个client方法由编译器收集类的静态变赋值和静态代码块后合并而成).JVM保证在对当前类的client方法执行之前,必定先执行其父类的client方法

类加载器

JVM团队在设计时就将类加载独立JVM之外,也就是天然允许应用程序使用自定义类加载器加载类
考虑到大部分情况下,都无需使用自定义加载,JVM也提供了以下三种内置类加载器

  • 引导类加载器(Bootstrap ClassLoader)
    因为类加载独立JVM之外,那么JVM自身的类如何加载呢?为此JVM设计BootstrapClassLoader,专门加载JVM自己需要的类库(一种另类的自定义)
  • 扩展类加载器(Extension ClassLoader)
    负责加载 JAVA_HOME\lib\ext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库,同上
  • 应用程序类加载器(Application ClassLoader)
    负责加载用户路径(classpath)上的类库,这也是通常意义上我们使用的类加载

双亲委派

双亲委派概述

每一个类都会有一个类加载器(大部分类都是共用一个类加载ApplicationClassLoader)

JVM的机制就是双亲委派

  • 当加载一个类时,会优先使用这个类的父类的加载器去加载 .注意双亲委派是递归的,也就是说最终会优先使用最顶层的类加载器来加载
  • 当ApplicationClassLoader加载一个类时,它不会首先加载这个类,会交给其父类ExtendClassLoader,ExtendClassLoader同理会继续交给BootStrapClassLoader
    源码如下
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 {
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            //这里就是我们的加载器起作用的地方  
            c = findClass(name);
        }
    }
    //如果最后没有加载到类,会再尝试解析一次
    if (resolve) {
        resolveClass(c);
    }
    return c;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

双亲委派的好处

双亲委派最大的好处就是安全(这个安全是指保护JDK自身的class安全).
类加载来源可能是非常多,比如自定义一个有害的java.lang.Integer来覆盖JDK中的Integer

public class Integer {
    public Integer() {
        System.exit(0)
    }
}
//此时任何尝试初始化Integer就会导致系统直接退出  
//而使用双亲委派后,最终会使用BootstrapClassloader从JDK路径中Integer,从而避开这个有危害的覆盖    

双亲委培的另一个好处是很好的解决了类加载的统一问题
对于类元数据的class对象,并不是以字节码为依托的.也就是说对同一个字节码文件被重复加载可能会产生不同的类元数据.而双亲委派下则保证了基类使用相同的类加载器(基类元数据唯一)

自定义类加载器

自定义类加载器核心在于继承ClassLoader并覆盖findClass对象
其意义在与如果在依托双亲委托加载无效的情况下,使用自己的类加载方式