在上一篇中,通过下面这幅图大致了JVM整体的内部运行结构图,在JVM的结构中,类加载子系统作为连接外部class文件与真正将class文件加载到运行时数据区,承担着重要的作用
类加载器是什么?有什么作用?
通俗来讲就是,类加载器确保加载到JVM内存中的字节码信息符合JVM运行时的规范,以便正确的执行class中的信息
下面是一副类加载器加载字节码的简易流程图过程,下面将结合这张图,通俗的对类加载器的执行过程做简单的说明
1、加载阶段(loading)
以某个具体的Class文件加载为例,当程序需要XXX.Class文件时,类加载器将该类的字节码文件读取之后加载到运行时数据区,具体加载到哪里去呢?很明显,首先会去堆中查找一下当前class的实例是否已存在?如果存在了,就直接从堆区取出来加载到方法区即可,否则执行上一个图的加载过程,在反射的使用中,经常看到 XXX.getClass()获取Class的信息就是这个道理
说到这,顺便总结下,Class的实例什么时候创建呢?主要包括下面几种:
2、链接(linking)
该阶段主要做的事情包括:
为类中相关的字段赋予初始值,比如像下面这样的,为类的静态变量赋予初始值
public class A {
private static int XXX =10;
}
解析:
将字节码中的静态字面关联转换为JVM内存中动态指针关联,举例来说,当A类extendsB类的时候,从语义上分析来说,表现出下面这样
即类加载器加载class时候读取到的A类和B类之间的关系可以理解为使用字面量的表达,但是JVM并不认识啊,在JVM中,需要使用一种新的形式去描述它们之间的关系,即动态指针的关联
而具体解析字节码中的信息包括下面几种:
3、初始化Initialization
初始化阶段要做的事情主要是:执行类的构造方法()的过程
clint完成类的初始化工作,该方法不需要显式声明,由Java编译器自动生成,这里和上面的,加载/验证/准备/解析有所不同的是,上面几个阶段是由虚拟机主导的,与代码无关,
而初始化则是通过代码生成clint来完成类的初始化过程
初始化过程总结:
上面简单聊了一下JVM类加载器加载时的几个重要的执行步骤,既然说到类加载器,不得不谈谈在JVM中,类加载具体有哪些呢?
按照层次划分,类加载器大概可以分为下面几种:
其中,启动类加载器,扩展类加载器,应用程序类加载器属于JVM自身的加载器,而自定义加载器在使用中更加灵活,不仅可以加载自定义目录中的class,还能加载来自网络输入的二进制流,需要说明的是,它们之间并非是自上而下的继承关系,仅为上下级
启动类加载器:
扩展类加载器:
应用程序类加载器 (AppClassLoader )
下面来简单看一段具体的实例代码
public class TestClassLoader {
public static void main(String[] args) {
//自定义的类,默认使用应用类加载器
ClassLoader classLoader = TestClassLoader.class.getClassLoader();
System.out.println(classLoader);
//扩展类加载器
ClassLoader extClassLoader = SunEC.class.getClassLoader();
System.out.println(extClassLoader);
//启动类加载器,由于启动类加载器是由C语言编写的,不能被JVM管理,所以返回null
ClassLoader bootstrapClassLoader = Object.class.getClassLoader();
System.out.println(bootstrapClassLoader);
}
}
上面我们了解了JVM中常用的几种类加载器,细心的小伙伴应该留意到,3者之间存在着一种上下级关系,这种关系的结构通俗解释就是“双亲委派”
何为“双亲委派”?
比如说,当JVM需要运行一个自定义的类时,自定义加载器由于存在上级扩展类加载器和引导类加载器,因此会逐级向上加载,但是上面的2级加载器真正开始加载时,发现自定义的类中的相关信息不在自己这一层加载器所能服务的范围,因此又自顶向下逐级加载,最终来到能够处理的加载器中进行处理
从下面这个简单的案例,可以发现,当我们自定义的类和系统的加载器中的核心包里面的类发生冲突时就报出下面的错误了,这也是双亲委派的沙箱机制的好处
本篇主要讲述了类加载器相关的内容,希望对看到的同学有用,本篇到此结束最后感谢观看!