当前位置: 首页 > 科技观察

jvm类加载器,类加载机制详解,看这一篇就够了

时间:2023-03-12 11:28:51 科技观察

Jvm类加载器,类加载机制详解,这篇看够呛了。jvm的启动是通过bootstrap类加载器创建一个初始类(initialclass)来完成的,由jvm的具体实现指定。[来自官方规范]jvm的组件之一是类加载器子系统。今天就来详细说说这个组件。Java代码执行流程图通过这个流程图,你可以了解我们写的Java代码是如何执行的。其中,我们要经过类加载器的过程。下面详细说一下这里的知识点。类加载子系统类加载系统架构图这两张图你暂时看不懂也没关系。跟着小弟往下看一个类的生命周期。类的生命周期包括:加载、链接、初始化、使用、卸载。其中loading、linking、Initialization属于类加载的过程,下面我们会详细说明。使用意味着我们的新对象被使用,而卸载意味着该对象被垃圾收集。类加载过程的第一步:Loading通过类的完全限定名(包名+类名)加载,获取类的.class文件的二进制字节流,将其表示的静态存储结构转换为二进制字节流为方法区的运行时数据结构生成一个代表内存中类的java.lang.Class对象,作为该类在方法区的各种数据的访问入口总结:加载二进制数据到内存->映射tojvm可以识别Structure—>在内存中生成class文件。第二步:LinkingLinking是指将上面创建的类合并到Java虚拟机中,使其可以执行的过程。它可以分为三个阶段:验证、准备和分析。①验证(Verify)确保类文件中字节流所包含的信息符合当前虚拟机的要求,保证加载类的正确性,不危及虚拟机的安全。②准备(Prepare)为类中的静态字段分配内存,并设置默认初始值,例如int类型的初始值为0。final修饰的静态字段不会被设置,因为final是分配的在编译时③解析阶段的目的是将常量池中的符号引用转换为直接引用成为实际引用)。如果符号引用指向一个没有被加载的类,或者是一个没有被加载的类的字段或方法,那么解析就会触发这个类的加载(但不一定触发这个类的链接和初始化。)实际上,解析器操作往往伴随着,这意味着JVM在执行初始化之后执行。符号引用是一组描述引用对象的符号。《Java 虚拟机规范》的Class文件格式中明确定义了符号引用的字面形式。直接引用是直接指向目标的指针、相对偏移量或间接定位目标的句柄。解析动作主要是针对类、接口、字段、类方法、接口方法、方法类型等。对应常量池中的CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info等。第三步:初始化初始化初始化就是执行类构造函数init()的过程。这个方法不需要定义,它是javac编译器自动收集的类中所有类变量的赋值动作和静态代码块中语句的组合。如果类有父类,jvm会保证先执行父类的init,再执行子类的init。类加载器的分类第一种:启动类/引导类:BootstrapClassLoader是用C/C++语言实现的,嵌套在JVM内部的类加载器。Java程序不能直接操作这个类。用于加载Java核心类库,如:JAVA_HOME/jre/lib/rt.jar、resources.jar、sun.boot.class.path路径下的包,用于提供需要的包jvm运行。不是继承自java.lang.ClassLoader,它没有父类加载器。它加载扩展类加载器和应用程序类加载器,并成为它们的父类加载器。出于安全考虑,启动类只加载包名:java、javax、sun开头的类第二个:扩展类加载器:ExtensionClassLoader是用Java语言编写的,由sun.misc.Launcher$ExtClassLoader实现。我们可以使用Java程序来操作这个加载器。它派生并继承自java.lang.ClassLoader。父类加载器是启动类加载器,从系统属性:java.ext.dirs目录加载类库,或者从JDK安装目录:jre/lib/ext目录加载类库。我们可以把自己的包放在上面的目录下,它会自动加载。第三种:应用类加载器:ApplicationClassloader是Java语言编写的,由sun.misc.Launcher$AppClassLoader实现。派生自java.lang.ClassLoader,父类加载器为启动类加载器,负责加载环境变量classpath或系统属性java.class.path指定路径下的类库。它是程序中默认的类加载器。我们的Java程序中的类都是由它加载的。我们可以通过ClassLoader#getSystemClassLoader()来获取并操作这个加载器第四种:自定义加载器一般情况下,以上三种加载器可以满足我们日常的开发工作,如果不能,我们也可以自定义加载器比如通过加载一个Java类网络,为了保证传输的安全性,采用了加密操作,那么上面的三个loader是无法加载这个类的,这时候就需要自定义loader了。自定义加载器的实现步骤继承java.lang中的.ClassLoader类,重写findClass()方法。如果没有复杂的需求,可以直接继承URLClassLoader类,重写loadClass方法。具体请参考AppClassLoader和ExtClassLoader。几种获取ClassLoader的方法是一个抽象类,后续所有的类加载器都继承自ClassLoader(启动类加载器除外)//方法一:获取当前类的ClassLoaderclazz.getClassLoader()//方法二:获取当前类classClassLoaderThread.currentThread().getContextClassLoader()ofthethreadcontext//方法三:获取系统的ClassLoader采用按需加载方式。当类需要被使用时,jvm会将其类文件加载到内存中,生成一个类对象。在加载类时,采用双亲委托机制,这是一种将请求交给父类处理的任务委托模式。工作原理(1)如果一个类加载器收到一个类加载请求,它不会首先加载它,而是将请求委托给父类加载器执行。(2)如果父类还有父类加载器,则继续向上委托,直到到达引导类加载器:BootstrapClassLoader(3)如果父类加载器能够完成加载任务,则返回成功结果.如果父类加载失败,子类会尝试自己加载。如果子类加载失败,将抛出ClassNotFoundException。这是双亲委托模式的第三方包加载方式:反向委托机制在Java应用中有很多服务提供者接口(ServiceProviderInterface,SPI),这些接口允许第三方为其提供实现,比如常见的SPI包括JDBC、JNDI等,这些SPI接口属于Java核心库,一般存在于rt.jar包中,由Bootstrap类加载器加载。但是Bootstrap类加载器不能直接加载SPI实现类。同时,由于双亲委托模型的存在,Bootstrap类加载器无法对AppClassLoader加载器的SPI实现类进行反向委托。在这种情况下,我们就需要一个专门的类加载器来加载第三方类库,而线程上下文类加载器(双亲委派模型的破坏者)就是一个不错的选择。从图中可以看出,rt.jar核心包是由Bootstrap类加载器加载的,其中包含了SPI核心接口类。由于SPI中的类经常需要调用外部实现类的方法,jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径下)无法通过Bootstrap类加载器加载,所以线程上下文类loader只能委托将jdbc.jar中的实现类加载到内存中,供SPI相关类使用。显然,这种线程上下文类加载器的加载方式破坏了“双亲委派模型”。它在执行过程中摒弃了双亲委托加载链模式,使程序可以反向使用类加载器。当然,这也使得Java类加载器变得更加灵活。沙箱安全机制自定义了String类,但是在加载自定义String类时,会先使用bootstrap类加载器加载,bootstrap类加载器在加载时会先加载JDK自带的文件(rt.jar包)加载过程。javalangString.classin),报错说没有main方法是因为加载的rt.jar包里有String类。这样就保证了Java核心源代码的保护,也就是沙盒安全机制。