jvm系列之类加载 骑猪看日落 2022-05-28 07:00 169阅读 0赞 # 1、前言 # ava语言的类型可以分为两大类:基本类型和引用类型。 基本类型 包括byte,short,int,long,float,double,boolean,char。 引用类型 包括类,接口,数组类和泛型参数。由于泛型参数会在编译过程中被擦除,因此Java虚拟机实际上只有前三种。在类,接口和数组类中,数组类是由Java虚拟机直接生成的,其它两种则有对应的字节流 # 2、ClassLoader类 # java.lang.ClassLoader类就是类加载器,是一个抽象类。如果开发人员要编写自己的类加载器,就必须直接或间接地继承这个类,java虚拟机在类加载时机会调用defineClass方法加载字节码文件进入jvm内存中,并创建对应的Class对象。 # 3、类加载过程 # ![20180526194147827][] ![format_png][] ## 3.1、加载 ## ![20180526195451884][] 1.加载,就是查找字节流,并且据此生成一个代表这个类的java.lang.Class对象的过程。注意这里字节流不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。加载的信息存储在JVM的方法区。 2.对于数组类来说,它并没有对应的字节流,而是由Java虚拟机直接生成的。对于其它的类来说,Java虚拟机则需要借助类加载器来完成查找字节流的过程。 3.类加载器有两种,一种是启动类加载器,其它的类加载器都是java.lang.ClassLoader的子类。启动类加载器是由C++实现的,没有对应的Java对象,因此在Java中只能用null代替。除了启动类加载器之外,另外两个重要的类加载器是扩展类加载器和应用类加载器,均由Java核心类库提供。 启动类加载器加载最为基础,最为重要的类,如JRE的lib目录下jar包中的类;扩展类加载器的父类是启动类加载器,它负责加载相对次要,但又通用的类,如JRE的lib/ext目录下jar包中的类;应用类加载器的父类加载器则是扩展类加载器,它负责加载应用程序路径下的类。 4.JVM通过双亲委派模型进行类的加载,当然我们也可以通过继承java.lang.ClassLoader实现自定义的类加载器。当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。 采用双亲委派的好处: a)是比如加载位于rt.jar包中的类java.lang.Object,不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象 b)可以避免重复加载,父类已经加载了,子类就不需要再次加载 c)更加安全,很好的解决了各个类加载器的基础类的统一问题,如果不使用该种方式,那么用户可以随意定义类加载器来加载核心api,会带来相关隐患 ### 3.1.1、验证双亲委派 ### ClassLoader cla = Thread.currentThread().getContextClassLoader(); ClassLoader clb = cla.getParent(); ClassLoader clc = cla.getParent().getParent(); System.out.println(cla); System.out.println(clb); System.out.println(clc); 以上程序输出如下: sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@1fb3ebeb null ### 3.1.2、自定义类加载器 ### 两种方式: a)遵守双亲委派模型:继承ClassLoader,重写findClass()方法。 b)破坏双亲委派模型:继承ClassLoader,重写loadClass()方法 ## 3.2、连接 ## ### 3.2.1、验证 ### 目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。 ### 3.2.2、准备 ### 为静态域分配空间。如static int i=5,只将i初始化为0,而5在初始化阶段才赋值。这里不包括用final修饰的static,如 final static int A=5,编译期常量不用初始化就可以被读取,而形如final static int A=new Random().nextInt()不是编译期常量,使用时会强制发生初始化。 ### 3.2.3、解析 ### 主要将常量池中的符号引用替换为直接引用。符号引用就是用一组符号来描述目标,而直接引用就是指向目标的指针、相对偏移量或y一个间接定位到目标的句柄。 ## 3.3、初始化 ## 初始化静态域。注意,类加载不会为实例变量分配空间和初始化。 # 4、class文件的显式加载与隐式加载 # ## 4.1、显式加载 ## 如Class.forName()、this.getClass().getClassLoader().loadClass()这种显式地去加载某个类,即为显式加载。 ## 4.2、隐式加载 ## 如在加载某个类的class文件时,该类的class文件中引用了另外一个类的对象,此时这个类就会被隐式地加载到jvm内存当中。 [20180526194147827]: /images/20220528/07b6e8fa310743ca94bd4c1096f36a45.png [format_png]: /images/20220528/f8bcd97de7ee4be7b6d85b3979d5001e.png [20180526195451884]: /images/20220528/1f05d87ef6ed4b2ba6873137125def07.png
还没有评论,来说两句吧...