JVM类加载机制原理及用法解析

JVM类加载机制原理及用法解析

Java虚拟机是Java语言实现"Write Once, Run Anywhere"程序设计理念的一个关键组成部分,而Java虚拟机中最重要的一个子系统就是类加载子系统。该子系统负责对字节码文件(.class文件)中的类进行加载、验证、准备、解析、初始化等操作,从而在程序的运行中实现类的动态加载和管理。那么,下面我们就来详细讲解JVM类加载机制原理及用法解析的完整攻略。

类装载的五个阶段

Java虚拟机类加载机制分为以下五个阶段:

JVM类加载机制

1. 加载

类的加载是指将类的.class文件中的二进制数据读入到内存中,将其放入运行时数据区的方法区内(堆区存放对象实例)。同时在内存中生成一个表示该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

2. 验证

这个步骤主要目的是确保Class文件的字节流中包含的信息是符合JVM规范,并且不会危害JVM的各种安全特性。

3. 准备

准备阶段是为类的静态变量分配内存,并为其赋上默认值。这里需要注意的是,这里赋值的是默认值,而不是程序中代码指定的值。

4. 解析

解析阶段是将常量池中的符号引用替换为直接引用的过程。也就是说,在这个阶段会把类、接口、方法和字段的符号引用解析为直接引用。

5. 初始化

初始化阶段是执行类构造器 ()方法的过程。这个方法并不是程序员自己定义的方法,而是由编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并产生的。

可以看到,初始化阶段是整个类加载过程中最后一个阶段,这个阶段非常重要,只有在这个阶段,class才真正开始执行。

类的加载时机

类的加载时机有以下三种:

  1. 当虚拟机启动时,初始化用户指定的主类,即main方法所在的类。
  2. 当遇到new指令时,初始化new指令后的对象所属的类。
  3. 当遇到调用静态方法或读取/设置一个静态字段时,初始化该静态方法所在的类。

在这三种情况中,第一种是最常见的,也是最直观的。第二种和第三种只有在当程序中使用到该类时会进行加载。

举个例子:下面有两个类,一个是主类,一个是Car类,代码如下:

public class Main {
    public static void main(String[] args) {
        new Car();
    }
}

public class Car {
    public Car() {
        System.out.println("Class Car init!");
    }
}

在代码执行时,当我们执行Main类中的main方法时,就会通过new关键字创建一个Car对象。这时就会触发对Car类的加载。由于我们只是用到了该类的构造方法,因此并不会触发该类的初始化。

类的加载机制

类的加载机制分为以下两种:

1. 多个类加载器的情况

  • 启动类加载器(Bootstrap):也叫根加载器。这个类是由C++实现的,是虚拟机自带的类加载器,它负责加载虚拟机自身的类(位于<JAVA_HOME>/lib下的jar包,如rt.jar、charsets.jar等)。

  • 扩展类加载器(Extension):也叫扩展加载器。它用来加载Java的扩展类库,默认加载<JAVA_HOME>/jre/lib/ext/目下的jar包。

  • 应用程序类加载器(Application):也叫系统加载器。它负责加载用户自定义的类。Java应用通常是由它来进行类加载的。可以通过Thread.currentThread().getContextClassLoader()来获取当前线程的类加载器。

除以上三种以外,还有一种叫做“自定义加载器”。如果需要实现特定的需求时,可以通过继承ClassLoader类,让Java程序拥有自己定制化的类加载器。

2. 双亲委派模型

双亲委派模型的工作流程是:如果一个类加载器(子加载器)收到了一个类加载请求,它首先不会自己去加载这个请求,而是把请求委派给父类加载器(如果有的话)去执行加载。

  • 如果父类加载器可以完成类加载任务,就成功返回,如果不能完成,则子加载器尝试自己去加载该类。
  • 如果还不成功,则回到跟加载器(BootStrap ClassLoader)来加载。

这种方式可以保证Java核心类库的类型安全,即Java核心类库都是由Bootstrap加载器加载的,而不是由用户自定义的类加载器来加载的。这种方式可以保证Java的核心类库不被篡改,从而提高了Java应用的稳定性和安全性。

代码示例

下面我们通过一个简单的示例来进一步说明类加载机制及双亲委派模型的使用。

public class ClassLoaderTest {

    public static void main(String[] args) {
        System.out.println("ClassLoaderTest类的加载器是:" + ClassLoaderTest.class.getClassLoader());
        System.out.println("String类的加载器是:" + String.class.getClassLoader());
    }

}

运行上述代码,可以看到结果如下:

ClassLoaderTest类的加载器是:sun.misc.Launcher$AppClassLoader@18b4aac2
String类的加载器是:null

从上面的输出结果可以看出:

  • ClassLoaderTest的类加载器是AppClassLoader(即应用程序类加载器)。
  • String类的类加载器是null,这是因为String类是由Bootstrap加载器(启动类加载器)加载的。

下面,我们再通过代码示例来进一步说明恰当使用自定义类加载器的方法:

public class MyClassLoader extends ClassLoader {

    private String classPath;

    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    public Class<?> findClass(String name) {
        byte[] data = getClassData(name);
        return defineClass(name, data, 0, data.length);
    }

    private byte[] getClassData(String name) {
        String path = classPath + "/" + name.replaceAll("\\.", "/") + ".class";
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(path);
            out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
            return out.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public static void main(String[] args) throws ClassNotFoundException {
        MyClassLoader classLoader = new MyClassLoader("/Users/zhangshanming/Desktop");
        Class clazz = classLoader.loadClass("User");
        System.out.println(clazz.getClassLoader());
    }
}

public class User {
    static {
        System.out.println("User被加载");
    }
}

运行上述代码,可以得到输出结果:

User被加载
MyClassLoader@18b4aac2

从上面的输出结果可以看出:

  • MyClassLoader这个类是继承自ClassLoader,用于实现自定义的类加载器。
  • MyClassLoader的findClass方法被重写,用于实现从指定路径中加载指定类名的类的字节码文件,并返回其Class对象。
  • 接下来根据加载class的名字去加载这个Class,会发现在加载User类的时候,先是使用了MyClassLoader这个类来进行加载,最终生成并返回了User的Class对象。而这个Class对象是属于MyClassLoader实例的,它的Classloader属性得到的值正是实例MyClassLoader本身,也就说明了该类是由MyClassLoader来执行加载请求的。

通过这个示例,我们可以看出,通过自己的类加载器来加载类,可以实现自己的定制化需求。当然,切换加载器实例,也使加载成本加大,因此,我们一般使用Java提供的框架和第三方工具类库来尽量避免此类问题的发生。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:JVM类加载机制原理及用法解析 - Python技术站

(0)
上一篇 2023年6月15日
下一篇 2023年6月15日

相关文章

  • Sprint Boot @SessionAttributes使用方法详解

    在Spring Boot中,@SessionAttributes注解用于将模型属性存储在会话中,以便在多个请求之间共享。在本文中,我们将详细介绍@SessionAttributes注解的作用和使用方法,并提供两个示例。 @SessionAttributes注解的作用 @SessionAttributes注解用于将模型属性存储在会话中,以便在多个请求之间共享。…

    Java 2023年5月5日
    00
  • JavaWeb文件上传下载功能示例解析

    JavaWeb文件上传下载功能示例解析 文件上传功能 基本原理 文件上传是将本地文件发送到服务器保存的过程。通过HTTP协议,客户端将文件数据发送到服务器端,服务器接收到数据后将其存储到指定的目录中。 在本例中,我们使用了Apache的文件上传组件commons-fileupload来实现文件上传功能。 实现步骤 引入相关依赖。 xml <depend…

    Java 2023年5月19日
    00
  • java实现打砖块游戏算法

    下面是详细讲解“Java实现打砖块游戏算法”的完整攻略: 1. 游戏规则 在开始讲解算法之前,首先需要了解砖块游戏的规则: 游戏区域由一个矩形网格构成,其中有一些砖块。 游戏中有一个挡板,玩家可以通过控制挡板来阻挡弹球。 玩家需要控制弹球击中砖块,摧毁所有砖块才能过关。 弹球碰到挡板或者砖块边缘会反弹。 2. 实现思路 要想实现砖块游戏算法,需要先了解以下几…

    Java 2023年5月19日
    00
  • 从零搭建SpringBoot+MyBatisPlus快速开发脚手架

    从零搭建SpringBoot+MyBatisPlus快速开发脚手架 在实际开发中,我们经常需要使用SpringBoot和MyBatisPlus来快速开发应用程序。本文将手把手教你如何从零开始搭建SpringBoot+MyBatisPlus快速开发脚手架,包括创建项目、添加依赖、配置数据源、创建实体类、创建Mapper接口、使用MyBatisPlus的CRUD…

    Java 2023年5月14日
    00
  • 在Java代码中解析html,获取其中的值方法

    要在Java代码中解析html,获取其中的值,可以使用Jsoup这个第三方开源库。下面是使用Jsoup的步骤: 第一步:导入Jsoup库 使用Maven导入依赖: <dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId…

    Java 2023年5月26日
    00
  • Java基于ShardingSphere实现分库分表的实例详解

    Java基于ShardingSphere实现分库分表的实例详解 ShardingSphere是一款开源的分布式数据库中间件,支持对MySQL、Oracle、SQLServer等关系型数据库进行分库分表。本文将详细讲解在Java项目中如何基于ShardingSphere实现分库分表的方法。 步骤一:引入依赖 在Java项目的pom.xml文件中引入Shardi…

    Java 2023年5月20日
    00
  • Java中Date日期时间类具体使用

    Java中Date日期时间类具体使用 在Java中,Date类是表示日期和时间的类。它是以毫秒为单位存储日期和时间的。Date类是Java中操作日期和时间最基本的类之一,下面我们来详细讲解一下关于Date类的使用。 1. 创建Date对象 Date对象可以通过无参构造方法创建,该方法将创建一个表示当前时间的Date对象。也可以通过给定时间戳的方式创建,表示从…

    Java 2023年5月20日
    00
  • 基于Java实现文件和base64字符串转换

    下面是基于Java实现文件和base64字符串转换的攻略: 1.前置知识 在进行文件和base64字符串转换时,需要了解以下知识: 文件读写的基本操作 Base64编码和解码的原理和实现方式 2.实现步骤 2.1 文件转base64字符串 文件转base64字符串的过程可以分解为以下几步: 步骤1:将文件转换为字节数组 首先,需要将文件读取到内存中并将其转换…

    Java 2023年5月27日
    00
合作推广
合作推广
分享本页
返回顶部