[讨论] 关于编译时常量问题

freish 2012-11-05
问题:如果一个类中包含编译时常量,该常量是否存在与方法区

读《深入Java虚拟机 第二版》P94最后一段有这样的描述“作为常量池或字节码流的一部分,编译时常量保存在方法区中,就和一般的类变量一样”——这个翻译的很拗口,原文“As part of the constant pool, final class variables are stored in the method area--just like non-final class variables”后面还有一句“But whereas non-final class variables are stored as part of the data for the type that declares them, final class variables are stored as part of the data for any type that uses them. ”


但是在P160页又发现这样的描述(第一段代码的下面):


class Example1d {

    static final int angle = 35;
    static final int length = angle * 2;
}

When the Example1d class is loaded by a Java Virtual Machine, angle and length are not stored as class variables in the method area


在java虚拟机规范中好像没有找到相关描述

通过反射可以获取常量值,推测应该是存在于方法区的。

----------------------------
综上,编译时常量到底有没有存在于方法区?

RednaxelaFX 2012-11-05
Java语言(由Java语言规范所规定的)有常量表达式的概念。这里所认可的常量很重要的一个方面就是它们都是值语义的。值语义意味着你只关心某个数据的值,不关心它的“身份”(identity);也就是说无论存在哪里、有多少份拷贝、是不是同一份拷贝都好,只要值相等你就会认为它们是“相同”的。

那么回过头来看些例子。留意声明的一侧跟使用的一侧不一定是对等的。

如果有:
public class Foo {
    public static final int BAR = 1;
    public static final int BAZ = 42;
    public static final int GOO = 256;
    public static final int QUUX = 65536;
}

(留意这是声明的一侧)

那么随便找个别的方法来看这些常量要访问的话会用怎样的字节码。
(接下来是使用的一侧)
int i = Foo.BAR;

这个语句的右手边的常量表达式会变成:
iconst_1

常量1就嵌在指令里了,而且不是作为指令的参数,而是指令本身就嵌着“1”这个常量值

int i = Foo.BAZ;

这个语句的右手边的常量表达式会变成:
bipush 42

常量42作为指令bipush的参数嵌在字节码里

int i = Foo.GOO;

这个语句的右手边的常量表达式会变成:
sipush 256

常量256作为参数嵌在字节码里

int i = Foo.QUUX;

这个语句的右手边的常量表达式会变成:
ldc #2

常量65536存在常量池里的第2项(这个常量池项的编号是我随便选的,无所谓)。

所以你会发现正常(非反射)去访问这些常量的时候,它们可能的形态就有好多种;其中小常量是尽量不放在常量池里的。
当然,因为字节码概念上会存在方法区里,所以那些嵌在指令里的常量概念上也存在方法区里了。不过跟许多人想像的形式大概不一样吧。

而为了支持反射,同样的一份值会在类的元数据里也放上一份。对应到Class文件格式那就是Field里的ConstantValue属性,具体的值会存在声明该常量的类的常量池里(跟使用的一侧没关系)。
ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}

(留意到constantvalue_index存的是个索引号,真正的常量值在这个索引号对应的常量池项)
这一份在运行时就会存在方法区里。
freish 2012-11-06
RednaxelaFX 写道
Java语言(由Java语言规范所规定的)有常量表达式的概念。这里所认可的常量很重要的一个方面就是它们都是值语义的。值语义意味着你只关心某个数据的值,不关心它的“身份”(identity);也就是说无论存在哪里、有多少份拷贝、是不是同一份拷贝都好,只要值相等你就会认为它们是“相同”的。

那么回过头来看些例子。留意声明的一侧跟使用的一侧不一定是对等的。

如果有:
public class Foo {
    public static final int BAR = 1;
    public static final int BAZ = 42;
    public static final int GOO = 256;
    public static final int QUUX = 65536;
}

(留意这是声明的一侧)

那么随便找个别的方法来看这些常量要访问的话会用怎样的字节码。
(接下来是使用的一侧)
int i = Foo.BAR;

这个语句的右手边的常量表达式会变成:
iconst_1

常量1就嵌在指令里了,而且不是作为指令的参数,而是指令本身就嵌着“1”这个常量值

int i = Foo.BAZ;

这个语句的右手边的常量表达式会变成:
bipush 42

常量42作为指令bipush的参数嵌在字节码里

int i = Foo.GOO;

这个语句的右手边的常量表达式会变成:
sipush 256

常量256作为参数嵌在字节码里

int i = Foo.QUUX;

这个语句的右手边的常量表达式会变成:
ldc #2

常量65536存在常量池里的第2项(这个常量池项的编号是我随便选的,无所谓)。

所以你会发现正常(非反射)去访问这些常量的时候,它们可能的形态就有好多种;其中小常量是尽量不放在常量池里的。
当然,因为字节码概念上会存在方法区里,所以那些嵌在指令里的常量概念上也存在方法区里了。不过跟许多人想像的形式大概不一样吧。

而为了支持反射,同样的一份值会在类的元数据里也放上一份。对应到Class文件格式那就是Field里的ConstantValue属性,具体的值会存在声明该常量的类的常量池里(跟使用的一侧没关系)。
ConstantValue_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 constantvalue_index;
}

(留意到constantvalue_index存的是个索引号,真正的常量值在这个索引号对应的常量池项)
这一份在运行时就会存在方法区里。





R大解释的非常详细清楚,我想知道的就是有没有存在于类的元数据里,3Q
RednaxelaFX 2012-11-06
freish 写道
R大解释的非常详细清楚,我想知道的就是有没有存在于类的元数据里,3Q

嗯嗯
而我想说明的是“不是只存在于声明常量的类的元数据里”。
Global site tag (gtag.js) - Google Analytics