类加载器
何为类加载器?类加载器是做什么用的?让我们一起带着这些疑问往下走。
我们平时写的代码都是可读的,但计算机直接读代码就显得比较麻烦了。因此,通过编译,计算机便得到了它可以识别的字节码文件.class。然而此时此刻,字节码文件还停留在磁盘里面,乡愁,是一湾浅浅的海峡,字节码文件在这头,JVM在那头,JVM正和它翘首以盼呢!类加载器就是一个摆渡人,不知疲惫的将字节码文件从硬盘摆渡到JVM里面,可以算得上是个默默无闻的劳动者了。然而,类加载器并不是只完成摆渡的任务,它还有其他任务要做。当然,此为后话,稍后再做分析。
那么JVM的类加载器有几个呢?
JVM的类加载器主要有三个:Bootstrap ClassLoader -> Ext ClassLoader -> App ClassLoader
前面的类加载器是后面类加载器的父类。需要注意的是第一个类加载器(Bootstrap ClassLoader)是用C++写的,所以如果你比较好奇,想通过xxx.class.getClassLoader().getParent().getParent()方法获取类加载器,你会无奈的发现,这个值时null。why?不为什么,JVM不想让你知道它是个什么东东,因为它压根不是java语言编写的。
类加载步骤
继续上面的话题,类加载器除了加载字节码文件到内存的职责,其还有什么功能呢?如果说要回答这个问题,那就不得不说一下JVM类加载的步骤。
JVM类加载的过程主要分为5个步骤:加载 -> 连接 -> 初始化 -> 使用 -> 卸载,其中连接又分为3个阶段,分别为:验证 -> 准备 -> 解析。加载就不多说了,就是将二进制class文件加载到内存中;验证是验证一下类的结构是否符合JVM加载的规范,防止恶意代码损伤JVM;准备是对于类中的静态变量,直接在方法去分配内存,并为其赋初值,对于类中的静态常量,直接为其分配内存,并赋原值。比如对于public static Integer value = 3,为value在方法去分配内存,并赋初值0。但是如果value的类型是final的,则为其赋初值3;解析是将类中的符号引用转变为直接引用。But,符号引用是什么东西呢?在很长一段时间内,自己都搞不懂这个名词是什么玩意,后来随着时间的推移,才慢慢搞懂一点。比如 Machine machine = new Machine()。在编译阶段,JVM的代码区内有一堆数据,标识为xxx,在装载的过程中,碰到一些已经分配过内存的静态实例,则将其符号引用改为直接引用;初始化比较容易理解,对于准备过程中赋为0的静态变量,在此阶段将其原值赋予改变量。
类加载方式
那么,类加载方式有哪些呢?
在JVM里面,类加载方式主要有两种:隐式加载和显式加载。隐式加载就是new XXXClass()这种方式,而显式加载就是Class.forName("")这种方式。
要说到类的加载方式,不得不提下JVM的延时加载,也就是说,JVM不会一次将所有的类都加载到内存中,如果这样的话,JVM还不被挤爆了?所以,JVM所采取的策略就是按需延时加载,也就是说,真正在类实例被用到的时候才加载进来。
类的双亲委托模型
类的双亲委托模型是指如果一个类加载器要加载一个类,首先需要让其父类也就是Ext ClassLoader在其空间范围内查找,如果找不到才有本类加载器在自身空间下加载。而Ext ClassLoader在加载的过程中也会优先其父类 BootStrap ClassLoader在其空间下查找。因为有两个父类,所以称为双亲委托模型。