开发者社区> 流浪的猪猪> 正文

JVM 类加载机制

简介: 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)方法,改变类的查找机制

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
18228 0
Java类加载机制总结
加载(Loading) 加载过程是Java的一大特点,类的来源可以多种多样,压缩包、网络字节流、运行时动态计算生成(reflect)等等...这也造就了Java语言强大的动态特性。
819 0
java面试-深入理解JVM(八)——类加载的时机
类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载——>验证——>准备——>解析——>初始化——>使用——>卸载 其中,类加载包括5个阶段: 加载——>验证——>准备——>解析——>初始化 在类加载的过程中,以下3个过程称为连接: 验证——>准备——>解析 因此,JVM的类加载过程也可以概括为3个过程: 加载——>连接——>初始化 C/C++在运行前需要完成预处理、编译、汇编、链接;而在Java中,类加载(加载、连接、初始化)是在程序运行期间完成的。
1062 0
类加载器原理
1.类的加载过程   JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接又分为三个步骤,如下图所示: 1) 装载:查找并加载类的二进制数据; 2)链接: 验证:确保被加载类的正确性; 准备:为类的静态变量分配内存,并将其初始化为默认值; 解析:把类中的符号引用转换为直接引用;
1113 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
17622 0
深入理解JVM虚拟机读书笔记——类的加载机制
注:本文参考自周志明老师的著作《深入理解Java虚拟机(第3版)》,相关电子书可以关注WX公众号,回复 001 获取。
10 0
JVM类加载、验证、准备、解析、初始化、卸载过程详解(下)
JVM类加载、验证、准备、解析、初始化、卸载过程详解(下)
15 0
36
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
OceanBase 入门到实战教程
立即下载
阿里云图数据库GDB,加速开启“图智”未来.ppt
立即下载
实时数仓Hologres技术实战一本通2.0版(下)
立即下载