类装载器

1. class装载验证流程

1.1. 加载 装载类的第一个阶段, 取得类的二进制流,转为方法区数据结构,在Java堆中生成对应的java.lang.Class对象
1.2. 链接
  • 1.2.1 验证 保证Class流的格式是正确的

    • 文件格式验证
      • 是否以0xCAFEBABE开头
      • 版本号是否合理
    • 1.2.2 元数据验证
      • 是否有父类
      • 继承了final类?
      • 非抽象类实现了所有的抽象方法
    • 1.2.3 字节码验证 (很复杂) 通过验证的class也不一定是没有问题的
      • 运行检查
      • 栈数据类型和操作码数据参数吻合
      • 跳转指令指定到合理的位置
    • 1.2.4 符号引用验证
      • 常量池中描述类是否存在
      • 访问的方法或字段是否存在且有足够的权限
  • 准备

    • 分配内存,并为类设置初始值 (方法区中)
      • public static int v=1;
      • 在准备阶段中,v会被设置为0
      • 在初始化的<clinit>中才会被设置为1
      • 对于static final类型,在准备阶段就会被赋上正确的值(常量在准备时就会被赋上正确的值)
        public static final int v=1;
  • 解析

    • 符号引用 字符串引用对象不一定被加载替换为直接引用指针或者地址偏移量引用对象一定在内存
1.3. 初始化
  • 执行类构造器<clinit>
    • static变量 赋值语句
    • static{}语句
  • 子类的<clinit>调用前保证父类的<clinit>被调用
  • <clinit>是线程安全的
  • 思考:Java.lang.NoSuchFieldError错误可能在什么阶段抛出

什么是类装载器

  • ClassLoader是一个抽象类
  • ClassLoader的实例将读入Java字节码将类装载到JVM中
  • ClassLoader可以定制,满足不同的字节码流获取方式
  • ClassLoader负责类装载过程中的加载阶段

JDK中ClassLoader默认设计模式

  • 重要方法
public Class<?> loadClass(String name) throws ClassNotFoundException
载入并返回一个Class
protected final Class<?> defineClass(byte[] b, int off, int len)
定义一个类,不公开调用
protected Class<?> findClass(String name) throws ClassNotFoundException
loadClass回调该方法,自定义ClassLoader的推荐做法
protected final Class<?> findLoadedClass(String name) 
寻找已经加载的类(找不到才要去加载类)
  • 分类
    BootStrap ClassLoader 启动类加载器
    Extension ClassLoader扩展类加载器
    App ClassLoader应用/系统类加载器
    Custom ClassLoader自定义类加载器
    启动类加载器没有parent,因为它是最顶层,其他每个ClassLoader都有一个Parent作为父亲
    参考
    自底向上:检查类是否已被加载
    自顶向下:尝试加载类
类加载器关系.png

-Xbootclasspath 可以手动指定 boot classpath
类加载例子:
位于:D:\helloJVM\HelloClassLoader.java:

public class HelloClassLoader {
    public void print(){
        System.out.println("I am in bootloader");
    }
}

应用中的HelloClassLoader.java:

public class HelloClassLoader {
    public void print(){
        System.out.println("I am in apploader");
    }
}

及定位class是被哪个loader定位的应用程序:

public class LookForClassLoader {
  public static void main(String[] args) {
      HelloClassLoader loader=new HelloClassLoader();
      loader.print();
  }
  1. 直接执行main方法(即用app loader来加载 HelloClassLoader )


    loader.print()结果.png
  2. 添加参数-Xbootclasspath/a:D:\helloJVM,并编译HelloClassLoader.java。执行main方法

D:\>cd helloJVM
D:\helloJVM>javac HelloClassLoader.java
要记得编译哦.png
loader.print()结果
  • I am in apploader 在classpath中却没有被加载,说明类的加载是从上往下
  1. 保持2的条件不变,强制在app 类加载器中加载classPath中的 HelloClassLoader
public class LookForClassLoader {
    public static void main(String[] args) {
        try {
            forceLoadInAppLoader();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void forceLoadInAppLoader() throws Exception {
        //  强制在apploader中加载 HelloClassLoader
        ClassLoader cl = LookForClassLoader.class.getClassLoader();
        byte[] bHelloClassLoader = loadClassBytes("jvmstudy.HelloClassLoader");
        Method md_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
        md_defineClass.setAccessible(true);
        md_defineClass.invoke(cl, bHelloClassLoader, 0, bHelloClassLoader.length);
        md_defineClass.setAccessible(false);
        HelloClassLoader loader = new HelloClassLoader();
        System.out.println(loader.getClass().getClassLoader());
        loader.print();
    }


    private static byte[] loadClassBytes(String className) throws IOException {
        //获取class文件路径
        String classFile = getClassFile(className);
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(classFile);
        } catch (FileNotFoundException e) {
            System.out.println(e);
            return null;
        }
        byte[] bytes = new byte[fis.available()];
        fis.read(bytes);
        fis.close();
        return bytes;
    }

    private static String getClassFile(String name) {
        StringBuffer sb = new StringBuffer("E:\\myspace\\local-git-hub\\Steping\\src\\");
        name = name.replace('.', File.separatorChar) + ".class";
        sb.append(File.separatorChar + name);
        return sb.toString();
    }
}
loader.print()结果.png
  • 在查找类的时候,先在底层的Loader查找,是从下往上的。app Loader能找到,就不会去上层加载器加载

即双亲委派模式,但是其问题也就是在于顶层classLoader 无法加载底层classLoader中的类

  • Java框架(rt.jar)如何加载应用的类? 在app Loader中包含一个rt接口的实例
    javax.xml.parsers包中定义了xml解析的类接口,Service Provider Interface SPI 位于rt.jar 。即接口在启动ClassLoader中,而SPI的实现类,在AppLoader。
Thread. setContextClassLoader() 即上下文加载器

是一个角色/职责,任何加载器都可以来承担这个角色,就像班长。
用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例

rt.jar 中javax.xml.parsers.FactoryFinder展示如何在启动类加载器加载AppLoader的类 ,即上下文ClassLoader可以突破双亲模式的局限性

/**
     * Attempt to load a class using the class loader supplied. If that fails
     * and fall back is enabled, the current (i.e. bootstrap) class loader is
     * tried.
     *
     * If the class loader supplied is <code>null</code>, first try using the
     * context class loader followed by the current (i.e. bootstrap) class
     * loader.
     *
     * Use bootstrap classLoader if cl = null and useBSClsLoader is true
     */
    static private Class<?> getProviderClass(String className, ClassLoader cl,
            boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
    {
        try {
            if (cl == null) {
                if (useBSClsLoader) {
                    return Class.forName(className, false, FactoryFinder.class.getClassLoader());
                } else {
                    cl = ss.getContextClassLoader();
                    if (cl == null) {
                        throw new ClassNotFoundException();
                    }
                    else {
                        return Class.forName(className, false, cl);
                    }
                }
            }
            else {
                return Class.forName(className, false, cl);
            }
        }
        catch (ClassNotFoundException e1) {
            if (doFallback) {
                // Use current class loader - should always be bootstrap CL
                return Class.forName(className, false, FactoryFinder.class.getClassLoader());
            }
            else {
                throw e1;
            }
        }
    }

打破常规模式

  • 双亲模式是默认的模式,但不是必须这么做
  • Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent
  • OSGi的ClassLoader形成网状结构,根据需要自由加载Class
破坏双亲模式例子:

OrderClassLoaders

热替换`当一个class被替换后,系统无需重启,替换的类立即生效

`

很不错的Java类加载器(ClassLoader) 推荐

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容