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日

相关文章

  • springboot使用ThreadPoolTaskExecutor多线程批量插入百万级数据的实现方法

    下面我来详细讲解一下“springboot使用ThreadPoolTaskExecutor多线程批量插入百万级数据的实现方法”的攻略。 1. 什么是ThreadPoolTaskExecutor ThreadPoolTaskExecutor是Spring内置的线程池实现类,它可以通过简单的配置就能够创建一个线程池,并且可以对线程池进行调度和管理。 2. 使用T…

    Java 2023年5月19日
    00
  • springboot日期格式化及时差问题分析

    下面我将为你介绍有关“springboot日期格式化及时差问题分析”的完整攻略。 1. 前言 在日常开发中,很多场景需要对时区、日期格式进行处理,如果不处理好,就可能会导致一些问题,如时差问题等,本文将介绍如何使用SpringBoot来处理日期格式化及时差问题。 2. 日期格式化 在Java中,日期格式化主要是通过SimpleDateFormat类实现。在S…

    Java 2023年5月20日
    00
  • Springboot详解线程池与多线程及阻塞队列的应用详解

    Spring Boot详解线程池与多线程及阻塞队列的应用详解 概述 在 Java 中使用线程池和多线程可以提高程序的并发处理能力,加快计算速度。Spring Boot 提供了良好的支持,本文将介绍 Spring Boot 中线程池与多线程及阻塞队列的应用,并通过示例说明。 线程池 线程池的作用 线程池可以减少线程的创建和销毁所带来的性能开销,并可以对并发执行…

    Java 2023年5月19日
    00
  • Java 数据结构与算法系列精讲之字符串暴力匹配

    Java 数据结构与算法系列精讲之字符串暴力匹配 1. 基本概念 字符串匹配是一种非常常见的算法问题。给定一个字符串 A 和一个模式串 B,要求在字符串 A 中查找是否有 B 出现的位置,如果有,则返回第一次出现的位置,否则返回-1。字符串暴力匹配就是一种解决此问题的算法,它的基本思路就是从字符串 A 中从头开始一个字符一个字符地去匹配模式串 B 的每个字符…

    Java 2023年5月19日
    00
  • Java图形用户界面设计(Swing)的介绍

    Java图形用户界面设计(Swing)的介绍 概述 Java Swing 是一个GUI工具包由Sun Microsystems创建,它允许程序员使用Java创建图形用户界面(GUI)在基于Java的应用程序中。Swing是Java平台中一个独立于操作系统的 GUI 工具箱,适用于Java SE和Java EE平台。 特点 Java Swing是一个跨平台的G…

    Java 2023年5月24日
    00
  • Spring Boot 入门教程

    SpringBoot入门教程 SpringBoot是一个快速开发、轻量级、微服务框架,它简化了Spring应用的开发过程,提供了自动化配置、可插拔的组件和简化的XML配置等特点,使得SpringBoot成为当前企业级Java应用开发的主流框架之一。本教程旨在帮助读者从入门到掌握SpringBoot,实现快速且高效的应用开发。 环境搭建 在开始使用Spring…

    Java 2023年5月15日
    00
  • 微信小程序实时聊天WebSocket

    下面为您详细讲解“微信小程序实时聊天WebSocket”的完整攻略。 一、前期准备 了解WebSocket协议的基础知识,包括握手过程、消息格式等; 了解微信小程序基础知识,包括小程序开发、页面结构、组件等; 确保开发环境已经安装好,包括微信web开发者工具、编辑器等。 二、创建WebSocket连接 微信小程序提供了wx.connectSocket() A…

    Java 2023年5月23日
    00
  • Java的后台文件夹下文件的遍历完整代码

    下面给您详细讲解Java后台文件夹下文件遍历的完整攻略。 一、文件夹遍历基本原理 首先需要一个File对象,用来表示文件夹或文件; 通过该File对象调用listFiles()方法获取该文件夹下的所有子文件或子文件夹; 遍历得到的子文件或子文件夹,如果是文件夹,递归调用自身方法,如果是文件,则可以直接操作。 二、Java后台文件夹遍历完整代码 import …

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