文档章节

ClassLoader

liuzhengyang
 liuzhengyang
发布于 2016/10/05 13:23
字数 1691
阅读 37
收藏 1
点赞 0
评论 1

博客地址:liuzhengyang

Java中的ClassLoader

先抛出几个问题

  • 类初始化 static{} 块会在什么时候执行
  • Class在方法区内的结构
  • 不同类加载器加载一个类,类初始化块会被执行几次,不同ClassLoader加载的类存放在哪里,是否指向同一个Class实例
  • 类A触发了类B的加载,那么类B的加载器是什么
  • 如何实现热部署,即在运行时改变类的行为,或类替换
  • Class.forName(name) 做了什么

回答这些问题还是要参考JDK的代码实现,另外还要依靠Java语言规范和Java 虚拟机规范

本篇文章用类来泛指类和接口

背景介绍

类加载器是用于加载Class的机制,Class可以以文件的形式或者是二进制流的形式存在,一般 按照最常见的Class文件来称呼。

<!-- -->

ClassLoader负责加载类,java.lang.ClassLoader类是一个抽象类, 可以通过一个类的二进制名称,来定位类在哪里,并生成class的数据定义。 最常见的策略是将类的名称转换成文件名并从文件系统中读取Class文件.

每个Class对象都会有指向定义它的ClassLoader的引用,通过Class#getClassLoader() 可以获得。

类的二进制名(binary name)是Java语言规范规定的,常见的有:

java.lang.String
test.loader.Test$Test2  // 静态内部类

Class文件可以存在jar包中,可以以目录形式存放。

加载、链接、初始化过程

  • 如果要加载一个类的时候发现类没有被加载过(就是JVM中没有这个类的二进制表示)就会使用类加载器尝试加载这个二进制表示。
  • 链接阶段设计验证、准备和解析。
  • 验证用于验证字节码格式是否正确。
  • 准备阶段会进行static字段的创建,并将这些字段初始化成默认值。这个阶段不会执行任何代码。static 字段的初始化会在初始化阶段执行。
  • 一些JVM实现会在准备阶段预先计算一些附加数据结构来使之后的类操作更加有效。一个特别的结构就是方法表或其他的数据结构,可以在让任意的调用在实例上的方法无需搜索父类。类的二进制表示通过符号引用引用其他类、字段、方法构造器等。对于字段和方法,符号引用包括字段、方法所在类的名字以及字段和方法自己的名称。在符号引用使用前必须要进行解析,解析阶段会对符号引用进行检查,并通常会替换成直接引用。如果解析失败,会抛出场景的NoSuchMethodError等错误。

Java中常见的ClassLoader

Java的类加载结构有bootstrap class loader用来加载$JAVA_HOME/jre/lib下载的 rt.jar中的文件,其中是Java的核心类.Bootstrap classloader是JVM中的实现, 如果要在ClassLoader中表示其为父类,用null表示。 另外有ExtClassLoader加载lib/ext文件夹下的jar包 AppClassLoader是用来加载ClassPath目录下的jar包和Class文件。 常说这三者是父类关系,并不是Java中的集成关系,而是ClassLoader中定义的 parent.

委托机制

ClassLoader文件在第一次加载类的时候会先委托其父加载器加载,如果加载失败再自己加载。 这样,一些关键的类,如String等就不会遭到篡改。但是在J2EE中,一个Web容器下 可能有多个应用,每个应用加载时有子类优先的需求,这时就需要覆盖默认的逻辑。 代码逻辑在ClassLoader类的loadClass(String name, boolean resolve) 方法中.ClassLoader#loadClass调用的是ClassLoader#loadClass(name, false) resolve表示的是是否进行链接步骤。 去掉一些不相关代码,loadClass方法的逻辑如下

类加载锁
synchronized (getClassLoadingLock(name)) {
            //  查看是否已经被加载过,会调用一个native方法判断
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                // 如果没加载过,则会进入加载过程
                try {
                    // 如果parent不是null,则说明是Java中的类加载器
                    if (parent != null) {
                        // 调用parent的loadClass方法递归向上加载
                        c = parent.loadClass(name, false);
                    } else {
                        // 如果是null,说明是Bootstrap class loader,
                        // 则使用Bootstrap加载器加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
                // 都没加载成功,则自己进行加载
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    // 调用findClass,会找到class的字节流然后使用
                    // defineClass()来定义类
                    c = findClass(name);
                }
            }
            if (resolve) {
                // 如果要链接,则进行链接,其中有验证、准备和符号引用解析过程
                resolveClass(c);
            }
            return c;
        }

另外,Class的静态方法Class.forName(className)也能够完成类的加载工作, 返回一个Class对象。会对类进行初始化,使用调用Class.forName的方法所在的类的类加载器 来加载。 Class.forName(className, initialize, loader) 方法会通过参数控制由是否进行初始化和由哪个类加载器加载。

ClassLoader中的loadResource方法

我们在写程序时,经常会在classpath下放置一些配置文件,在运行时读取配置文件的内容 可以通过Class.getResource(), 例如

Test.class.getResourceAsStream("/config.properties")
这个方法会委托Test类的加载器来进行加载资源

类加载器与类

当我们判断两个Class对象是否是相等时,或判断是否是集成关系时,需要看它们的类加载器是否是同一个。 类加载器和类,组成了Class对象的标识。 测试代码

        自定义的类加载器,修改默认的委托机制。
    private static class MyClassLoader extends URLClassLoader {
        public MyClassLoader(URL[] urls) {
            super(urls);
        }

        @Override
        public Class<?> loadClass(String name) throws ClassNotFoundException {
            return defineClass(name);
        }
    }
    public static class Class1 {
        static {
            System.out.println("Initialize class 1");
            Class2.doSay();
            Class<Class2> class2Class = Class2.class;
            System.out.println("Class2 Loader is " + class2Class.getClassLoader());
        }
        public static void say() {

        }
    }
    public static class Class2 {
        static {
            System.out.println("Initialize Class2");
        }
        public static void doSay() {
            System.out.println("Say");
        }
    }

    public static void main(String[] args) throws Exception {
        URL path = ClassLoaderTest.class.getResource("/");
        URL rtPath = Object.class.getResource("/");

        MyClassLoader myClassLoader = new MyClassLoader(new URL[]{path, rtPath});
        Class<?> class1 = myClassLoader.loadClass("classloader.ClassLoaderTest$Class1");
        Class<?> aClass = Class.forName("classloader.ClassLoaderTest$Class1", true, myClassLoader);
        Class<?> aClass2 = Class.forName("classloader.ClassLoaderTest$Class1");

        System.out.println(class1);
        System.out.println("Class 1 classLoader is " + class1.getClassLoader());
        System.out.println(aClass);
    }

能够看出,类A触发类B的初始化时,会用类A的加载器去加载。

什么时候回触发类的初始化

  • 创建类的实例,通过new 或者Class.newInstance
  • 类的初始化方法被调用 invokestatic
  • 类的static字段被绑定 putstatic
  • 类的static字段被使用,并且不是常量
  • 一个类被初始化的时候,它的父类会被先初始化 我们看到,用两个加载器加载一个类,初始化块被执行了两次。

© 著作权归作者所有

共有 人打赏支持
liuzhengyang

liuzhengyang

粉丝 52
博文 17
码字总数 15932
作品 2
海淀
程序员
加载中

评论(1)

FantasyBabymg
FantasyBabymg
大神,如果一个对象已经被加载或者引用 这个时候改变了这个对象的结构 怎么(可以)实现热部署吗?

暂无相关文章

Netweaver和SAP云平台的quota管理

Netweaver 以需要为一个用户上下文(User Context)能够在SAP extended memory区域中分配内存尺寸创建quota为例。 对于Dialog工作进程,使用事务码修改参数 ztta/roll_extension_dia. 对于非D...

JerryWang_SAP ⋅ 4分钟前 ⋅ 0

IDEA提示编码速度

焦点移动 将焦点冲代码编辑窗口移动到菜单栏:Alt+菜单栏带下划线字母 将焦点从工具窗口移动到代码编辑窗口 Esc或Shift+Esc 将焦点从代码编辑移动到最近使用的工具窗口 F12 模板提示 Ctrl+J...

bithup ⋅ 13分钟前 ⋅ 0

180623-SpringBoot之logback配置文件

SpringBoot配置logback 项目的日志配置属于比较常见的case了,之前接触和使用的都是Spring结合xml的方式,引入几个依赖,然后写个 logback.xml 配置文件即可,那么在SpringBoot中可以怎么做?...

小灰灰Blog ⋅ 37分钟前 ⋅ 0

冒泡排序

原理:比较两个相邻的元素,将值大的元素交换至右端。 思路:依次比较相邻的两个数,将小数放在前面,大数放在后面。即在第一趟:首先比较第1个和第2个数,将小数放前,大数放后。然后比较第...

人觉非常君 ⋅ 43分钟前 ⋅ 0

Vagrant setup

安装软件 brew cask install virtualboxbrew cask install vagrant 创建project mkdir -p mst/vmcd mst/vmvagrant init hashicorp/precise64vagrant up hashicorp/precise64是一个box......

遥借东风 ⋅ 今天 ⋅ 0

python3.6 安装pyhook_3

我的是在win下的,忙了半天老是安装不了, pip install 也不行。 那么可以看出自己的版本是32bit 一脸懵逼 没办法 只好下载32版本的来安装 我一直以为 是 对应32 位的 。 下面是 小例子 http...

之渊 ⋅ 今天 ⋅ 0

004、location正则表达式

1、location的作用 location指令的作用是根据用户请求的URI来执行不同的应用,也就是根据用户请求的网站URL进行匹配,匹配成功即进行相关的操作。 2、location的语法 = 开头表示精确匹配 ^~...

北岩 ⋅ 今天 ⋅ 0

CentOS7 静默安装 Oracle 12c

环境 CentOS7.5 最小安装 数据库软件 linuxx64_12201_database.zip 操作系统配置 关闭 SELinux sed -i '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config 关闭防火墙 systemctl disable ......

Colben ⋅ 今天 ⋅ 0

Yii2中findAll()的正确使用姿势/返回为空的处理办法

从一次错误的操作开始 $buildingObject = Building::findAll("status=1"); 1 这个调用看着没有任何毛病,但是在使用时返回的结果却是一个空数组。再回过头来看看数据表中: 按照套路来讲,查...

dragon_tech ⋅ 今天 ⋅ 0

如何优雅的编程——C语言界面的一点小建议

我们鼓励在编程时应有清晰的哲学思维,而不是给予硬性规则。我并不希望你们能认可所有的东西,因为它们只是观点,观点会随着时间的变化而变化。可是,如果不是直到现在把它们写在纸上,长久以...

柳猫 ⋅ 今天 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部