Java JVM 3: ClassLoading&Initialization

来源:互联网 发布:淘宝旺旺引流神器 编辑:程序博客网 时间:2024/06/08 06:19

C/C++语言的编译时就要完成连接工作,而在Java里类的加载、连接、初始化都是在程序运行期间完成,在稍稍增加性能开销的同时极大的提高了Java的灵活性。我们知道,编写一个面向接口的程序,可以等到运行时指定其具体的实现类。这就依赖于Java运行时动态加载和动态链接的特性。

Java中类的生命周期如下图所示:


Java类生命周期
Java类生命周期

其中,加载、验证、准备、初始化和卸载的开始顺序(仅仅指开始顺序,并非指完成,实际上这些阶段都是交叉混合进行)如图所示是一定的,而解析阶段不一定。
每个阶段都做了什么?

  • 加载:查找并加载类的二进制数据
  • 链接
    • 验证:确保被加载类的正确性
    • 准备:为类的静态变量分配内存,并将其初始化为默认值
    • 解析: 把类的符号引用转换为直接引用
  • 初始化:为类的静态变量赋予正确的初始值

下面详细分析每个过程:

加载

  1. 通过一个类的全限定名来获取其定义的二进制字节流。(全限定名即报名+类名)。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在Java堆中生成一个代表这个类的 java.lang.Class对象,作为对方法区中这些数据的访问入口。

注意:获得类的二进制字节流并非一定是IDE编译后的class文件,还可以从zip/jar包或者网络中获取或者其他文件获取(JSP应用),正因为其来源多样,为保证class文件的正确性,才有了下面的验证阶段。
类加载由ClassLoader完成,java中有如下几种:

  • BootStrap Classloader
    负责加载存放在$JAVA_HOME/jre/lib下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如 rt.jar)。启动类加载器是C++编写的,所以无法再Java中直接引用。
  • ExtClassloader
    负责加载$JAVA_HOME/jre/lib/ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。
    在jdk1.9中类加载器有所变化!1.9中jdk.internal.loader.ClassLoaders$PlatformClassLoader,称为平台类加载器。
  • AppClassLoader
    负责加载用户类路径ClassPath所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。注意在jdk1.9中,应用程序加载器由jdk.internal.loader.ClassLoaders$AppClassLoader实现.
  • 自定义的Classloader

双亲委托模型
每一层上面的类加载器叫做当前层类加载器的父加载器,当然,它们之间的父子关系并不是通过继承关系来实现的,而是使用组合关系来复用父加载器中的代码。该模型在JDK1.2期间被引入并广泛应用于之后几乎所有的Java程序中,但它并不是一个强制性的约束模型,而是Java设计者们推荐给开发者的一种类的加载器实现方式。
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。

验证

前面提到过验证的必要性,验证阶段大致流程如下:


验证阶段

准备

准备阶段是正式为类变量分配内存并设置初始值。这些变量所使用的内存都将在方法区中进行分配。
这个阶段有两个容易混淆的概念:首先,进行内存分配的仅包括类变量(被static修饰的变量),不包括实例变量,实例变量是在对象实例化的时候分配在heap区;其次,所赋初值是根据数据类型不同的默认值(如下图所示),并非程序中所赋的值,赋值发生在初始化阶段。

Java初值
Java准备阶段初值

解析

初始化

JVMS严格规定了5种必须立即对类初始化的情形,这5种场景中的行为也称为对一个类的主动引用
主动引用的几种情形:

  • 创建类的实例时,比如使用new关键字, 或者反射、克隆、反序列化;
  • 调用类的静态方法时,也就是使用了字节码invokestatic指令;
  • 使用类或接口的静态字段(final常量除外)时,比如getstatic或者putstatic指令;
  • 当使用java.lang.reflect包中的方法反射类的方法时
  • 初始化子类时,需要先初始化父类
  • 启动虚拟机,含有main()方法那个类

既然有主动引用就有被动引用,被动引用有以下几种常见情形:

  • 引用一个字段时,只有直接定义该字段的类,才会被初始化
    子类引用父类的静态变量,只有父类被初始化,子类不必初始化
  • 引用final常量时并不会引起类的初始化
    因为class文件已经对常量进行了优化, 放进常量池中了,类自然不会被加载

参考:
《深入理解JVM》
《实战Java虚拟机》
https://blog.stormma.me/2017/11/14/%E4%B8%80%E7%82%B9%E4%B8%80%E6%BB%B4%E6%8E%A2%E7%A9%B6JVM%E4%B9%8B%E7%B1%BB%E5%8A%A0%E8%BD%BD%E6%9C%BA%E5%88%B6/

原创粉丝点击