JVM 类加载机制

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: jvm将描述java类的.class的字节码文件加载到内存中,并对文件中的数据进行安全性校验、解析和初始化,最终形成可以被java虚拟机直接使用的java类型,这个复杂的过程为jvm的类加载机制 类从被java到虚拟机内存开始,直到被卸载出内存为止,整个生命周期如上图所示,分为:加载、验证、准备、解析、初始化、使用、卸载共7个阶段。

jvm将描述java类的.class的字节码文件加载到内存中,并对文件中的数据进行安全性校验、解析和初始化,最终形成可以被java虚拟机直接使用的java类型,这个复杂的过程为jvm的类加载机制

image

类从被java到虚拟机内存开始,直到被卸载出内存为止,整个生命周期如上图所示,分为:加载、验证、准备、解析、初始化、使用、卸载共7个阶段。

class类的初始化时机

虚拟机规范严格规定,有且只有5种情况,必须立即对类进行初始化:

  • 1、遇到new(对应新建对象)、getstatic(对应访问类变量、static final除外)、putstatic(对应设置类变量、static final除外)、invokestatic(调用类方法即静态方法)这4条指令时,如果类没有初始化,则需要先触发其初始化。
  • 2、对类进行反射调用时,如果类没有初始化,则必须触发初始化
  • 3、当初始化一个类时,如果其父类没有初始化,则必须先初始化他的父类
  • 4、虚拟机执行的main方法入口,这个类必须初始化
  • 5、JDK1.7.动态语言支持时,如果java.lang.invoke.MethodHandler指向的是REF_getstatic,REF_putstatic、REF_invokestatic的方法句柄时,必须先初始化这个类

这两种情况不会进行类的初始化

  • 1、申明数组类:Cat [] cat = new Cat[10];
  • 2、如下,B类不会初始化,A类会初始化
public class Test {
    public static void main(String[] args) {
        System.out.println(B.value); 
    }
}

class A {
    public static int value = 123;
    
}

class B extends A {
    
}

类加载的过程

加载

image

在加载阶段,虚拟机需要完成以下3件事情:

  • 1、通过一个类的全限定名来获取定义此类的二进制字节流。对于字节流的获取,jvm并没有明确的限制

    • 从ZIP包中读取
    • 从网络中获取
    • 运行时计算生成
    • 从其他文件生成,如JSP技术
  • 2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  • 3、在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

连接

连接是很重要的一步,过程比较复杂,分为三步 验证 》准备 》解析   

  • 验证:确保类加载的正确性。一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编 译规则,这一步就是要过滤掉这部分不合法文件 
  • 准备:为类的静态变量分配内存,将其初始化为默认值 。我们都知道静态变量是可以不用我们手动赋值的,它自然会有一个初始值 比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的 
  • 解析:把类中的符号引用转化为直接引用。解释一下符号引用和直接引用。比如在方法A中使用方法B,A(){B();},这里的B()就是符号引用,初学java时我们都是知道这是java的引用,以为B指向B方法的内存地址,但是这是不完整的,这里的B只是一个符号引用,它对于方法的调用没有太多的实际意义,可以这么认为,他就是给程序员看的一个标志,让程序员知道,这个方法可以这么调用,但是B方法实际调用时是通过一个指针指向B方法的内存地址,这个指针才是真正负责方法调用,他就是直接引用。

初始化

  • 初始化阶段是执行类构造器()方法的过程。类构造器()方法是由编译器自动收藏类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生
  • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
  • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
  • 当范围一个Java类的静态域时,只有真正声名这个域的类才会被初始化
  • 类的主动引用(一定会发生类的初始化)

    • new一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当虚拟机启动,java Demo01,则一定会初始化Demo01类,说白了就是先启动main方法所在的类
    • 当初始化一个类,如果其父类没有被初始化,则先初始化它父类
  • 类的被动引用(不会发生类的初始化)

    • 当访问一个静态域时,只有真正声名这个域的类才会被初始化

      • 通过子类引用父类的静态变量,不会导致子类初始化
    • 通过数组定义类的引用,不会触发此类初始化
    • 引用常量不会触发此类的初始化(常量在编译阶段就存入调用类的常量池中了)

jvm类加载器

虚拟机设计团队把加载动作放到JVM外部实现,以便让应用程序决定如何获取所需的类,实现这个动作的代码模块称为“类加载器”。JVM提供了3种类加载器:

  • 启动类加载器(Bootstrap ClassLoader):负责加载 JAVA_HOMElib 目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
  • 扩展类加载器(Extension ClassLoader):负责加载 JAVA_HOMElibext 目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
  • 应用程序类加载器(Application ClassLoader):负责加载用户路径(classpath)上的类库。
  • 用户自定义类加载器(Customized Class Loader):用户可以自己定义类加载器来加载类。所有的类加载器都要继承java.lang.ClassLoader类。

JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。

image

类加载器加载的class在堆内存中的模型:
image

认识双亲委派模型

对于某个特定的类加载器而言,应该为其指定一个父类加载器,当用其进行加载类的时候:

    1. 委托父类加载器帮忙加载;
    1. 父类加载器加载不了,则查询引导类加载器有没有加载过该类;
    1. 如果引导类加载器没有加载过该类,则当前的类加载器应该自己加载该类;
    1. 若加载成功,返回 对应的Class 对象;若失败,抛出异常“ClassNotFoundException”。

请注意:
双亲委派模型中的"双亲"并不是指它有两个父类加载器的意思,一个类加载器只应该有一个父加载器。上面的步骤中,有两个角色:

    1. 父类加载器(parent classloader):它可以替子加载器尝试加载类
    1. 引导类加载器(bootstrap classloader): 子类加载器只能判断某个类是否被引导类加载器加载过,而不能委托它加载某个类;换句话说,就是子类加载器不能接触到引导类加载器,引导类加载器对其他类加载器而言是透明的。

双亲模型如下图:
image

AppClassLoader的工作流程如下图:
image

双亲委派模型底层代码实现:

//提供class类的二进制名称表示,加载对应class,加载成功,则返回表示该类对应的Class<T> instance 实例
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
 
    
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查是否已经被当前的类加载器记载过了,如果已经被加载,直接返回对应的Class<T>实例
            Class<?> c = findLoadedClass(name);
                //初次加载
                if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        //如果有父类加载器,则先让父类加载器加载
                        c = parent.loadClass(name, false);
                    } else {
                        // 没有父加载器,则查看是否已经被引导类加载器加载,有则直接返回
                        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.
                    long t1 = System.nanoTime();
                    // 自己尝试加载
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            //是否解析类 
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

如何破坏双亲委托模型:

  • 1、在构造类加载器时将parent设为null(构造函数参数为null即可,可以看下源码)
  • 2、重写loadloadClass(String,boolean)方法,改变类的查找机制
相关文章
|
7月前
|
安全 Java
对 JVM 的类加载机制以及寻找字节码文件的“双亲委派模型”的理解
对 JVM 的类加载机制以及寻找字节码文件的“双亲委派模型”的理解
43 0
|
7月前
|
安全 前端开发 Java
JDK源码级别彻底剖析JVM类加载机制
JDK源码级别彻底剖析JVM类加载机制
|
2月前
|
存储 Java C语言
【JVM】类加载机制
【JVM】类加载机制
23 1
|
4月前
|
数据库 C# 开发者
WPF开发者必读:揭秘ADO.NET与Entity Framework数据库交互秘籍,轻松实现企业级应用!
【8月更文挑战第31天】在现代软件开发中,WPF 与数据库的交互对于构建企业级应用至关重要。本文介绍了如何利用 ADO.NET 和 Entity Framework 在 WPF 应用中访问和操作数据库。ADO.NET 是 .NET Framework 中用于访问各类数据库(如 SQL Server、MySQL 等)的类库;Entity Framework 则是一种 ORM 框架,支持面向对象的数据操作。文章通过示例展示了如何在 WPF 应用中集成这两种技术,提高开发效率。
63 0
|
4月前
|
安全 前端开发 Java
【JVM 探秘】ClassLoader 类加载器:揭秘 Java 类加载机制背后的秘密武器!
【8月更文挑战第25天】本文全面介绍了Java虚拟机(JVM)中的类加载器,它是JVM的核心组件之一,负责将Java类加载到运行环境中。文章首先概述了类加载器的基本工作原理及其遵循的双亲委派模型,确保了核心类库的安全与稳定。接着详细阐述了启动、扩展和应用三种主要类加载器的层次结构。并通过一个自定义类加载器的例子展示了如何从特定目录加载类。此外,还介绍了类加载器的完整生命周期,包括加载、链接和初始化三个阶段。最后强调了类加载器在版本隔离、安全性和灵活性方面的重要作用。深入理解类加载器对于掌握JVM内部机制至关重要。
167 0
|
7月前
|
前端开发 Java 数据库连接
JVM(类加载机制)
JVM(类加载机制)
61 4
|
7月前
|
存储 缓存 安全
深入浅出JVM(三)之HotSpot虚拟机类加载机制
深入浅出JVM(三)之HotSpot虚拟机类加载机制
|
存储 算法 Java
Android 面试必备 - JVM 及 类加载机制
Android 面试必备 - JVM 及 类加载机制
|
7月前
|
前端开发 Java
深入理解Java虚拟机:类加载机制
【2月更文挑战第23天】本文深入探讨了Java虚拟机(JVM)的类加载机制,包括类加载器的层次结构、类加载的过程以及双亲委派模型。通过对JVM类加载机制的理解,可以帮助我们编写更高效的Java代码。
|
7月前
|
安全 Java 应用服务中间件