代码之家  ›  专栏  ›  技术社区  ›  baruchiro

JVM加载中的静态字段

  •  0
  • baruchiro  · 技术社区  · 7 年前

    作为JVM加载、链接和初始化的一部分,当

    1. 静态字段
    2. 最终静态场

    课堂上

    1. 在内存中分配
    2. 初始化为默认值
    3. 初始化为实值

    在静态final变量的情况下,我认为一切都发生在加载步骤中,因为所有值都已经在常量池中。

    但我不知道静态场是怎么回事。原则上,书面 documantation 它们在准备步骤中使用默认值初始化。

    准备工作包括为类或接口创建静态字段,并将这些字段初始化为其默认值(第2.3节和第2.4节)。这不需要执行任何Java虚拟机代码;静态字段的显式初始值设定项作为初始化的一部分执行(§5.5),而不是准备。

    但是在这个 source (类变量段落)加载静态字段初始化已在加载步骤中发生。这很有意义,因为在加载步骤结束时,将创建类对象的实例,并且它必须包含静态字段的空间。

    在Java虚拟机使用类之前,它必须从方法区域为类中声明的每个非最终类变量分配内存。

    所以我想知道在这种情况下正确的事实是什么。

    3 回复  |  直到 7 年前
        1
  •  2
  •   Holger    7 年前

    通常,如果 the official specification 而在互联网上的一些文章中,你可以有把握地认为规范是最后一句话,而这篇文章是错误的。这将在99.99%的情况下为您服务。

    当谈到Java虚拟机时,尤其如此,在这里,文章将问题的步骤(加载、链接和初始化)混为一谈,并且经常将正式步骤和实现细节混为一谈。

    你链接的那篇文章在几个方面是否有错:

    • 并非所有 static final 字段是编译时常量。只有 静态最终版本 基元类型的字段或 String 是编译时常量,如果它们立即用编译时常量初始化。考虑

      static final String CONSTANT1 = ""; // compile-time constant
      static final String CONSTANT2 = CONSTANT1; // compile-time constant
      // but
      static final String NO_CONSTANT1 = CONSTANT1.toString(); // not a constant expression
      static final String NO_CONSTANT2; // no initializer
      static {
          NO_CONSTANT2 = ""; // assignment in class initializer, valid, but not constant
      }
      static final BigInteger NO_CONSTANT3 = BigInteger.ONE; // neither primitive nor String
      
    • 对于编译时常量,每个普通Java语言读取访问在编译时都会被常量值替换,但标识符仍然存在,可以通过反射进行检查,也可以通过非Java语言源代码生成的字节码进行访问。JVM在存储方面是否特别对待常量字段是一个实现细节,但通常情况下,实现者会尽量避免特殊对待,除非有真正的好处。

      形式规范描述的常量变量就像其他变量一样具有存储空间,但当然,如果实现仍然能够保留强制行为(例如,使值可供反射使用),则可能会忽略这一点。

      常量和非常量的初始化 static 变量明确指定为 Initialization (虽然不是同时):

      Â

      1. 否则,请记录以下事实: Class 的对象 C 当前线程正在进行,并发布 LC . 然后,初始化每个 final 静止的 字段 C 在其 ConstantValue 属性( §4.7.2 ),按字段在 ClassFile 结构

      …

       9. 接下来,执行的类或接口初始化方法 C .

      类或接口初始化方法是名为 <clinit> 在字节码级别,它包含所有非常量的初始值设定项 静止的 字段以及中的任何代码 static { … } 阻碍。

    • 类变量和常量池是不同的东西。常量池包含字段的符号名称以及编译时常量的值(顺便说一下,其中可能包括非- 静止的 字段)。

      常量池的值可用于构造实际运行时值,例如,描述字符串的字节序列必须转换为对实际 一串 对象和基元类型可以进行Endianess转换。如上文所述,当该处理作为JVMS§5.5中所述步骤6的一部分进行时,随后对该字段的访问将始终使用该处理的结果。

        2
  •  0
  •   Maurice Perry    7 年前

    我的理解是,内存分配是在准备过程中完成的,而初始化程序的执行是在初始化过程中完成的。这与任何一个来源都不矛盾。

        3
  •  0
  •   Jayanth    7 年前

    准确地说,

    • 实例变量将存储在堆上。
    • 堆栈上的局部变量(如果变量不是 基元[引用变量]引用变量位于堆栈上,并且 堆上的对象)。只有方法调用和部分结果 存储在堆栈中,而不是方法本身。
    • 静态变量 和方法(包括静态和非静态) 方法区域。静态方法(实际上是所有方法)以及静态变量都存储在PermGen部分,因为它们是反射数据的一部分(与类相关的数据,与实例无关)。

    对于静态变量,如果不初始化变量,则会在对象准备之前存储默认值(JVM负责此操作),但对于最终静态变量,则需要在创建对象之前初始化变量,即当我们尝试创建调用构造函数的新对象时,在对象返回到引用变量之前,值需要初始化,否则为编译时错误。

    回答您的问题:

    • Java中的静态变量属于类和 仅在调用类时初始化一次 (如果未初始化-延迟初始化)。

    • 静态最终变量在 类装入 方法区域 ,因为它已在代码中初始化 (早期初始化)。

    • 静态和最终静态的内存分配不会有任何变化。

      public class Object {
      
          public static final int i;
          static{
              i=0; // comment this line gives you compile time error
          }
          /**
           * @param args
           */
          public static void main(String[] args) {
      
          }
      }