Java虚拟机类加载机制——案例分析

  在《Java虚拟机类加载机制》一文中详细阐述了类加载的过程,并举了几个例子进行了简要分析,在文章的最后留了一个悬念给各位,这里来揭开这个悬念。建议先看完《Java虚拟机类加载机制》这篇再来看这个,印象会比较深刻,如若不然,也没什么关系~~
下面是程序代码:

package jvm.classload;

public class StaticTest
{
    public static void main(String[] args)
    {
        staticFunction();
    }

    static StaticTest st = new StaticTest();

    static
    {
        System.out.println("1");
    }

    {
        System.out.println("2");
    }

    StaticTest()
    {
        System.out.println("3");
        System.out.println("a="+a+",b="+b);
    }

    public static void staticFunction(){
        System.out.println("4");
    }

    int a=110;
    static int b =112;
}

  问题是:请问这段程序的输出是什么?
  这个是我在论坛上看到的一个问题,我觉得比较经典。
  一般对于这类问题,小伙伴们脑海中肯定浮现出这样的knowledge:

Java中赋值顺序:
1. 父类的静态变量赋值
2. 自身的静态变量赋值
3. 父类成员变量赋值和父类块赋值
4. 父类构造函数赋值
5. 自身成员变量赋值和自身块赋值
6. 自身构造函数赋值

  ok,按照这个理论输出是什么呢?答案输出:1 4,这样正确嚒?肯定不正确啦,这里不是说上面的规则不正确,而是说不能简单的套用这个规则。
  正确的答案是:

2
3
a=110,b=0
1
4

  是不是有点不可思议?且听我一一道来,这里主要的点之一:实例初始化不一定要在类初始化结束之后才开始初始化。
  类的生命周期是:加载->验证->准备->解析->初始化->使用->卸载,只有在准备阶段和初始化阶段才会涉及类变量的初始化和赋值,因此只针对这两个阶段进行分析;
  类的准备阶段需要做是为类变量分配内存并设置默认值,因此类变量st为null、b为0;(需要注意的是如果类变量是final,编译时javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将变量设置为指定的值,如果这里这么定义:static final int b=112,那么在准备阶段b的值就是112,而不再是0了。)
  类的初始化阶段需要做是执行类构造器(类构造器是编译器收集所有静态语句块和类变量的赋值语句按语句在源码中的顺序合并生成类构造器,对象的构造方法是<init>(),类的构造方法是<clinit>(),可以在堆栈信息中看到),因此先执行第一条静态变量的赋值语句即st = new StaticTest (),此时会进行对象的初始化,对象的初始化是先初始化成员变量再执行构造方法,因此设置a为110->打印2->执行构造方法(打印3,此时a已经赋值为110,但是b只是设置了默认值0,并未完成赋值动作),等对象的初始化完成后继续执行之前的类构造器的语句,接下来就不详细说了,按照语句在源码中的顺序执行即可。
  这里面还牵涉到一个冷知识,就是在嵌套初始化时有一个特别的逻辑。特别是内嵌的这个变量恰好是个静态成员,而且是本类的实例。
  这会导致一个有趣的现象:“实例初始化竟然出现在静态初始化之前”。
  其实并没有提前,你要知道java记录初始化与否的时机。
  看一个简化的代码,把关键问题解释清楚:

public class Test {
    public static void main(String[] args) {
        func();
    }
    static Test st = new Test();
    static void func(){}
}

  根据上面的代码,有以下步骤:

  1. 首先在执行此段代码时,首先由main方法的调用触发静态初始化。
  2. 在初始化Test 类的静态部分时,遇到st这个成员。
  3. 但凑巧这个变量引用的是本类的实例。
  4. 那么问题来了,此时静态初始化过程还没完成就要初始化实例部分了。是这样么?
  5. 从人的角度是的。但从java的角度,一旦开始初始化静态部分,无论是否完成,后续都不会再重新触发静态初始化流程了。
  6. 因此在实例化st变量时,实际上是把实例初始化嵌入到了静态初始化流程中,并且在楼主的问题中,嵌入到了静态初始化的起始位置。这就导致了实例初始化完全至于静态初始化之前。这也是导致a有值b没值的原因。
  7. 最后再考虑到文本顺序,结果就显而易见了。

  详细看到这里,心中大概有个结论了吧,如果对于类的加载机制比较模糊的话,可以参考开篇推荐的博文~ 有问题欢迎留言。

时间: 2024-10-25 17:23:44

Java虚拟机类加载机制——案例分析的相关文章

Java虚拟机类加载机制

看到这个题目,很多人会觉得我写我的java代码,至于类,JVM爱怎么加载就怎么加载,博主有很长一段时间也是这么认为的.随着编程经验的日积月累,越来越感觉到了解虚拟机相关要领的重要性.闲话不多说,老规矩,先来一段代码吊吊胃口. public class SSClass{ 运行结果: SSClassSuperClass init!123 答案答对了嚒? 也许有人会疑问:为什么没有输出SubClass init.ok~解释一下:对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父

深入理解Java:类加载机制及反射

一.Java类加载机制 1.概述 Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能. 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 2.工作机制 类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示

JVM学习笔记(三)——虚拟机类加载机制

在介绍完class文件格式后,我们来看下虚拟机是如何把一个由class文件描述的类加载到内存中的.具体来说java中类的加载涉及7个阶段:加载.校验.准备.解析.初始化.使用.卸载. 1.加载时机 并不是所有的类在程序启动时即被加载,为提升效率,虚拟机通常秉承的是按需加载的原则,即需要使用到相应的类时才加载对应的类.具体包括如下几个加载时机: 遇到new.getstatic.putstatic.invokestatic这4条指令时,如果对应的类没有被加载,虚拟机会首先加载对应的类.这4条指令对应

Java虚拟机结构分析

本博文主要介绍了JVM(Java Virtual Machine)的组成部分以及它们内部的工作机制和原理.需要注意的是,虽然平时我们用的大多是Sun(现已被Oracle收购)JDK提供的JVM,但是JVM本身是一个规范,所以可以有多种实现,除了Hotspot外,还有诸如Oracle的JRockit.IBM的J9也都是非常有名的JVM. 1. 结构 下图展示了JVM的主要结构: 可以看出,JVM主要由类加载器子系统.运行时数据区(内存空间).执行引擎以及与本地方法接口等组成.其中运行时数据区又由方

《深入理解Java虚拟机》学习笔记

自动内存管理机制 第2章 垃圾收集器与内存分配策略 1.Java虚拟机在执行java程序时会把它所管理的内存会分为若干个不同的数据区域, 这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在, 有些区域则是在以来用户线程的启动和结束而建立和销毁. 根据<Java虚拟机规范>,包括以下几个运行时数据区域: //此处应有类图,但是画起来太麻烦! 程序计数器(Program Counter Register) 方法区(Method Area) 虚拟机栈(VM Stack)

深入理解Java虚拟机:JVM高级特性与最佳实践

目 录 [ - ] <深入理解Java虚拟机:JVM高级特性与最佳实践>前言 <深入理解Java虚拟机:JVM高级特性与最佳实践>内容特色 <深入理解Java虚拟机:JVM高级特性与最佳实践>目录 第1章 走近Java 1.1 概述 1.2 Java技术体系 1.3 Java发展史 1.4 展望Java技术的未来 1.4.1 模块化 1.4.2 混合语言 1.4.3 多核并行 1.4.4 进一步丰富语法 1.4.5 64位虚拟机 1.5 实战:自己编译JDK 1.5.1

【转载】JVM类加载机制小结

本文转载自http://shift-alt-ctrl.iteye.com/blog/1845137   一.类加载        虚拟机把class文件加载至内存之后,对字节码数据进行校验/解析/初始化等操作,最终形成可被VM直接使用的java类型,这就是虚拟机类加载机制.类的加载完全可以在运行时进行,这给VM提供了动态加载类提供了可行性.     类生命周期过程大概分为:加载-->校验-->解析-->初始化-->使用-->卸载;类在被"使用"之前必须进行

JVM类加载机制小结

一.类加载        虚拟机把class文件加载至内存之后,对字节码数据进行校验/解析/初始化等操作,最终形成可被VM直接使用的java类型,这就是虚拟机类加载机制.类的加载完全可以在运行时进行,这给VM提供了动态加载类提供了可行性.     类生命周期过程大概分为:加载-->校验-->解析-->初始化-->使用-->卸载;类在被"使用"之前必须进行前4个阶段,其中初始化工作可以被延迟进行,直到类需要创建实例/static方法或属性被调用(非编译常量)

深入研究Java虚拟机的类加载机制

说到Java虚拟机的类加载机制,很多朋友第一反应想到的应该就是ClassLoader,我也如此,不过ClassLoader其实只是Java虚拟机加载机制中的一部分,最近在看<深入理解Java虚拟机>,对Java虚拟机的类加载机制有了更深入的了解,不吐不快. JVM中类的整个生命周期如下: 加载=>验证=>准备=>解析=>初始化=>使用=>卸载 使用和卸载这两个步骤不在今天的讨论范围之内,今天我们将着重讨论一下前5个步骤,也就是JVM中类的整个加载机制. 1.