阅读 128

JVM的类加载机制

JVM的类加载机制

一 类加载是什么?什么时候加载类?加载过程是怎么样的?

1.1 什么是类加载

类加载就是指将在硬盘上编译好的class字节码文件,加载到JVM内存中,然后对字节码文件进行链接和初始化的过程。只有加载后的字节码文件才可以被执行引擎解释和执行。


1.2 类加载的时机

JVM并不是启动的时候,就把全部的class字节码加载到内存,而是根据运行时候需要进行加载。比如没有加载到内存的类能够实例化吗?要访问静态类的变量或者方法,如果所在类没有被加载到内存,能访问静态变量和或者静态方法吗?

第一:创建类实例的时候,肯定要加载的,比如new、反射等

第二:访问类的静态变量或者静态方法

第三:加载子类的时候,父类也需要被加载,否则子类继承的父类属性或者方法有可能访问不到

第四:包含main方法的主类肯定也是要加载的


1.3 类加载过程

类的加载过程主要分为三个阶段: 加载、链接和初始化。


1.3.1 加载

第一:根据类的全限定名(包名+类名),获取二进制字节流

第二:将二进制字节流加载到内存,字节码文件中的常量池中的字面量如果是字符串则放到堆中字符串常量池,符号引号放到直接内存;静态变量和方法放入也放入堆中

第三:根据字节码信息创建Class对象放入到堆中


1.3.2 链接

链接主要是将目标文件链起来,和C/C++程序的链接要实现的效果是一样的,他主要包括三个阶段:验证、准备和解析


1.3.2.1 验证

第一: 验证class文件的格式。比如魔数、版本号大小

第二:验证元数据信息,比如继承的父类是否可以被继承;如果不是抽象类是否实现了所有的接口

第三:符号引用验证,是否可以根据全限定名找到对应的类,符号引用中的类的字段和方法是否可以被当前类访问


1.3.2.3 解析

解析就是将符号引用替换为直接引用的过程。在编译的时候无法确定引用对象的地址(还没有分配),是不知道实际的物理地址,所以才用一个唯一的符号来代替。而且经过验证过程知道符号引号的类是可以访问的到的,所以在解析这一步的时候直接将符号引号替换为真实的地址,这一步就是将目标文件链接起来了


1.3.3 初始化

执行类的初始化代码比如静态变量赋值或者执行静态代码块。初始化不是在准备阶段已经分配空间和给了默认值吗,但是注意,并没有在准备阶段执行初始化代码。比如这段代码:


public class JVMClient {


    private static long interval = Long.parseLong(System.getProperty("rebalance.interval"),20000);

}

1

2

3

4

interval 在准备阶段会在堆中分配8字节的内存,然后初始值为0,Long.parseLong(System.getProperty(“rebalance.interval”),20000);现在在在初始化阶段,则会执行后面的这段代码,比如rebalance.interval=10000,interval=10000


又比如这段代码,静态代码块也会在这个时候执行:


public class JVMClient {


    private static long interval = Long.parseLong(System.getProperty("rebalance.interval"),20000);


    private static List<String> nodeList;


    static {

        nodeList = getNodeList();

    }


    private static List<String> getNodeList() {

        return new ArrayList<String>();

    }

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

类初始化的时机,也可以说是类的加载时间,因为只要类被加载了肯定需要经历初始化阶段

第一:创建类实例的时候,需要加载类到JVM内存,然后就会经历链接和初始化阶段

第二:访问一个类的静态变量或者静态方法,需要加载类到JVM内存,然后这个类就会经历链接和初始化阶段

第三:加载一个类的时候,如果发现父类还没有加载,则也会加载父类并初始化父类

第四:main方法所在的类会被加载,所以也会初始化


二 类加载器有哪些?双亲委派模型的工作机制是怎样的?

2.1 类的加载器有哪些

2.1.1 Bootstrap类加载器

主要负责加载%JAVA_HOME%/lib下面的非ext包的所有类库,常用的java.util.、java.io.、java.lang.*都是由根加载器加载的


2.1.2 Extension类加载器

主要负责加载%JAVA_HOME%/lib/ext下面的所有扩展类库,比如内置的Javascript引擎、XML解析器等,这些类库以javax开头,它们的jar包位于%JAVA_HOME%/lib/ext目录中。


2.1.3 Application类加载器

主要负责加载应用程序classpath类路径上的类库,我们自己编写的代码以及引入的第三方JAR包都是由它来加载。


2.1.4 自定义类加载器

一般场景,使用Java默认的类加载器即可,但有时为了达到某种目的,又不得不实现自己的类加载器,例如为了使类库互相隔离,又或者为了实现热部署重加载功能,这时候就需要定义自己的类加载器,每一个类加载器加载各自的资源,以此达到资源隔离效果。可以沿用双亲委派机制,也可以打破这个机制。


2.2 双亲委派机制

第一:假设你的Application ClassLoader应用程序类加载器需要加载一个类,他不是自己去加载,而是先委托父类加载器Extension ClassLoader去加载

第二:Extension ClassLoader判断自己有父类加载器,则继续委托父类加载器Bootstrap ClassLoader去加载

第三:Bootstrap ClassLoader是根类加载器,没有父类加载器,则不再委托,它会在%JAVA_HOME%/lib/下去找要加载的类,

如果没有,则告诉Extension ClassLoader扩展类加载器没有找到

第四:Extension ClassLoader扩展类加载器则在%JAVA_HOME%/lib/ext目录下找,如果没有找到则告诉Application ClassLoader

应用程序类加载器

第五:应用程序类加载器则会在类路径下查找要加载的类


所以,这就输双亲委派模型,先找父类加载器要,没有则子类加载器自己找。这样可以避免多层级的类加载器重复加载某些类。

如果应用程序有类和%JAVA_HOME%/lib/或者%JAVA_HOME%/lib/ext下的类一样,则不会加载类路径下的类


2.3 tomcat类加载器

tomcat中拥有不同的自定义类加载器,以实现对各种资源的控制。一般来说,tomcat主要用类加载器解决以下四个问题:

#1 同一个web服务器,各个web项目之前各自使用的Java类库互相隔离

#2 同一个web服务器,各个web项目之间可以共享的Java类库

#3 服务器的类库与应用程序的类库互相独立

#4 对于支持JSP的web容器,应该支持热插拔的(HotSwap)功能


tomcat中最重要的类加载器是Common类加载器,他的父类是应用程序类加载器,负责加载CATALINA_BASE/lib,CATALINA_HOME/lib两个目录下所有的class文件和jar文件。


三 如何自定义类加载器

3.1 继承ClassLoader

3.2 重写findClass方法

public class MyClassLoader extends ClassLoader {

    //要加载的java类的classpath路径

    private String classPath;


    public MyClassLoader( String classPath) {

        super(ClassLoader.getSystemClassLoader());

        this.classPath = classPath;

    }


    @Override

    protected Class<?> findClass(String name) throws ClassNotFoundException {

        byte[] data = this.loadData(name);

        //返回加载后的Class对象

        return this.defineClass(name, data, 0, data.length);

    }

    

    private byte[] loadData(String name) {

        try {

            name = name.replace(".", "/");

            FileInputStream is = new FileInputStream(new File(classPath + "/" +name + ".class"));

            ByteArrayOutputStream bos = new ByteArrayOutputStream();

            int b = 0;

            while ((b = is.read()) != -1) {

                bos.write(b);

            }

            is.close();

            return bos.toByteArray();

        } catch (Exception e) {

            e.printStackTrace();

        }

        return null;

    }


    public String getClassPath() {

        return classPath;

    }


    public void setClassPath(String classPath) {

        this.classPath = classPath;

    }

}

————————————————

版权声明:本文为CSDN博主「happy19870612」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/zhanglh046/article/details/115843150


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐