java 常量池

前言

对常量池的理解之前,需要熟悉的是一些术语:

字面量

在计算机科学中,字面量(literal)是用于表达源代码中一个固定值的表示法(notation)。
几乎所有计算机编程语言都具有对基本值的字面量表示,诸如:整数、浮点数以及字符串;而有很多也对布尔类型和字符类型的值也支持字面量表示;
还有一些甚至对枚举类型的元素以及像数组、记录和对象等复合类型的值也支持字面量表示法。C语言关于复合字面量的介绍可参考: [1]  。

百度也给了一个例子:

这个object-c 的例子,容易理解。

#include <stdio.h>
int main(void)
{    
    int a = 10; // 10为int类型字面量
    char a[] = {"Hello world!"} // Hello world 为字符串形式字面量
       .............
   // 以此类推,不再赘述
    return 0;
}

正文

JVM常量池主要分为Class文件常量池、运行时常量池,全局字符串常量池,以及基本类型包装类对象常量池。

我在网上找了一个例子:

private int value = 1;
public String s = "abc";
public final static int f = 0x101;
public static void main(String[] args)
{
}
public void setValue(int v){
	final int temp = 3;
	this.value = temp + v;
}
public int getValue(){
	return value;
}

编译后:

下面只截取了一部分,常量池:

public class test.program
  minor version: 0
  major version: 57
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #1                          // test/program
  super_class: #3                         // java/lang/Object
  interfaces: 0, fields: 3, methods: 4, attributes: 1
Constant pool:
   #1 = Class              #2             // test/program
   #2 = Utf8               test/program
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               value
   #6 = Utf8               I
   #7 = Utf8               s
   #8 = Utf8               Ljava/lang/String;
   #9 = Utf8               f
  #10 = Utf8               ConstantValue
  #11 = Integer            257
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Methodref          #3.#16         // java/lang/Object."<init>":()V
  #16 = NameAndType        #12:#13        // "<init>":()V
  #17 = Fieldref           #1.#18         // test/program.value:I
  #18 = NameAndType        #5:#6          // value:I
  #19 = String             #20            // abc
  #20 = Utf8               abc
  #21 = Fieldref           #1.#22         // test/program.s:Ljava/lang/String;
  #22 = NameAndType        #7:#8          // s:Ljava/lang/String;
  #23 = Utf8               LineNumberTable
  #24 = Utf8               LocalVariableTable
  #25 = Utf8               this
  #26 = Utf8               Ltest/program;
  #27 = Utf8               main
  #28 = Utf8               ([Ljava/lang/String;)V
  #29 = Utf8               args
  #30 = Utf8               [Ljava/lang/String;
  #31 = Utf8               setValue
  #32 = Utf8               (I)V
  #33 = Utf8               v
  #34 = Utf8               temp
  #35 = Utf8               getValue
  #36 = Utf8               ()I
  #37 = Utf8               SourceFile
  #38 = Utf8               program.java

好的下面介绍class 常量池;

class 常量池

主要包括:字面量和符号引用

首先字面量不是全部的字面量,如果不明白什么是字面值请看上面;
字符字面值:

#7 = Utf8               s
#20 = Utf8               abc

用final修饰的成员变量

#9 = Utf8               f
#11 = Integer            257

大概包含的就是这两种。

  1. 符号引用

符号引用主要设涉及编译原理方面的概念,包括下面三类常量:

类和接口的全限定名,也就是java/lang/String;这样,将类名中原来的"."替换为"/"得到的,主要用于在运行时解析得到类的直接引用,像上面

#5 = Class              #33            // JavaBasicKnowledge/JavaBean
#33 = Utf8               JavaBasicKnowledge/JavaBean

字段的名称和描述符,字段也就是类或者接口中声明的变量,包括类级别变量和实例级的变量

#4 = Fieldref           #5.#32         // JavaBasicKnowledge/JavaBean.value:I
#5 = Class              #33            // JavaBasicKnowledge/JavaBean
#32 = NameAndType       #7:#8          // value:I

#7 = Utf8               value
#8 = Utf8               I

//这两个是局部变量,值保留字段名称
#23 = Utf8               v
#24 = Utf8               temp

可以看到,对于方法中的局部变量名,class文件的常量池仅仅保存字段名。

方法中的名称和描述符,也即参数类型+返回值

 #21 = Utf8               setValue
 #22 = Utf8               (I)V

 #25 = Utf8               getValue
 #26 = Utf8               ()I

其实并不需要怎么关注符号引用。

那么这些class 常量池有什么好处呢?

运行时常量池是方法区的一部分,所以也是全局贡献的,我们知道,jvm在执行某个类的时候,必须经过加载、链接(验证、准备、解析)、初始化,在第一步加载的时候需要完成:

通过一个类的全限定名来获取此类的二进制字节流

将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

在内存中生成一个类对象,代表加载的这个类,这个对象是java.lang.Class,它作为方法区这个类的各种数据访问的入口。

类对象和普通对象是不同的,类对象是在类加载的时候完成的,是jvm创建的并且是单例的,作为这个类和外界交互的入口, 而普通的对象一般是在调用new之后创建。

上面的第二条,将class字节流代表的静态存储结构转化为方法区的运行时数据结构,其中就包含了class文件常量池进入运行时常量池的过程,这里需要强调一下不同的类共用一个运行时常量池

,同时在进入运行时常量池的过程中,多个class文件中常量池相同的字符串,多个class文件中常量池中相同的字符串只会存在一份在运行时常量池,这也是一种优化。

运行时常量池的作用是存储java class文件常量池中的符号信息,运行时常量池中保存着一些class文件中描述的符号引用,同时在类的解析阶段还会将这些符号引用翻译出直接引用(直接指向实例对象的指针,内存地址),翻译出来的直接引用也是存储在运行时常量池中。

运行时常量池相对于class常量池一大特征就是具有动态性,java规范并不要求常量只能在运行时才产生,也就是说运行时常量池的内容并不全部来自class常量池,在运行时可以通过代码生成常量并将其放入运行时常量池中,这种特性被用的最多的就是String.intern()。

那么就看下String.intern() 来理解:运行时常量池。

首先看下:

string x="x" 和 String x=new String("x");

有什么区别?

可以肯定的是他们的值是一样的。

但是他们运行差别很大。string x="x" 会查找常量池,如果没有x的话,那么会存入常量池,如果有的话,那么会存在于常量池并进行引用。

而 String x=new String("x") 则只会生成在堆中,而不会和常量池产生联系。

注:

常量字符串和变量拼接时(如:String str3=baseStr + “01”;)会调用stringBuilder.append()在堆上创建新的对象。

那么String.intern() 是什么意思呢?这个是会去查找变量词中有没有,如果有的话那么会返回引用,如果没有的话,这个和版本有关。

题目

public static void main(String[] args) {
// write your code here
	Integer i01=59;
	int i02=59;
	Integer i03=Integer.valueOf(59);
	Integer i04= new Integer(59);
}

public static Integer valueOf(int i) {
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

问题如下:

i01 是否和 i02 相等?

i03 是否和 i01 相等。

i04 是否和 i01相等。

总结

写的比较仓促,后续会完善好。

原文地址:https://www.cnblogs.com/aoximin/p/12824458.html