类加载器子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识。
当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成3个步骤,所以有时也把这3步骤统称为类加载或类初始化。
类被加载到 JVM 开始,到卸载出内存,整个生命周期如图:
就是将已经读入内存的类的二进制数据合并到JVM运行时环境中去,包含以下步骤:
验证
检验被加载的类是否有正确的内部结构,并和其他类协调一致。
准备
准备阶段则负责为类的静态属性分配内存,并设置默认初始值;不包含final修饰的static实例变量,在编译时进行初始化。不会为实例变量初始化。
解析
将类的二进制数据中的符号引用替换成直接引用。
类什么时候初始化?
注意:对于一个final类型的静态变量,如果该变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。反之,如果final类型的静态Field的值不能在编译时确定下来,则必须等到运行时才可以确定该变量的值,如果通过该类来访问它的静态变量,则会导致该类被初始化。
类的初始化顺序
对static修饰的变量或语句块进行赋值。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
顺序是:父类static -> 子类static -> 父类构造方法 -> 子类构造方法
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)
无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个:
自定义类加载器(User-Defined ClassLoader)
从概念上来讲,自定义类加载器一般指的是程序汇总有开发人员自定义的一类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。
扩展类加载器(Extension ClassLoader)
Java语言编写的,由sun.misc.Launcher$ExtClassLoader实现,父类加载器为null。
派生于ClassLoader类。
上层类加载器为引导类加载器。
它负责加载JRE的扩展目录。
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK系统安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的jar放在此目录下,也会自动由扩展类加载器加载。
应用程序类加载器(系统类加载器 Application ClassLoader)
Java语言编写的,由sun.misc.Launcher$AppClassLoader实现,父类加载器为ExtClassLoader。
派生于ClassLoader类。
上层类加载器为扩展类加载器。
加载我们自己定义的类。
该类加载器是程序中默认的类加载器。
通过类名.class.getClassLoader(),ClassLoader.getSystemClassLoader()来获得。
引导类加载器(启动类加载器/根类加载器)(Bootstrap ClassLoader)
这个类加载器使用C/C++语言实现,嵌套在JVM内部。用来加载Java核心类库。
并不继承于java.lang.ClassLoader没有父加载器。
负责加载扩展类加载器和应用类加载器,并为它们指定父类加载器。
出于安全考虑,引用类加载器只加载器包名为java,javax,sun等开头的类。
注意:ClassLoader类,它是一个抽象类,其后所有类加载器都继承自ClassLoader(不包括启动类加载器)
类加载器加载Class大致要经过如下8个步骤:
JVM的类加载机制主要有3种
细讲一下双亲委派机制(面试):
工作原理:
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器区执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父加载器无法完成此加载任务,子加载器才会尝试自己去加载,如果均加载失败,就会抛出ClassNotFoundException异常,这就是双亲委派模式。即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了了时,儿子自己才想办法去完成。
双亲委派优点:
作用:防止恶意代码污染java源代码。
比如我们定义了一个类名为String所在包也命名为java.lang,因为这个类本来属于jdk的,如果没有沙箱安全机制,这个类将会污染到系统中的String,但是由于沙箱安全机制,所以就委托顶层的引导类加载器查找这个类,如果没有的话就委托给扩展类加载器,再没有就委托到系统类加载器。但是由于String就是jdk源代码,所以在引导类加载器那里就加载到了,先找到先使用,所以就使用引导类加载器里面的String,后面的一概不能使用,这就保证了不被恶意代码污染。
JVM规定,每个类或者接口被首次主动使用时才对其进行初始化,有主动使用,自然就有被动使用。
主动使用:
被动使用:
除了上面的几种主动使用其余就是被动使用了。
引用该类的静态常量,不会导致初始化,但是也有意外,这里的常量是指已经指定字面量的常量,对于那些需要一些计算才能得出结果的常量就会导致初始化。
public final static int NUMBER = 5 ; //不会导致类初始化,被动使用
public final static int RANDOM = new Random().nextInt() ;//会导致类的初 始化,主动使用
构造某个类的数组时不会导致该类的初始化。
Student[] students = new Student[10] ;
注意:主动使用和被动使用的区别在于类是否会被初始化。
面试题:
描述一下JVM加载Class文件的原理机制
java中的所有类,都需要由类加载器装载到JVM中才能运行。类加载器本身也是一个类,而它的工作就是把class文件从硬盘读取到内存中。在写程序的时候,我们几乎不需要关心类的加载,因为这些都是隐式装载的,除非我们有特殊的用法,像是反射,就需要显示的加载所需要的类。
类装载方式:
java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类完全加载到JVM中,至于其他类,则在需要的时候才加载。节省内存开销。
面试题:
在jvm中如何判断两个对象是属于同一个类?
1.类的全类名(地址)完全一致。
2.类的加载器必须相同。