Java面试通关手册(Java学习指南),会一直完善下去,欢迎你们star以及和我一块儿完善。 github.com/Snailclimb/…java
《深刻理解Java虚拟机:JVM高级特性与最佳实践(第二版》读书笔记与常见相关面试题总结git
本节常见面试题(推荐带着问题阅读,问题答案在文中都有提到):github
简单说说类加载过程,里面执行了哪些操做?面试
对类加载器有了解吗?编程
什么是双亲委派模型?数组
双亲委派模型的工做过程以及使用它的好处。缓存
代码编译的结果从本地转换为字节码,是存储格式发展的一小步,倒是编程语言发展的一大步。安全
上一节咱们已经知道了类文件结构,在class文件中描述的各类信息最终都须要加载到虚拟机中以后才能运行和使用。微信
那么虚拟机是如加载这些class文件呢?class文件中的信息进入到虚拟机后会发生什么变化呢?网络
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析和初始化。最终造成能够被虚拟机最直接使用的java类型的过程就是虚拟机的类加载机制。
另外须要注意的很重要的一点是:java语言中类型的加载链接以及初始化过程都是在程序运行期间完成的,这种策略虽然会使类加载时稍微增长一些性能开销,可是会为java应用程序提供高度的灵活性。java里天生就能够动态扩展语言特性就是依赖运行期间动态加载和动态链接这个特色实现的。好比,若是编写一个面向接口的程序,能够等到运行时再指定其具体实现类。
类从被加载到虚拟机内存到卸出内存为止,它的整个生命周期包括:
虚拟机规范严格规定了有且只有五种状况必须当即对类进行“初始化”:
而对于接口,当一个接口在初始化时,并不要求其父接口所有都完成了初始化,只有在真正使用到父接口时(如引用父接口中定义的常量)才会初始化。
全部引用类的方式都不会触发初始化称为被动引用,下面是3个被动引用例子:
①经过子类引用父类静态字段,不会致使子类初始化;②经过数组定义引用类,不会触发此类的初始化
public class SuperClass {
static {
System.out.println("SuperClass(父类)被初始化了。。。");
}
public static int value = 66;
}
复制代码
public class Subclass extends SuperClass {
static {
System.out.println("Subclass(子类)被初始化了。。。");
}
}
复制代码
public class Test1 {
public static void main(String[] args) {
// 1:经过子类调用父类的静态字段不会致使子类初始化
// System.out.println(Subclass.value);//SuperClass(父类)被初始化了。。。66
// 2:经过数组定义引用类,不会触发此类的初始化
SuperClass[] superClasses = new SuperClass[3];
// 3:经过new 建立对象,能够实现类初始化,必须把1下面的代码注释掉才有效果否则通过1的时候类已经初始化了,下面这条语句也就没用了。
//SuperClass superClass = new SuperClass();
}
}
复制代码
③常量在编译阶段会存入调用类的常量池中,本质上并无直接引用定义常量的类,所以不会触发定义常量的类的初始化
public class ConstClass {
static {
System.out.println("ConstClass被初始化了。。。");
}
public static final String HELLO = "hello world";
}
复制代码
public class Test2 {
public static void main(String[] args) {
System.out.println(ConstClass.HELLO);//输出结果:hello world
}
}
复制代码
下面咱们详细的说一下java虚拟机中类加载的全过程:加载、验证、准备、解析和初始化这5个阶段锁执行的具体工做。
“加载” 是 “类加载” 过程的一个阶段,切不可将两者混淆。
加载阶段由三个基本动做组成:
经过类型的彻底限定名,产生一个表明该类型的二进制数据流(根本没有指明从哪里获取、怎样获取,能够说一个很是开放的平台了)
解析这个二进制数据流为方法区内的运行时数据结构
建立一个表示该类型的java.lang.Class类的实例,做为方法区这个类的各类数据的访问入口。
经过类型的彻底限定名,产生一个表明该类型的二进制数据流的几种常见形式:
注意: 非数组类加载阶段既可使用系统提供的类加载器来完成,也能够由用户自定义的类加载器去完成。(即重写一个类加载器的loadClass()方法)
验证是链接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。
虚拟机若是不检查输入的字节流,并对其彻底信任的话,极可能会由于载入了有害的字节流而致使系统崩溃,因此验证是虚拟机对自身保护的一项重要工做。这个阶段是否严谨,直接决定了java虚拟机是否能承受恶意代码的攻击。
从总体上看,验证阶段大体上会完成4个阶段的校验工做:文件格式、元数据、字节码、符号引用。
验证字节流是否符合Class文件格式的规范,而且能被当前版本的虚拟机处理。该验证阶段的主要目的是保证输入的字节流能正确地解析并存储于方法区以内。这个阶段验证是基于二进制字节流进行的,只有经过这个阶段的验证后,字节流才会进入内存的方法区进行存储,因此后面的3个阶段的所有是基于方法区的存储结构进行的,不会再直接操做字节流。
该阶段对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求,目的是保证不存在不符合Java语言规范的元数据信息。
该阶段主要工做时进行数据流和控制流分析,保证被校验类的方法在运行时不会作出危害虚拟机安全的行为。 例如,保证跳转指令不会跳转到方法体之外的字节码指令上、保证方法体中的类型转换是有效的等等。
因为数据流校验的高复杂性,耗时较大,因此JDK1.6以后,在Javac中引入一项优化方法(能够经过参数关闭):在方法体的Code属性的属性表中增长一项“StackMapTable”属性,该属性描述了方法体中全部基本块开始时本地变量表和操做栈应有的状态,从而将字节码验证的类型推导转变为类型检查从而节省一些时间。
注意: 若是一个方法体经过了字节码验证,也不能说明其必定是安全的,由于校验程序逻辑没法作到绝对精确。
最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动做将在链接的第三个阶段——解析阶段中发生。符号引用验证的目的是确保解析动做能正常执行。
验证的内容主要有:
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。(备注:这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一块儿分配在Java堆中)。
初始值一般是数据类型的零值:
对于:public static int value = 123;,那么变量value在准备阶段事后的初始值为0而不是123,这时候还没有开始执行任何java方法,把value赋值为123的动做将在初始化阶段才会被执行。
一些特殊状况:
对于:public static final int value = 123;编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123。
基本数据类型的零值:
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
那么符号引用与直接引用有什么关联呢?
符号引用(Symbolic References): 符号引用以一组符号来描述所引用的目标,符号能够是符合约定的任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不必定已经加载到内存中。
直接引用(Direct References): 直接引用能够是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用与虚拟机实现的内存布局相关,引用的目标一定已经在内存中存在。
虚拟机规范没有规定解析阶段发生的具体时间,虚拟机实现能够根据须要来判断究竟是在类被加载时解析仍是等到一个符号引用将要被使用前才去解析。
同一符号引用进行屡次解析请求是很常见的,除invokedynamic指令之外,虚拟机实现能够对第一次解析结果进行缓存,来避免解析动做重复进行。不管是否真正执行了屡次解析动做,虚拟机须要保证的是在同一个实体中,若是一个引用符号以前已经被成功解析过,那么后续的引用解析请求就应当一直成功;一样的,若是 第一次解析失败,那么其余指令对这个符号的解析请求也应该收到相同的异常。
解析动做主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。前面四种引用的解析过程,对于后面三种,与JDK1.7新增的动态语言支持息息相关,因为java语言是一门静态类型语言,所以没有介绍invokedynamic指令的语义以前,没有办法将他们和如今的java语言对应上。
类初始化阶段是类加载的最后一步,前面的类加载过程当中,除了在加载阶段用户应用程序能够经过自定义类加载器参与以外,其他动做彻底由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的java程序代码(或者说是字节码)。
对于任意一个类,都须要由加载它的类加载器和这个类自己一同确立其在Java虚拟机中的惟一性。若是两个类来源于同一个Class文件,只要加载它们的类加载器不一样,那么这两个类就一定不相等。
从Java虚拟机的角度分为两种不一样的类加载器:启动类加载器(Bootstrap ClassLoader) 和其余类加载器。其中启动类加载器,使用C++语言实现,是虚拟机自身的一部分;其他的类加载器都由Java语言实现,独立于虚拟机以外,而且全都继承自java.lang.ClassLoader类。(这里只限于HotSpot虚拟机)。
从Java开发人员的角度来看,绝大部分Java程序都会使用到如下3种系统提供的类加载器。
启动类加载器(Bootstrap ClassLoader):
这个类加载器负责将存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,而且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即便放在lib目录中也不会被加载)类库加载到虚拟机内存中。
扩展类加载器(Extension ClassLoader):
这个加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的全部类库,开发者能够直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader):
这个类加载器由sun.misc.Launcher$AppClassLoader实现。因为这个类加载器是ClassLoader中的getSystemClassLoader()方法的返回值,因此通常也称它为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者能够直接使用这个类加载器,若是应用程序中没有自定义过本身的类加载器,通常状况下这个就是程序中默认的类加载器。
咱们的应用程序都是由这3种类加载器互相配合进行加载的,若是有必要,还能够加入本身定义的类加载器。
双亲委派模型(Pattern Delegation Model),要求除了顶层的启动类加载器外,其他的类加载器都应该有本身的父类加载器。这里父子关系一般是子类经过组合关系而不是继承关系来复用父加载器的代码。
使用双亲委派模型来组织类加载器之间的关系,有一个显而易见的好处就是java类随着它的类加载器一块儿具有了一种带有优先级的层次关系。
注意:双亲委派模型是Java设计者们推荐给开发者们的一种类加载器实现方式,并非一个强制性 的约束模型。在java的世界中大部分的类加载器都遵循这个模型,但也有例外。
双亲委派模型主要出现过3次较大规模“被破坏”的状况。
第一次破坏是由于类加载器和抽象类java.lang.ClassLoader在JDK1.0就存在的,而双亲委派模型在JDK1.2以后才被引入,为了兼容已经存在的用户自定义类加载器,引入双亲委派模型时作了必定的妥协:在java.lang.ClassLoader中引入了一个findClass()方法,在此以前,用户去继承java.lang.Classloader的惟一目的就是重写loadClass()方法。JDK1.2以后不提倡用户去覆盖loadClass()方法,而是把本身的类加载逻辑写到findClass()方法中,若是loadClass()方法中若是父类加载失败,则会调用本身的findClass()方法来完成加载,这样就能够保证新写出来的类加载器是符合双亲委派模型规则的。
第二次破坏是由于模型自身的缺陷,现实中存在这样的场景:基础的类加载器须要求调用用户的代码,而基础的类加载器可能不认识用户的代码。为此,Java设计团队引入的设计时“线程上下文类加载器(Thread Context ClassLoader)”。这样能够经过父类加载器请求子类加载器去完成类加载动做。已经违背了双亲委派模型的通常性原则。
第三次破坏 是因为用户对程序动态性的追求致使的。这里所说的动态性是指:“代码热替换”、“模块热部署”等等比较热门的词。说白了就是但愿应用程序可以像咱们的计算机外设同样,接上鼠标、U盘不用重启机器就能当即使用。OSGi是当前业界“事实上”的Java模块化标准,OSGi实现模块化热部署的关键是它自定义的类加载器机制的实现。每个程序模块(OSGi中称为Bundle)都有一个本身的类加载器,当须要更换一个Bundle时,就把Bundle连同类加载器一块儿换掉以实现代码的热替换。在OSGi环境下,类加载器再也不是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构。
本节主要介绍了类加载过程当中:“加载”、“验证”、“准备”、“解析”、“初始化”这5个阶段中虚拟机进行了了那些动做,还介绍了类加载器的工做原理及对虚拟机的意义。
欢迎关注个人微信公众号:"Java面试通关手册"(一个有温度的微信公众号,期待与你共同进步~~~坚持原创,分享美文,分享各类Java学习资源):