0
点赞
收藏
分享

微信扫一扫

2. JVM类加载机制

Sophia的玲珑阁 2021-09-19 阅读 33
摸鱼JVMJVM

前言

我们知道, 在java开发中, .java文件会被编译超成一个个.class文件, 最终被JVM加载和运行.

什么是类的加载

我们编写的java文件都是保存着业务逻辑代码,java编译器将 .java 文件编译成扩展名为 .class 的文件,.class 文件中保存着java转换后,虚拟机将要执行的指令,
当需要某个类的时候,java虚拟机会加载 .class 文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程被称为类的加载.

类加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口.

类加载器并不需要等到某个类被“首次主动使用”时再加载它, JVM规范允许类加载器在预料某个类将要被使用时就预先加载它;

如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误.

知道了什么类的加载, 下面我们就来了解下JAVA类的生命周期

JAVA类的生命周期

可以看出, JAVA类的生命周期如下:

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化
  • 使用
  • 卸载

类加载的过程包括了加载、验证、准备、解析、初始化五个阶段.

在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的;
而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定).

加载

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

  1. 通过一个类的全限定名来获取其定义的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

相对于类加载的其他阶段而言,加载阶段是可控性最强的阶段, 因为开发人员既可以使用系统提供的类加载器来完成加载, 也可以使用自己定义的类加载器来完成加载.

JVM类加载器

类的加载由类加载器完成,类加载器通常由JVM提供.

JVM提供的这些类加载器通常被称为系统类加载器;除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器.

JVM预定义有三种类加载器:

  • 启动类加载器(Bootstrap ClassLoader)
    用来加载 Java 的核心类,是用原生代码来实现的,并不继承自java.lang.ClassLoader(负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类);
    由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作
//获得根类加载器所加载的核心类库,并会看到本机安装的Java环境变量指定的jdk中提供的核心jar包路径
public class ClassLoaderTest {
    public static void main(String[] args) {
        URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        for(URL url : urls){
            System.out.println(url.toExternalForm());
        }
    }
}

结果

file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_201.jdk/Contents/Home/jre/classes
  • 扩展类加载器(Extension ClassLoader)
    它负责加载JRE的扩展目录,lib/ext或者由java.ext.dirs系统属性指定的目录中的JAR包的类, 由Java语言实现,父类加载器为null

  • 应用程序类加载器(Application ClassLoader)
    被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径.
    程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器;如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器,由Java语言实现,父类加载器为ExtClassLoader.

  1. 检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步
  2. 如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步
  3. 请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步
  4. 请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步
  5. 当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步
  6. 从文件中载入Class,成功后跳至第8步
  7. 抛出ClassNotFountException异常
  8. 返回对应的java.lang.Class对象
JVM类加载机制

JVM的类加载机制主要有如下3种:

  • 全盘负责
    所谓全盘负责,就是当一个类加载器负责加载某个Class时,该Class所依赖和引用其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入

  • 缓存机制
    缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓冲区中.这就是为什么修改了Class后,必须重新启动JVM,程序所做的修改才会生效的原因

  • 双亲委派
    双亲委派就是如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成, 若成功则直接返回, 否则继续向上,直到到达最顶层的类加载器;
    因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器无法完成该加载请求时,子加载器才会尝试自己去加载该类

JVM类加载方式

JVM有3种类加载方式:

  • 命令行启动应用时候由JVM初始化加载

  • 通过ClassLoader.loadClass()方法动态加载
    将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块;
    Classloader.loaderClass得到的class是还没有连接(验证、准备、解析)的

  • 通过Class.forName()方法动态加载
    将类的.class文件加载到jvm中,还会对类进行解释,执行类中的static块; Class.forName()得到的class是已经初始化完成的

实例:

package com.test.classloader;
public class loaderTest { 
        public static void main(String[] args) throws ClassNotFoundException { 
    ClassLoader loader = HelloWorld.class.getClassLoader(); 
    System.out.println(loader); 
    //使用ClassLoader.loadClass()来加载类,不会执行初始化块 
    loader.loadClass("TestClass"); 
    //使用Class.forName()来加载类,默认会执行初始化块 
    Class.forName("TestClass"); 
    //使用Class.forName()来加载类,并指定ClassLoader,初始化时不执行静态块 
    Class.forName("TestClass", false, loader); 
       } 
}

public class Test { 
        static { 
                System.out.println("静态初始化块执行了!"); 
        } 
}

验证

例如:这个类是否有父类, 除了java.lang.Object之外;

准备

解析

初始化

JVM负责对类进行初始化,主要对类变量进行初始化.

举报

相关推荐

0 条评论