精华内容
下载资源
问答
  • java虚拟机常量池

    2019-08-20 12:08:48
    常量池通用格式 cp_info { u1 tag; u1 info[]; } 1 2 3 4 tag 表示常量项类型,整理如下: 常量类型 值 描述 CONSTANT_Class_info 7 表示类或接口 CONSTANT_Fieldref_info 9 字段信息表 CONSTANT_Methodref_info 10 ...

    常量池通用格式

    cp_info {
    u1 tag;
    u1 info[];
    }
    1
    2
    3
    4
    tag 表示常量项类型,整理如下:

    常量类型 值 描述
    CONSTANT_Class_info 7 表示类或接口
    CONSTANT_Fieldref_info 9 字段信息表
    CONSTANT_Methodref_info 10 方法
    CONSTANT_InterfaceMethodref_info 11 接口方法
    CONSTANT_String_info 8 java.lang.String 类型的常量对象
    CONSTANT_Integer_info 3 整型字面量
    CONSTANT_Float_info 4 浮点型字面量
    CONSTANT_Long_info 5 长整型字面量
    CONSTANT_Double_info 6 双精度型字面量
    CONSTANT_NameAndType_info 12 名称和类型表
    CONSTANT_Utf8_info 1 utf-8 编码的字符串
    CONSTANT_MethodHandle_info 15 方法句柄表
    CONSTANT_MethodType_info 16 方法类型表
    CONSTANT_InvokeDynamic_info 18 动态方法调用点
    对应具体类型分析如下:
    1、CONSTANT_Class_info

    功能: 表示类或接口
    格式:

    CONSTANT_Class_info {
    u1 tag;
    u2 name_index;
    }
    其中:tag 值为7,表示一个 CONSTANT_Class_info 类型
    name_index, 必须是对常量池的一个有效索引。 常量池在该索引处的项必须是CONSTANT_Utf8_info 结构, 代表一个有效的类或接口二进制名称的内部形式。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    2、CONSTANT_Fieldref_info

    功能: 表示字段字面量
    格式:

    CONSTANT_Fieldref_info {
    u1 tag; //9
    u2 class_index; //CONSTANT_Fieldref_info 结构的 class_index 项的类型既可以是类也可以是接口。
    u2 name_and_type_index;
    }
    class_index 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_Class_info 结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。
    name_and_type_index: 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是 CONSTANT_NameAndType_info 结构,它表示当前字段或方法的名字和描述符。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    方法符号引用 和 接口方法符号引用结构和 字段字面量 类似,如下:
    3、CONSTANT_Methodref_info

    功能: 表示方法符号引用
    格式:
    CONSTANT_Methodref_info {
    u1 tag; //10
    u2 class_index; //CONSTANT_Methodref_info 结构的 class_index 项的类型必须是类(不能是接口)。
    u2 name_and_type_index;
    }
    1
    2
    3
    4
    5
    6
    7
    4、CONSTANT_InterfaceMethodref_info

    功能: 表示接口方法符号引用
    格式:
    CONSTANT_InterfaceMethodref_info {
    u1 tag; //11
    u2 class_index; //CONSTANT_InterfaceMethodref_info 结构的class_index 项的类型必须是接口
    (不能是类)。
    u2 name_and_type_index;
    }
    1
    2
    3
    4
    5
    6
    7
    8
    5、CONSTANT_String_info

    功能: 表示方法符号引用
    格式:
    CONSTANT_String_info {
    u1 tag; //8
    u2 string_index;
    }
    string_index 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_Utf8_info 结构,表示一组 Unicode 码点序列,这组 Unicode 码点序列最终会被初始化为一个 String 对象
    1
    2
    3
    4
    5
    6
    7
    6、CONSTANT_Integer_info、CONSTANT_Float_info

    功能: 表示表示 4 字节的整型、浮点型字面量
    格式:
    CONSTANT_Integer_info {
    u1 tag; //3
    u4 bytes;
    }
    CONSTANT_Integer_info 结构的 bytes 项表示 int 常量的值,按照 Big-Endian的顺序存储。

    CONSTANT_Float_info {
    u1 tag; //4
    u4 bytes;
    }
    CONSTANT_Float_info 结构的 bytes 项按照 IEEE 754 单精度浮点格式.表示 float 常量的值,按照 Big-Endian 的顺序存储。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    7、CONSTANT_Long_info 、CONSTANT_Double_info

    功能:表示 8 字节(long 和 double)的数值常量
    注1
    在Class 文件的常量池中,所有的 8 字节的常量都占两个表成员(项)的空间。如果一个 CONSTANT_Long_info 或 CONSTANT_Double_info 结构的项在常量池中的索引为 n,则常量池中下一个有效的项的索引为 n+2, 此时常量池中索引为 n+1 的项有效但必须被认为不可用。

    格式:
    CONSTANT_Long_info {
    u1 tag; //5
    u4 high_bytes;
    u4 low_bytes;
    }
    CONSTANT_Long_info 结构中的无符号的 high_bytes 和 low_bytes 项用于共同表
    示 long 型常量,构造形式为((long) high_bytes << 32) + low_bytes,high_bytes 和 low_bytes 都按照 Big-Endian 顺序存储。

    CONSTANT_Double_info {
    u1 tag; //6
    u4 high_bytes;
    u4 low_bytes;
    }
    CONSTANT_Double_info 结构中的 high_bytes 和 low_bytes 共同按照 IEEE 754
    双精度浮点格式 表示 double 常量的值。 high_bytes 和 low_bytes 都按照 Big-Endian 顺序存储。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    8、CONSTANT_NameAndType_info

    功能:表示字段或方法(描述其名称和类型)
    格式:
    CONSTANT_NameAndType_info {
    u1 tag; //12
    u2 name_index;
    u2 descriptor_index;
    }

    name_index 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_Utf8_info 结构,这个结构要么表示特殊的方法名,要么表示一个有效的字段或方法的非限定名(Unqualified Name)。

    descriptor_index 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_Utf8_info 结构,这个结构表示一个有效的字段描述符 或方法描述符。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    9、CONSTANT_Utf8_info

    功能:表示字段或方法
    格式:
    CONSTANT_Utf8_info {
    u1 tag; //1
    u2 length;
    u1 bytes[length];
    }
    length 项的值指明了 bytes[]数组的长度(注意,不能等同于当前结构所表示的String 对象的长度), CONSTANT_Utf8_info 结构中的内容是以 length 属性确定长度而不是以 null 作为字符串的终结符。
    如果 length 的值为 0x00, 则没有 bytes[length]。

    bytes[]是表示字符串值的byte数组, bytes[]数组中每个成员的byte值都不会是0,
    也不在 0xf0 至 0xff 范围内。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    10、CONSTANT_MethodHandle_info

    功能:表示方法句柄
    格式:
    CONSTANT_MethodHandle_info {
    u1 tag; //15
    u1 reference_kind;
    u2 reference_index;
    }
    reference_kind 项的值必须在 1 至 9 之间(包括 1 和 9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为。
    1
    2
    3
    4
    5
    6
    7
    8
    11、CONSTANT_MethodType_info

    功能:表示方法类型
    格式:
    CONSTANT_MethodType_info {
    u1 tag; //16
    u2 descriptor_index;
    }
    descriptor_index 项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_Utf8_info 结构,表示方法的描述符。
    1
    2
    3
    4
    5
    6
    7
    12、CONSTANT_InvokeDynamic_info

    功能:表示方法类型
    格式:
    CONSTANT_InvokeDynamic_info {
    u1 tag; //18
    u2 bootstrap_method_attr_index;
    u2 name_and_type_index;
    }

    bootstrap_method_attr_index 项的值必须是对当前 Class 文件中引导方法表的 bootstrap_methods[]数组的有效索引。

    name_and_type_index 项的值必须是对当前常量池的有效索引, 常量池在该索引处的项必须是 CONSTANT_NameAndType_info 结构,表示方法名和方法描述。

    展开全文
  • ,这样的,999这个值是自动装入虚拟机常量池的;下面来看一下具体情况是不是; hello4.java;此程序只是定义数值,啥也不做;然后反汇编之; public class hello4 { public static void main(String[] args) { ...

    从此文可以大体了解,

        https://blog.csdn.net/bcbobo21cn/article/details/109108970

    自己的程序中定义的数值,像 int a=999; ,这样的,999这个值是自动装入虚拟机常量池的;下面来看一下具体情况是不是;

    hello4.java;此程序只是定义数值,啥也不做;然后反汇编之;

    public class hello4 {
        public static void main(String[] args) {    
            int a11 = -21474999;
            int a16 = -32769;
            int a07 = 32768;
            int a08 = 65535;
            int a09 = 65536;
            int a10 = 21474999;
        }
    }

    构建;反汇编;

    看一下对于每个定义的数值执行了2条指令:ldc,istore_n;

    ldc: 从运行时常量池中提取数据并压入操作数栈;
    istore_<n>: 将一个int类型数据保存到本地变量表中;

        也就是说,自己程序给定的数值,是自动装入了虚拟机常量池;然后用ldc放入操作数栈,用istore指令在本地变量表中再存一份;

        常量池在网上资料有很多解释;什么是本地变量表,还有一个词叫局部变量表,目前还不清楚二者的区别;

    看一下下图的描述;

    从这图看;每个线程有一个java虚拟机实例,其中包含一个虚拟机栈;各个类的方法调用的栈帧在虚拟机栈中;一个具体的栈帧中包含本地变量表、操作数栈等这些;本地变量表就放各种具体类型的数值; 

    使用如下命令可以查看常量池;javap -verbose hello4

    看 Constant pool:后面;自己程序给定的数值都在常量池中,这是自动装入的;常量池中还有一些固定的符号,如 hello4 这些;

    此命令还有如下输出;

        还有一个叫行号表,LineNumberTable ,的东西;先到这里;

    展开全文
  • 常量池中不存在“wwe”时,会在常量池中新建一个常量。若存在,则直接返回该常量。 源码介绍如下: /** * Returns a canonical representation for the string object. * * A pool of strings, initially...
        

    public static void main(String[] args) {
    String str1 = new String("wwe");
    String str2 = new String("wwe");
    System.out.println(str1 == str2);
    System.out.println(str1.intern() == str2.intern());

        Integer a1 = 1;
        Integer a2 = new Integer(1);
        System.out.println(a1.equals(a2));
        System.out.println(a1 instanceof Integer);
    }
    

    输出:false true true true

    1.false 。 String str1 = new String("wwe"); String str2 = new String("wwe"); System.out.println(str1 == str2);类中的 ‘==’ 比较的是内存地址,通过new生成的对象会在堆中创建一个新的对象,内存地址明显不同。
    2.trueSystem.out.println(str1.intern() == str2.intern());。String.intern(),比较的是常量池中的值。当常量池中不存在“wwe”时,会在常量池中新建一个常量。若存在,则直接返回该常量。
    源码介绍如下:

    /**
    * Returns a canonical representation for the string object.
    * <p>
    * A pool of strings, initially empty, is maintained privately by the
    * class {@code String}.
    * <p>
    * When the intern method is invoked, if the pool already contains a
    * string equal to this {@code String} object as determined by
    * the {@link #equals(Object)} method, then the string from the pool is
    * returned. Otherwise, this {@code String} object is added to the
    * pool and a reference to this {@code String} object is returned.
    * <p>
    * It follows that for any two strings {@code s} and {@code t},
    * {@code s.intern() == t.intern()} is {@code true}
    * if and only if {@code s.equals(t)} is {@code true}.
    * <p>
    * All literal strings and string-valued constant expressions are
    * interned. String literals are defined in section 3.10.5 of the
    * <cite>The Java™ Language Specification</cite>.
    *
    * @return a string that has the same contents as this string, but is
    * guaranteed to be from a pool of unique strings.
    */

    public native String intern();

    一道思考题 new String("wwe").equals("11去去去") 创建了几个对象

    这里创建了1或2 个对象。如果常量池不存在“11去去去”则需要创建一个对象。

    3.true System.out.println(a1.equals(a2)); 以下是interger类型的equals函数,不同的复合类是不一样的

    /**
    * Compares this object to the specified object. The result is
    * {@code true} if and only if the argument is not
    * {@code null} and is an {@code Integer} object that
    * contains the same {@code int} value as this object.
    *
    * @param obj the object to compare with.
    * @return {@code true} if the objects are the same;
    * {@code false} otherwise.
    */

    public boolean equals(Object obj) {
    if (obj instanceof Integer) {
    return value == ((Integer)obj).intValue();
    }
    return false;
    }

    4.true。 System.out.println(a1 instanceof Integer);

    用来在运行时指出对象是否是特定类的一个实例

    展开全文
  • 常量池 class文件常量池(class constant pool) 常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,包含了类也是占用Class文件中第一个出现的表类型数据项目。 常量池中...

    常量池

    class文件常量池(class constant pool)

    常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型,包含了类也是占用Class文件中第一个出现的表类型数据项目。

    常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。而符号引用则属于编译原理方面的概念,包含了下面三类常量:

    • 类和接口的全限定名(Full Qualified Name)
    • 字段的名称和描述符(Descriptor)
    • 方法的名称和描述符

    类和接口的全限定名,例如:com/example/demo/Demo.class

    字段的名称和描述符,例如:Field a:[Ljava/lang/String

    方法的名称和描述符,例如:Method java/lang/String."<init>":(Ljava/lang/String;)V

    后两个是字节码指令,不懂得可以查阅下相关资料(TODO) + 可以通过查看字节码的形式来查看Class的常量池的内容,因为是在编译时产生的,也可以称为静态常量池

    public class Main {
       private int a=1;
       private int b=1;
       private Aload c=new Aload();
       private String [] d =new String[10];
       public static void main(String[] args) {
    
       }
    }
    字节码:
    public class com.verzqli.snake.Main
     minor version: 0
     major version: 51
     flags: ACC_PUBLIC, ACC_SUPER
    Constant pool: //这里就是class文件的常量池
      #1 = Methodref          #10.#30        // java/lang/Object."<init>":()V
      #2 = Fieldref           #9.#31         // com/verzqli/snake/Main.a:I
      #3 = Fieldref           #9.#32         // com/verzqli/snake/Main.b:I
      #4 = Class              #33            // com/verzqli/snake/Aload
      #5 = Methodref          #4.#30         // com/verzqli/snake/Aload."<init>":()V
      #6 = Fieldref           #9.#34         // com/verzqli/snake/Main.c:Lcom/verzqli/snake/Aload;
      #7 = Class              #35            // java/lang/String
      #8 = Fieldref           #9.#36         // com/verzqli/snake/Main.d:[Ljava/lang/String;
      #9 = Class              #37            // com/verzqli/snake/Main
     #10 = Class              #38            // java/lang/Object
     #11 = Utf8               a
     #12 = Utf8               I
     #13 = Utf8               b
     #14 = Utf8               c
     #15 = Utf8               Lcom/verzqli/snake/Aload;
     #16 = Utf8               d
     #17 = Utf8               [Ljava/lang/String;
     #18 = Utf8               <init>
     #19 = Utf8               ()V
     #20 = Utf8               Code
     #21 = Utf8               LineNumberTable
     #22 = Utf8               LocalVariableTable
     #23 = Utf8               this
     #24 = Utf8               Lcom/verzqli/snake/Main;
     #25 = Utf8               main
     #26 = Utf8               ([Ljava/lang/String;)V
     #27 = Utf8               args
     #28 = Utf8               SourceFile
     #29 = Utf8               Main.java
     #30 = NameAndType        #18:#19        // "<init>":()V
     #31 = NameAndType        #11:#12        // a:I
     #32 = NameAndType        #13:#12        // b:I
     #33 = Utf8               com/verzqli/snake/Aload
     #34 = NameAndType        #14:#15        // c:Lcom/verzqli/snake/Aload;
     #35 = Utf8               java/lang/String
     #36 = NameAndType        #16:#17        // d:[Ljava/lang/String;
     #37 = Utf8               com/verzqli/snake/Main
     #38 = Utf8               java/lang/Object
    

    运行时常量池

    当java文件被编译成class文件之后,就会生成上面的常量池,在Class文件中描述的各种信息,最终都需要加载到虚拟机中之后才能运行和使用。 类从被加载到虚拟机内存中开始,到卸载出内存位置,他的生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initalization)、使用(Using)和卸载(Unloading),其中验证、准备、解析三个部分统称Wie连接(Linking)。

    而当类加载到内存中后,JVM就会将Class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析过程中需要将常量池中所有的符号引用(classes、interfaces、fields、methods referenced in the constant pool)转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法)。直接引用可以是内存中,直接指向目标的指、相对偏移量,或是一个能间接定位到目标的句柄,解析的这个阶段其实就是将符号引用转换为可以直接定位对象等在内存中的位置的直接引用。

    运行时常量池位于JVM规范的方法区中,在Java8以前,位于永生代;Java8之后位于元空间。

    全局字符串常量池(string pool / string literal pool)

    全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中。在HotSpot中具体实现string pool这一功能的是StringTable类,它是一个哈希表,里面存的是key(字面量“abc”, 即驻留字符串)-value(字符串"abc"实例对象在堆中的引用)键值对,StringTable本身存在本地内存(native memory)中。

    StringTable在每个HotSpot VM的实例只有一份,被所有的类共享(享元模式)。在Java7的时候将字符串常量池移到了堆里,同时里面也不在存放对象(Java7以前被intern的String对象存放于永生代,所以很容易造成OOM),而是存放堆上String实例对象的引用。

    那么字符串常量池中引用的String对象是在什么时候创建的呢?在JVM规范里明确指定resolve阶段可以是lazy的,即在需要进行该符号引用的解析时才去解析它,这样的话,可能该类都已经初始化完成了,如果其他的类链接到该类中的符号引用,需要进行解析,这个时候才会去解析。

    这时候就需要ldc这个字节码指令,其作用是将int、float或String型常量值从常量池中推送至栈顶,如下面这个例子。

    public class Main {
        public static void main(String[] args) {
          String a="B";
        }
    }
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=2, args_size=1
             0: ldc           #2                  // String B
             2: astore_1
             3: return
          LineNumberTable:
            line 14: 0
            line 15: 3
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       4     0  args   [Ljava/lang/String;
                3       1     1     a   Ljava/lang/String;
    }
    

    在main方法的字节码中使用ldc将字符串“B”推到栈顶,然后赋值给局部变量a,最后退出。

    根据上面说的,在类加载阶段,这个 resolve 阶段( constant pool resolution )是lazy的。换句话说并没有真正的对象,字符串常量池里自然也没有,那么ldc指令还怎么把人推送至栈顶?或者换一个角度想,既然resolve 阶段是lazy的,那总有一个时候它要真正的执行吧,是什么时候?执行ldc指令就是触发这个lazy resolution动作的条件。

    ldc字节码在这里的执行语义是:到当前类的运行时常量池(runtime constant pool,HotSpot VM里是ConstantPool + ConstantPoolCache)去查找该index对应的项,如果该项尚未resolve则resolve之,并返回resolve后的内容。

    在遇到String类型常量时,resolve的过程如果发现StringTable已经有了内容匹配的java.lang.String的引用,则直接返回这个引用,反之,如果StringTable里尚未有内容匹配的String实例的引用,则会在Java堆里创建一个对应内容的String对象,然后在StringTable记录下这个引用,并返回这个引用出去。可见,ldc指令是否需要创建新的String实例,全看在第一次执行这一条ldc指令时,StringTable是否已经记录了一个对应内容的String的引用。

    public class Main {
        String a="b";
        public static void main(String[] args) {
        }
    }
    
    public com.verzqli.snake.Main();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: ldc           #2                  // String b
             7: putfield      #3                  // Field a:Ljava/lang/String;
            10: return
          LineNumberTable:
            line 12: 0
            line 13: 4
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      11     0  this   Lcom/verzqli/snake/Main;
    

    上面例子执行完main方法后,“b”就不会进入字符串常量池。因为String a = "b"是Main类的成员变量,成员变量只有在执行到构造方法的时候才会初始化。

    往细讲,只有执行了ldc指令的字符串才会进入字符串常量池

    至于ldc指令的工作原理可以看这篇文章

    String.intern()

    当一个字符串对象调用这个intern方法时,如果该字符串常量池中不包含该对象引用,也即StringTable不包含该对象字面量和引用时,将该字符串对象引用存入字符串常量中 ,同时返回该地址。这样做的目的是为了提升性能,降低开销,后续如果定义相同字面量的字符串即可返回该引用(内存地址),不必再在堆上创建字符串实例。

    实例(以下实例环境为JDK7以后)

       String a="c";
          String b = new String("c");
          System.out.println("a==b.intern()="+(a==b.intern()));
          System.out.println("b==b.intern()="+(b==b.intern()));
          
          结果:
          a==b.intern()=true
          b==b.intern()=false
    

    类加载阶段,什么都没干。

    然后运行main方法,创建“c”对象 ,假设其地址为0xeee,将其加入字符串常量池。随后在堆上创建了String对象b,假设其地址为0xfff

    这里b.intern()检测到了字符串常量池中包含“c”这个字符串引用,所以其返回的是0xeee,而b指向的依旧是0xfff,所以第一个为true,第二个为false

       String a = new String("hellow") + new String("orld");
         String b = new String("hello") + new String("world");
         System.out.println("a==a.intern()="+(a==a.intern()));
         System.out.println("a==b.intern()="+(a==b.intern()));
         System.out.println("b==b.intern()="+(b==b.intern()));
    
       结果:
      a==b.intern()=true
      a==b.intern()=true
      b==b.intern()=false
    

    类加载阶段,什么都没干。

    然后运行main方法,创建“hellow”,"orld"对象,并放入字符串常量池。然后会创建一个"helloworld"对象,没有放入字符串常量池,a指向这个"helloworld"对象(0xeee)。

    接着创建“hello”,"world"对象,同样也创建一个"helloworld"对象,也没有放入字符串常量池,b指向这个"helloworld"对象地址(0xfff)。

    这时候第一个判断,字符串常量池没有“helloworld”这个字符串对象引用,所以将a的引用(0xeee)放入字符串常量池,也就是说池子中的引用和a的引用(0xeee)是一样的,所以a==a.intern()

    b.intern()时因为上一部字符串常量池中已经有了这个“helloworld”的引用,所以他返回回去的引用(0xeee)就是a的引用,所以a==b.intern()

    从上面可以清楚的知道b.intern()返回的是0xfff,而b引用地址为0xfff,所以b!=b.intern()

    //        String a1="helloworld";
    String a = new String("hello")+new String("world");
    System.out.println("a==a=" + (a == a.intern()));
    

    这里的结果如果a1没有被注释则为false,注释了则为true,原理同上,可以自己脑补一下。

    JVM对字符串的优化

          String a = "hello";
        String b = a+"world";
        String c = "helloworld";
        String d = "hello"+"world";
        System.out.println(b==c); false
        System.out.println(d==c); true
        System.out.println(b==d); false
        
            Code:
      stack=3, locals=5, args_size=1
         0: ldc           #4                  // String hello //ldc指令创建字符串对象“hello”
         2: astore_1                          // 将a从放入局部变量表(第一个局部变量,第0个是this)
         3: new           #5                  // class java/lang/StringBuilder //创建StringBuilder对象
         6: dup                               // 复制栈顶数据(创建StringBuilder对象)压入栈中
         7: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V 
        10: aload_1                           // 从局部变量中载入a到栈中
        11: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; //可以看出字符串相加在字节码里就是StringBuilder的append
        14: ldc           #8                  // String world /ldc指令创建字符串对象“world”
        16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;//继续append
        19: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String; //相加完毕,隐形的调用toString生成String对象返回
        22: astore_2                          // 将b放入局部变量表(第二个局部变量)   
        23: ldc           #10                 // String helloworld  //ldc指令创建字符串对象“helloworld”
        25: astore_3                          // 将c放入局部变量表(第三个局部变量) 
        26: ldc           #10                 // String helloworld  //这里字符串常量池中已经包含了helloworld,就不会再创建,直接引用,而且这个helloworld是"hello"+"world"拼接的,这就是JVM对字符串的优化
        28: astore        4                   // 将d放入局部变量表(第四个局部变量) 
        30: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream; //调用静态方法打印
        33: aload_2                           // 从局部变量表加载b入栈
        34: aload_3                           // 从局部变量表加载c入栈
        35: if_acmpne     42                  // 比较两个对象的引用类型 下面四行就是一个if else 语句,如果相等就直接doto打印结果,
        38: iconst_1                          // 获得两个引用是否相等的结果(true为1,false为0),将1入栈
        39: goto          43                  // 跳转到43行 直接打印出结果
        42: iconst_0                          // 两引用不相等,将0入栈 
        43: invokevirtual #12                 // Method java/io/PrintStream.println:(Z)V
        46: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
        后续都是相同的意思,这里就不注释了。
        49: aload         4
        51: aload_3
        52: if_acmpne     59
        55: iconst_1
        56: goto          60
        59: iconst_0
        60: invokevirtual #12                 // Method java/io/PrintStream.println:(Z)V
        63: getstatic     #11                 // Field java/lang/System.out:Ljava/io/PrintStream;
        66: aload_2
        67: aload         4
        69: if_acmpne     76
        72: iconst_1
        73: goto          77
        76: iconst_0
        77: invokevirtual #12                 // Method java/io/PrintStream.println:(Z)V
        80: return
    

    从上面的字节码可以看出字符串的相加其实是new了一个StringBuilder来进行append,a和b不相等就是因为这已经是两个不同的对象了,引用也不相等。后续c和d相等是因为JVM对纯字符串想加做了调优,会在字节码中把他们直接相加后的值赋给局部变量,所以c和d指向的是同一个字符串。

        String a= "a";
        for (int i = 0; i < 3; i++) {
            a+="b";
        }
        
        Code:
      stack=2, locals=3, args_size=1
         0: ldc           #4                  // String a
         2: astore_1
         3: iconst_0
         4: istore_2
         5: iload_2
         6: iconst_3
         7: if_icmpge     36
        10: new           #5                  // class java/lang/StringBuilder
        13: dup
        14: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        17: aload_1
        18: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        21: ldc           #2                  // String b
        23: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        26: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        29: astore_1
        30: iinc          2, 1
        33: goto          5
        36: return
    

    对于for循环中的字符串相加(3到33行就是for循环的内容),JVM就没有优化了,每次相加都是重新创建了StringBuilder,开销就是一个StringBuilder的几何倍数那么大,因而在循环中使用StringBuilder的append来替代直接相加。

    总结

    除了日常的如果觉得文章有错误,欢迎指出并交流。这里问一个问题,后续如果知道了再删除:字符串常量池和StringTable是一个东西吗,两者都是存的字符串引用,但是R大说过StringTable是存于本地内存(native memory),但是看过的文章都说的是字符串常量池位于java堆中,希望有知道的大佬可以告知一下。

    (想自学习编程的小伙伴请搜索圈T社区,更多行业相关资讯更有行业相关免费视频教程。完全免费哦!)

    展开全文
  • (静态)常量池:用于存放编译器生成的各种字面量和符号引用(符号引用区别于直接引用,后者在class字节码文件被虚拟机解析之后,符号引用将被替换为直接引用)。 运行时常量池:(静态)常量池中的内容在类加载...
  • 运⾏时常量池是⽅法区的⼀部分,Class⽂件除了有类的版本、字段、⽅法、接⼝等描述信息 外,还有⼀项信息是常量池,⽤于存放编译器⽣成的各种字⾯量和符号引⽤,这部分内容将 在类加载后进⼊⽅法区的运⾏时常量池中...
  • Java几个常量池:全局字符串池 string poolclass文件常量池 class constant pool运行时常量池 runtime constant pool全局字符串池存放字符串实例的引用(类加载完成、验证、准备后在堆中生成),真正实例在堆中在...
  • java虚拟机学习-深入理解JVM(1) java虚拟机学习-慢慢琢磨JVM(2) java虚拟机学习-慢慢琢磨JVM(2-1)ClassLoader的工作机制 java虚拟机学习-JVM内存管理:深入Java内存区域与OOM(3) java虚拟机学习-JVM内存管理:...
  • 运行时常量池是方法区的一部分。... Java虚拟机对Class文件每一部分(自然包括常量池)的格式都有严格的规定,每一个字节用于存储那种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但...
  • 运行时常量池是方法区的一部分。class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池...
  • 一、运行时常量池简介 运行时常量池(Runtime Constant Pool),它是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期...
  • 1.运行时常量池 运行时常量池,是方法区一部分,
  • 虚拟机栈是jvm执行java代码所使用的栈(局部变量表(栈)、操作数栈(数值计算)、动态链接、附加信息、返回地址(帮助栈帧去恢复上层方法的状态))。 方法区存放了一些常量、静态变量、类信息等,可以理解成class...
  • Class文件中的常量池在Class文件结构中,最头的4个字节用于存储魔数Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号...
  • 而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。 运行时常量池相对于Class文件常量池的另外一个重要...
  • 比如像常量池,它是类文件中最为重要的一部分之一,但是网络上关于常量池的说话五花八门,因此我决定参考《深入理解Java虚拟机》一书去对Class文件以及常量池等重要知识进行学习和梳理。 Class类文件的结构 Class...
  • 如果要向运行时常量池中添加内容,最简单的做法就是使用 String.intern()这个 Native 方法。该方法的作用是:如果池中已经包含一个等于此 String 对象的字符串,则返回代表池中这个字符串的String 对象;否则,将此 ...
  • 本文详细介绍了 Java 中的常量和字面量的区别,字节码中的常量池和运行时常量池的区别,最后详细介绍了字符串常量池和 String.intern 方法
  • 解析常量池是解读Java字节码文件的的基础。 jvm会将字节码文件中的常量池信息进行解析,并存储到jvm内存模型中的常量区。 jvm使用ClassFileParser:parseClassFile()方法解析字节码文件。 内部解析步骤: 解析...
  • Java虚拟机--方法区(运行时常量池)

    千次阅读 2018-07-15 11:38:20
    深入理解Java虚拟机 一 方法区描述 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息/常量//静态信息/即时编译器编译后的代码等数据.虽然Java虚拟机规范把方法区...
  • java中常量以及常量池

    2020-12-22 19:39:53
    1、举例说明 变量 常量 字面量  1 int a=10;  2 float b=1.234f;  3 String c="abc";  4 final long d=10L;  a,b,c为变量,d为常量 两者都是左值;...  运行时常量池:是jvm虚拟机在完成类装
  • 深入理解java虚拟机(三):String.intern()-字符串常量池 深入理解java虚拟机(四):对象存活判定算法和垃圾收集算法 深入理解java虚拟机(五):hotspot垃圾收集算法实现  深入理解java虚拟机(六):java...
  • 感于以上的种种,我打算把我在学习JVM虚拟机的过程中学到的东西,结合自己的理解,总结成《Java虚拟机原理图解》 这个系列,以图解的形式,将抽象的JVM虚拟机的知识具体化,希望能够对想了解Java虚拟机原理的的Java...
  • Java常量池

    2017-10-14 16:22:47
    ava中的常量池,实际上分为两种形态:静态常量池和运行时常量池。... 而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中
  • 这几天在看Java虚拟机方面的知识时,看到了有几种不同常量池的说法,然后我就去CSDN、博客园等上找资料,里面说的内容真是百花齐放,各自争艳,因此,我好好整理了一下,将我自认为对的理解写下来与大家共同探讨: ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,293
精华内容 1,717
关键字:

java虚拟机常量池

java 订阅