欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java类的装载

程序员文章站 2022-05-23 10:05:48
...

类的装载过程:

1 类加载
2 链接(验证-准备-解析)
3 初始化
首先是类加载过程:

    先是获取类的二进制流,转到内存的方法区(放置类的元数据,类型,方法,形参类型,返回值地址。。。),在堆中生成对应的java.lang.class对象。

再到链接:

    验证:文件格式验证(是否以0xCAFEBABE开头,版本号),元数据验证(是否有父类,object类等,是否有继承错误,方法是否实现,重写等),字节码校验(操作数栈,变量是否吻合,栈空间大小问题,跳转指令跳转到偏移量等问题),符号引用验证(权限验证,private,protected这种,检查常量池中描述的类或接口是否存在)。

    准备:会对全局变量进行初始值的设定(例如:private static int i=1 ,会先初始成0,在初始化<clinit>才会初始成1,除了private static final i = 1,一开始就会初始成1,final会特例)。

    解析:符号引用会替换为直接引用(符号引用指的是,若没有继承,默认是继承java.lang.object类,在这个类的常量池中有个字符串是java.lang.object,只是用来表示,直接引用是一段内存地址,指向真正的对象)。

    看过本书说过,引用不止有指向堆中得对象,还有种是在堆中有个句柄池,指向对象,引用指向句柄池。在进行gc回收算法的时候,对象发生地址改变,会对直接引用有影响,用句柄池的话,引用地址就不会变化了,我想这就是有些引用前面加final原因,使用句柄池后,在发生对象部分回收后在内存中移动,这时候修改同在堆的句柄池地址会比修改栈上的引用地址快。

最后初始化:

    执行类构造初始化<clinit>,给staic变量赋值,运行静态块,对象块,父类必须在之前先调用父类的<clinit>(<clinit>是线程安全的)。

类的装载器ClassLoader:

    ClassLoader是一个抽象类,ClassLoader的实例将读入java字节码将类装载入jvm,可以定制符合不同字节码流获取方式(可以从网络或文件加载),在装载负责加载阶段。

ClassLoader比较重要方法:loadClass()(根据类名去装载,返回类的信息),defineClass()(定义一个类,在形参传入类的二进制字节码),findClass()(loadClass回调方法,自定义装载器做法),findLoad()(查看是否是已装载类)

ClassLoader拥有四种:

    BootStrap ClassLoader(加载系统类 rt.jar)

    Extension ClassLoader(加载扩展类 lib/ext/*.jar)

    App ClassLoader(加载应用类,也就是我们自己写的类 ,classpath下)

    Custom ClassLoader(自定义加载器)

    除了第一个加载都有个Parent作为父亲,按照上面的排序进行 自底向上检查类是否加载(寻找需要加载的类是否在对应装载器的目录下,找不到会报ClassNotFoundException),自顶向下加载类(自上而下加载对应装载器路径下的类)。

protected synchronized Class<?> loadClass(String name,boolean resolve) throw ClassNotFoundException{
    //检查请求的类是否已经被加载过了
    Class c = findLoadedClass(name);
    if(c==null){
        try{
            if(parent!=null){
                c = parent.loadClass(name,false);
            }else{
                c = findBootstrapClassOrNull(name);
            }
        }catch(ClassNotFoundException e){
            //如果父类加载器抛出ClassNotFoundException说明父类无法完成类加载
        }
        if(c == null){
            c = findClass(name);
        }
    }
    if(resolve){
        resolveClass(c);
    }
    return c;
}

    上面这段代码是加载类,先是查找是否被加载,没有的话会去请求父类加载,这就是自顶向下的加载。

    使用(/-Xbootclasspath)指定应用类加载路径,最终会使用bootstrap加载器加载应用类(我们自己写的类),本来是使用app加载器加载。使用反射用app classloader先加载,就可以避免以上情况。

    双亲模式:顶层ClassLoader无法加载底层的ClassLoader类,是因为寻找加载类和加载类的方式的问题。但是在核心类rt.jar却有应用层的类的接口,这样会造成问题,这样就需要个上下文加载器(Thread.setContextClassLoader()),这只是个线程。

    双亲模式破坏:tomcat(先加载自身的.class),OSGi(可以进行模块化,热加载)

    热替换:系统无需重启,在类被替换后马上生效。

    内存溢出:生成对象过多。

    内存泄漏:对象无法回收。