精华内容
下载资源
问答
  • Java有三种常量池,即字符串常量池、class文件常量池、运行时常量池。1. 字符串常量池——特殊的常量池,存在于方法区(method are), 不是堆字符串常量池在每个VM中只有一份,他在内存中的位置如图,红色箭头所指向的...

    Java有三种常量池,即字符串常量池、class文件常量池、运行时常量池

    4bfdbcb0141774c2da6553a10f384781.png

    1. 字符串常量池——特殊的常量池,存在于方法区(method are), 不是堆

    字符串常量池在每个VM中只有一份,他在内存中的位置如图,红色箭头所指向的区域 Interned Strings, 存在于方法区, 不是堆

    抽象点忽略细节来看:大概需要注意这些

    37997b52d08442e7cef1037564406021.png
    抽象图


    String str1 = “abc”;

    String str2 = “abc”;

    String str3 = “abc”;

    //new 一定会新建对象——且会有类似引用变量的指向常量池的"引用"行为

    String str4 = new String(“abc”);

    String str5 = new String(“abc”);

    面试题: 相信绝对是个经典兼考倒一堆人的题目

    1. String a = new String("1"+"2")共建了几个对象?

    2个

    第一个:看构造器里面("1"+"2"),这个是在编译期就已经做了处理,即代表生成一个字符串:"12"

    第二个:当使用new的方法创建字符串时,注意这个”new“,就表示直接开辟了内存空间;然后把值"引用”的常量池中的“12”。最后返回该对象引用。

    所以就创建了2个对象。

    总结:

    1. string的 "+操作符,先在常量池生成常量!!” 但是"+"操作符,有编译器优化

    2. 有new String(...)的话: String也是常量池生成完之后!!才轮到堆;没有new,就不管堆的情况

    2. 同理:String str="a"+"b"+"c" 共建了几个对象?

    只有1个

    在编译期已经常量折叠为"abc", 通过编译器优化后,得到的效果是

    String str="abc"

    3. String str2 = new String("ABC") + "ABC" ; 会创建多少个对象?

    3个

    new String("ABC") :常量池+堆各一个,2个

    后面再 "+ ABC": 常量池新生成 “ABCABC" 1个

    所以共3个


    下面两个池都在方法区里(method area),是每个被加载的类,都有的

    2. 运行时常量池(runtime constant pool)

    当程序运行到某个类时,class文件中的信息就会被解析到内存的方法区里的运行时常量池中。看图可清晰感知到每一个类被加载进来都会产生一个运行时常量池,由此可知,每个类都有一个运行时常量池

    3. class文件常量池(class constant pool)

    class常量池也是在编译后每个class文件都有的

    class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是 常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。

    *字面量就是我们所说的常量概念,如文本字符串、被声明为final的常量值等。

    *他在class文件中的位置如上图所示,Constant Pool 中。

    展开全文
  • 字符串池与常量池是完全不同的两个东西,但是很多地方都喜欢把它们混为一谈,很容易让初学者产生误解,这里我想好好讨论一下它们。字符串池也可以被称为字符串常量池,我认为这个名称就是产生误解的根源,有些人说...

    字符串池与常量池是完全不同的两个东西,但是很多地方都喜欢把它们混为一谈,很容易让初学者产生误解,在这里我想好好讨论一下它们。

    字符串池也可以被称为字符串常量池,我认为这个名称就是产生误解的根源,有些人说着说着就把字符串三个字省略了,只剩下了常量池... 所以为了避免误解,我建议在指代字符串对象的缓存池的时候,就直接称之为字符串池

    1 常量池

    常量池分为两个类型,一是.class文件中静态的常量池,二是.class文件中的静态常量池被加载到JVM中而形成的运行时常量池

    1.1 静态常量池

    .class文件中的常量池可以看作一个数组,数组中存储了一些常量,当需要在字节码指令中用到这个常量的时候,就通过数组的索引来访问它。

    看下面的代码:

     String m = "hellohellohellohellohello";
     String n = "hellohellohellohellohello";

    它在字节码中将会是这种形式:

     // 常量池:
     #1 hellohellohellohellohello
     #2 ...
     ...
     ----------------------------
     ​
     String m = #1;
     String n = #1;

    当然,这只是一个简化的版本,实际上要更加复杂 (实际的版本可以看文章末尾参考资料部分里面贴出的那个回答,目前可以先只考虑简化的版本)

    注意,在这个里面存储的字符串常量只是一个简单的UTF8编码的字节序列,而不是Java的字符串对象,它就和你在一个txt文本中存储的字符串一样,我们用UTF8格式来打开一个.class文件,可以看到hellohellohellohellohello是可以被解析的:

    eb493a5e719f70563808116f20bf8ec7.png

    1.2 运行时常量池

    理解了静态的常量池之后,运行时常量池就很容易想明白了。简单来说,运行时常量池就是.class文件中的静态常量池在JVM中的运行时表示,每一个.class文件的静态常量池都会生成一个对应的运行时常量池。等到JVM在解释String m = #1这条指令时,它可以去这个类的运行时常量池中查找#1的定义。

    2 字符串池

    字符串池是Java为了重用String对象而设置的一个缓存池,Java1.7之前设置在方法区上,保存的是String对象;Java1.7之后设置在堆上,保存的是String对象的引用,String对象本身存在于堆上的其他位置。下文中以Java1.7之后的情况为标准。

    继续上面的例子。当JVM在解释String m = #1时,它已经从运行时常量池拿到了相应的UTF8序列,接下来,它会在字符串池中寻找和这个UTF8序列对应的String对象,并把这个对象的引用赋值给m。你可能会好奇这个String被创建的时机,根据R大的这篇文章,在这条语句所在的类被加载时,如果字符串池中已经存在对应的对象了,那么就什么都不做,如果不存在,就会创建一个对应的String对象,并把其引用放入池中。

    除了字符串池,IntegerLong等Wrapper类型也有自己的缓存池,比如Integer会缓存从-128~127的Integer对象,当使用字面量赋值或者Integer.valueOf()时,如果池中存在相应的对象,就会返回池中的对象,只有当池中没有时才会在堆上创建新对象。

    不过,和字符串池不同的时,这些Wrapper池不会像字符串池一样可以增长,也就是池中的对象数目是固定的,Integer池中只会有-128~127。

    3 涉及String的其他内容

    在这里我收集了一些关于String的一些有趣和需要注意的信息,欢迎大家补充

    强调,下面的代码只在JDK 11的环境下进行了测试,不同的JDK中可能会有不同的行为,同样欢迎大家补充

    3.1 在Java 7之后String.intern()的行为

    考虑下面的代码:

     String a = new String("hello");
     a.intern();

    a.intern()会在字符串池中查找是否有一个字符串引用所指向的对象的值是"hello",如果有,就直接返回池中的引用;如果没有,就把a指向的对象的引用放入池中,在返回该引用。所以:

     String a = new String("hello");
     return a == a.intern(); 
     
     // 返回false.
     // a调用intern方法时,字符串池中已经存在表示hello这个字符序列的String对象引用了(因为"hello"字面量的存在),所以a.intern返回的引用和a肯定不同
    
    ------------------------------------------------------------------------------
    
     String a = new String(new char[] {'h', 'e', 'l', 'l', 'o'});
     return a == a.intern();
     
     // 返回true
     // new String(new char[] {'h', 'e', 'l', 'l', 'o'})因为没有出现"hello"字面量所以池中没有表示hello的对象引用那么a.intern会将a放入池中再返回返回结果自然和a相等

    3.2 两个字符串相加时创建的新对象(的引用)会被放入字符串池中吗:

    结论:除非是两个字面量直接相加,否则都不会被放入字符串池中

     String m = "ja" + "va";
     String n = new String(new char[]{'j', 'a', 'v', 'a'});
     
     System.out.println(n.intern() == n);
     
     // 输出:false
     
     // 分析:
     // 根据n.intern() != n 可以得出相加后的"java"对象被放入了字符串池。实际上,编译器会将"ja" + "va"直接连起来变成"java",所以字节码中是m = "java",这样一来在解释器遇到这一行时,一个"java"对象就会在堆上被创建,并且它的引用会被放入字符串池中
    
    ---------------------------------------------------------------------------
    
     String m1 = "ja";
     String m2 = "va";
     String m = m1 + m2;
     String n = new String(new char[]{'j', 'a', 'v', 'a'});
     
     System.out.println(n.intern() == n);
     
     // 输出:true
     
     // 分析:
     // 根据n.intern() == n 可以得出相加后的"java"对象没有被放入字符串池。
     
     // 可以自己测试当m1 = new String("ja"), m2 = new String("va")的情况得到的结果和本例是一样的

    4 总结

    运行时常量池中的字符串是UTF8序列,字符串池中存储的是Java的String对象(的引用),不要把两者弄混了。

    5 参考资料

    更详细的内容比如字面量对应的String对象被创建的时机、对字节码的分析可以看 @木女孩大大的这篇回答:

    Java 中new String("字面量") 中 "字面量" 是何时进入字符串常量池的?www.zhihu.com

    其他参考:

    String pool vs Constant poolstackoverflow.com
    5735cde254a831de1bf93a51ff4f3afe.png
    Why does the behavior of the Integer constant pool change at 127?stackoverflow.com
    5735cde254a831de1bf93a51ff4f3afe.png
    请别再拿“String s = new String("xyz");创建了多少个String实例”来面试了吧www.iteye.com

    有帮助的话麻烦点个赞奥~ 另外也欢迎关注我的专栏!

    展开全文
  • 我们都知道,JVM运行时数据区中,有块内容也叫常量池,它位于方法区中,这两个常量池有什么关系呢?其实,可以简单这么理解,class文件中的常量池是基础,是通过字节码文件进行的静态的描述,而方法区中的常量池,是...

    我们都知道,JVM运行时数据区中,有块内容也叫常量池,它位于方法区中,这两个常量池有什么关系呢?其实,可以简单这么理解,class文件中的常量池是基础,是通过字节码文件进行的静态的描述,而方法区中的常量池,是程序运行起来后,类加载器将class字节码文件进行加载,其中class文件常量池部分,动态加载到了内存中,目的地就是方法区中的常量池内。下面,我们先来看看class文件中常量池的存储结构。

    常量池在class文件中的位置

    50154bcde0cb6d1c05621f6d797e01ce.png

    常量池的位置

    常量池的组成

    d27c2666f86de88a27e2ac7d4266f301.png

    常量池组成

    常量池主要包括两部分:

    1. constant_pool_count:常量池中cp_info的个数
    2. cp_info:常量池中的项

    常量池项的结构

    e10c5172da363873243d10e4a1a3d24f.png

    常量池项结构

    JVM虚拟机规定了不同的tag值和不同类型的字面量对应关系如下:

    e4846543bf2b5f8c29c685c769907c70.png

    tag值含义

    根据cp_info中的tag 不同的值,可以将cp_info 更细化为以下结构体:

    721b4c1c966f3653d3c867cb3493d036.png

    常量池项分类

    int、float存储方式

    Java语言规范规定了 int类型和Float 类型的数据类型占用 4 个字节的空间。class字节码文件中的该类型的常量是按照如下方式存储的:

    ea65a3dc26be7e7c22e4572766c80137.png

    int、float存储方式

    示例:

    package jvm.constant.pool;public class IntAndFloatTest {    private final int a = 10;    private final int b = 10;    private final float c = 11f;    private final float d = 11f;    private final float e = 11f;}

    使用javap -v,截取常量池部分:

    Constant pool:   #1 = Methodref          #9.#28         // java/lang/Object."":()V   #2 = Fieldref           #8.#29         // jvm/constant/pool/IntAndFloatTest.a:I   #3 = Fieldref           #8.#30         // jvm/constant/pool/IntAndFloatTest.b:I   #4 = Float              11.0f           //常量11f   #5 = Fieldref           #8.#31         // jvm/constant/pool/IntAndFloatTest.c:F   #6 = Fieldref           #8.#32         // jvm/constant/pool/IntAndFloatTest.d:F   #7 = Fieldref           #8.#33         // jvm/constant/pool/IntAndFloatTest.e:F   #8 = Class              #34            // jvm/constant/pool/IntAndFloatTest   #9 = Class              #35            // java/lang/Object  #10 = Utf8               a  #11 = Utf8               I  #12 = Utf8               ConstantValue  #13 = Integer            10             //常量10  #14 = Utf8               b  #15 = Utf8               c  #16 = Utf8               F  #17 = Utf8               d  #18 = Utf8               e  #19 = Utf8                 #20 = Utf8               ()V  #21 = Utf8               Code  #22 = Utf8               LineNumberTable  #23 = Utf8               LocalVariableTable  #24 = Utf8               this  #25 = Utf8               Ljvm/constant/pool/IntAndFloatTest;  #26 = Utf8               SourceFile  #27 = Utf8               IntAndFloatTest.java  #28 = NameAndType        #19:#20        // "":()V  #29 = NameAndType        #10:#11        // a:I  #30 = NameAndType        #14:#11        // b:I  #31 = NameAndType        #15:#16        // c:F  #32 = NameAndType        #17:#16        // d:F  #33 = NameAndType        #18:#16        // e:F  #34 = Utf8               jvm/constant/pool/IntAndFloatTest  #35 = Utf8               java/lang/Object

    可以看到代码中出现了两次10三次11f,但是常量池中就只有一个10一个11f。从结果上可以看到常量池第#13 个常量池项(cp_info) 就是CONSTANT_Integer_info,值为10;第#4个常量池项(cp_info) 就是CONSTANT_Float_info,值为11f。

    2b5634e8ae094546d309cb85b56a7e36.png

    常量池存储示例

    long、double存储方式

    Java语言规范规定了 long 类型和 double类型的数据类型占用8 个字节的空间,常量池中存储结构如下:

    61bfd5ec6ad45533147788f64142e67d.png

    long、double存储方式

    示例:

    public class LongAndDoubleTest { private long a = -6076574518398440533L; private long b = -6076574518398440533L; private long c = -6076574518398440533L; private double d = 10.1234567890D; private double e = 10.1234567890D; private double f = 10.1234567890D; }
    Constant pool:   #1 = Methodref          #13.#31        // java/lang/Object."":()V   #2 = Long               -6076574518398440533l  //一个   #4 = Fieldref           #12.#32        // jvm/constant/pool/LongAndDoubleTest.a:J   #5 = Fieldref           #12.#33        // jvm/constant/pool/LongAndDoubleTest.b:J   #6 = Fieldref           #12.#34        // jvm/constant/pool/LongAndDoubleTest.c:J   #7 = Double             10.123456789d   //一个   #9 = Fieldref           #12.#35        // jvm/constant/pool/LongAndDoubleTest.d:D  #10 = Fieldref           #12.#36        // jvm/constant/pool/LongAndDoubleTest.e:D  #11 = Fieldref           #12.#37        // jvm/constant/pool/LongAndDoubleTest.f:D  #12 = Class              #38            // jvm/constant/pool/LongAndDoubleTest  #13 = Class              #39            // java/lang/Object  #14 = Utf8               a  #15 = Utf8               J  #16 = Utf8               b  #17 = Utf8               c  #18 = Utf8               d  #19 = Utf8               D  #20 = Utf8               e  #21 = Utf8               f  #22 = Utf8                 #23 = Utf8               ()V  #24 = Utf8               Code  #25 = Utf8               LineNumberTable  #26 = Utf8               LocalVariableTable  #27 = Utf8               this  #28 = Utf8               Ljvm/constant/pool/LongAndDoubleTest;  #29 = Utf8               SourceFile  #30 = Utf8               LongAndDoubleTest.java  #31 = NameAndType        #22:#23        // "":()V  #32 = NameAndType        #14:#15        // a:J  #33 = NameAndType        #16:#15        // b:J  #34 = NameAndType        #17:#15        // c:J  #35 = NameAndType        #18:#19        // d:D  #36 = NameAndType        #20:#19        // e:D  #37 = NameAndType        #21:#19        // f:D  #38 = Utf8               jvm/constant/pool/LongAndDoubleTest  #39 = Utf8               java/lang/Object

    结论和int和float存储结构的类似,多个重复数值,在常量池中只有一个

    b0a72b6328c2ed61c8b49fdd069208da.png

    常量池存储示例

    String存储方式

    一个String需要两个cp_info结构表示:

    b1f2484db5d02b447afd1100da32ae93.png

    CONSTANT_String_info

    一个CONSTANT_String_info对象,其中string_index指向了一个CONSTANT_Utf8_info对象

    ed635214bbd5e346f0e4dfdd815c4129.png

    CONSTANT_Utf8_info

    CONSTANT_Utf8_info对象中,存储了字节数组长度及utf8编码后的字节数组内容

    示例:

    public class StringTest {    private String s1 = "JVM原理";    private String s2 = "JVM原理";    private String s3 = "JVM原理";    private String s4 = "JVM原理";}
    Constant pool:   #1 = Methodref          #8.#23         // java/lang/Object."":()V   #2 = String             #24            // JVM原理 CONSTANT_String_info指向24   #3 = Fieldref           #7.#25         // jvm/constant/pool/StringTest.s1:Ljava/lang/String;   #4 = Fieldref           #7.#26         // jvm/constant/pool/StringTest.s2:Ljava/lang/String;   #5 = Fieldref           #7.#27         // jvm/constant/pool/StringTest.s3:Ljava/lang/String;   #6 = Fieldref           #7.#28         // jvm/constant/pool/StringTest.s4:Ljava/lang/String;   #7 = Class              #29            // jvm/constant/pool/StringTest   #8 = Class              #30            // java/lang/Object   #9 = Utf8               s1  #10 = Utf8               Ljava/lang/String;  #11 = Utf8               s2  #12 = Utf8               s3  #13 = Utf8               s4  #14 = Utf8                 #15 = Utf8               ()V  #16 = Utf8               Code  #17 = Utf8               LineNumberTable  #18 = Utf8               LocalVariableTable  #19 = Utf8               this  #20 = Utf8               Ljvm/constant/pool/StringTest;  #21 = Utf8               SourceFile  #22 = Utf8               StringTest.java  #23 = NameAndType        #14:#15        // "":()V  #24 = Utf8               JVM原理   //CONSTANT_Utf8_info结构  #25 = NameAndType        #9:#10         // s1:Ljava/lang/String;  #26 = NameAndType        #11:#10        // s2:Ljava/lang/String;  #27 = NameAndType        #12:#10        // s3:Ljava/lang/String;  #28 = NameAndType        #13:#10        // s4:Ljava/lang/String;  #29 = Utf8               jvm/constant/pool/StringTest  #30 = Utf8               java/lang/Object

    在面的图中,我们可以看到CONSTANT_String_info结构体位于常量池的第#2个索引位置。而存放"JVM原理" 字符串的 UTF-8编码格式的字节数组被放到CONSTANT_Utf8_info结构体中,该结构体位于常量池的第#24个索引位置。上面的图只是看了个轮廓,让我们再深入地看一下它们的组织:

    f6eb505faaf65d86342549845c1673f3.png

    String类型常量池存储示例

    类在常量池中存储方式

    一个String需要两个cp_info结构表示:

    dddcd18bb18cc83c16bd68dea23c5276.png

    CONSTANT_Class_info

    一个CONSTANT_Class_info对象,其中name_index指向了一个CONSTANT_Utf8_info对象

    ed635214bbd5e346f0e4dfdd815c4129.png

    CONSTANT_Utf8_info

    CONSTANT_Utf8_info对象中,存储了字节数组长度及utf8编码后的类完全限定名的内容

    示例:

    import java.util.Date;public class ClassTest {    private Date date =new Date();}
    Constant pool:   #1 = Methodref          #6.#18         // java/lang/Object."":()V   #2 = Class              #19            // java/util/Date   #3 = Methodref          #2.#18         // java/util/Date."":()V   #4 = Fieldref           #5.#20         // com/jvm/ClassTest.date:Ljava/util/Date;   #5 = Class              #21            // com/jvm/ClassTest   #6 = Class              #22            // java/lang/Object   #7 = Utf8               date   #8 = Utf8               Ljava/util/Date;   #9 = Utf8                 #10 = Utf8               ()V  #11 = Utf8               Code  #12 = Utf8               LineNumberTable  #13 = Utf8               LocalVariableTable  #14 = Utf8               this  #15 = Utf8               Ljvm/constant/pool/ClassTest;  #16 = Utf8               SourceFile  #17 = Utf8               ClassTest.java  #18 = NameAndType        #9:#10         // "":()V  #19 = Utf8               java/util/Date  #20 = NameAndType        #7:#8          // date:Ljava/util/Date;  #21 = Utf8               com/jvm/ClassTest  #22 = Utf8               java/lang/Object

    如上图所示,在ClassTest.class文件的常量池中,共有 3 个CONSTANT_Class_info结构体,分别表示ClassTest 中用到的Class信息。 看其中一个表示com/jvm/ClassTest的CONSTANT_Class_info 结构体。它在常量池中的位置是#5,它的name_index值为#21,它指向了常量池的第21个常量池项,如下所示:

    dffc37f37bc432d3c255942081845558.png

    class存储

    对于某个类而言,其class文件中至少要有两个CONSTANT_Class_info常量池项,用来表示自己的类信息和其父类信息。(除了java.lang.Object类除外,其他的任何类都会默认继承自java.lang.Object)如果类声明实现了某些接口,那么接口的信息也会生成对应的CONSTANT_Class_info常量池项。

    除此之外,如果在类中使用到了其他的类,只有真正使用到了相应的类,JDK编译器才会将类的信息组成CONSTANT_Class_info常量池项放置到常量池中。

    import java.util.Date; public class Other{ private Date date; public Other() { Date da; } }

    上述的Other的类,在JDK将其编译成class文件时,常量池中并没有java.util.Date对应的CONSTANT_Class_info常量池项,为什么呢?

    在Other类中虽然定义了Date类型的两个变量date、da,但是JDK编译的时候,认为你只是声明了“Ljava/util/Date”类型的变量,并没有实际使用到Ljava/util/Date类。将类信息放置到常量池中的目的,是为了在后续的代码中有可能会反复用到它。很显然,JDK在编译Other类的时候,会解析到Date类有没有用到,发现该类在代码中就没有用到过,所以就认为没有必要将它的信息放置到常量池中了。将上述的Other类改写一下,仅使用new Date(),如下所示:

    import java.util.Date;public class Other {    public Other() {        new Date();    }}

    这时候使用javap -v Other.class,可以查看到常量池中有表示java/util/Date的常量池项。

    总结一下:

    1. 对于某个类或接口而言,其自身、父类和继承或实现的接口的信息会被直接组装成CONSTANT_Class_info常量池项放置到常量池中;

    2. 类中或接口中使用到了其他的类,只有在类中实际使用到了该类时,该类的信息才会在常量池中有对应的CONSTANT_Class_info常量池项;

    3. 类中或接口中仅仅定义某种类型的变量,JDK只会将变量的类型描述信息以UTF-8字符串组成CONSTANT_Utf8_info常量池项放置到常量池中,上面在类中的private Date date;JDK编译器只会将表示date的数据类型的“Ljava/util/Date”字符串放置到常量池中。

    哪些字面量会进入常量池中

    结论如下:

    1. final类型的8种基本类型的值会进入常量池。

    2. 非final类型(包括static的)的8种基本类型的值,只有double、float、long的值会进入常量池。

    3. 常量池中包含的字符串类型字面量(双引号引起来的字符串值)。

    测试代码:

    public class ConstantPoolTest {    private int int_num = 110; //无    private char char_num = 'a'; //无    private short short_num = 120; //无    private float float_num = 130.0f; //有 #5    private double double_num = 140.0; //有 #7    private byte byte_num = 111; //无    private long long_num = 3333L; //有 #11    private long long_delay_num;     private boolean boolean_flage = true; //无    public void init() {        this.long_delay_num = 5555L; //有 #15    }}

    测试结果:

    Constant pool:   #1 = Methodref          #19.#47        // java/lang/Object."":()V   #2 = Fieldref           #18.#48        // jvm/constant/pool/ConstantPoolTest.int_num:I   #3 = Fieldref           #18.#49        // jvm/constant/pool/ConstantPoolTest.char_num:C   #4 = Fieldref           #18.#50        // jvm/constant/pool/ConstantPoolTest.short_num:S   #5 = Float              130.0f   #6 = Fieldref           #18.#51        // jvm/constant/pool/ConstantPoolTest.float_num:F   #7 = Double             140.0d   #9 = Fieldref           #18.#52        // jvm/constant/pool/ConstantPoolTest.double_num:D  #10 = Fieldref           #18.#53        // jvm/constant/pool/ConstantPoolTest.byte_num:B  #11 = Long               3333l  #13 = Fieldref           #18.#54        // jvm/constant/pool/ConstantPoolTest.long_num:J  #14 = Fieldref           #18.#55        // jvm/constant/pool/ConstantPoolTest.boolean_flage:Z  #15 = Long               5555l  #17 = Fieldref           #18.#56        // jvm/constant/pool/ConstantPoolTest.long_delay_num:J  #18 = Class              #57            // jvm/constant/pool/ConstantPoolTest  #19 = Class              #58            // java/lang/Object  #20 = Utf8               int_num  #21 = Utf8               I  #22 = Utf8               char_num  #23 = Utf8               C  #24 = Utf8               short_num  #25 = Utf8               S  #26 = Utf8               float_num  #27 = Utf8               F  #28 = Utf8               double_num  #29 = Utf8               D  #30 = Utf8               byte_num  #31 = Utf8               B  #32 = Utf8               long_num  #33 = Utf8               J  #34 = Utf8               long_delay_num  #35 = Utf8               boolean_flage  #36 = Utf8               Z  #37 = Utf8                 #38 = Utf8               ()V  #39 = Utf8               Code  #40 = Utf8               LineNumberTable  #41 = Utf8               LocalVariableTable  #42 = Utf8               this  #43 = Utf8               Ljvm/constant/pool/ConstantPoolTest;  #44 = Utf8               init  #45 = Utf8               SourceFile  #46 = Utf8               ConstantPoolTest.java  #47 = NameAndType        #37:#38        // "":()V  #48 = NameAndType        #20:#21        // int_num:I  #49 = NameAndType        #22:#23        // char_num:C  #50 = NameAndType        #24:#25        // short_num:S  #51 = NameAndType        #26:#27        // float_num:F  #52 = NameAndType        #28:#29        // double_num:D  #53 = NameAndType        #30:#31        // byte_num:B  #54 = NameAndType        #32:#33        // long_num:J  #55 = NameAndType        #35:#36        // boolean_flage:Z  #56 = NameAndType        #34:#33        // long_delay_num:J  #57 = Utf8               jvm/constant/pool/ConstantPoolTest  #58 = Utf8               java/lang/Object
    展开全文
  • C++20都支持虚函数的constexpr了,我打算用三篇读文章讲清楚编译期常量和constexpr这个东西和编译期常量...从编译期常量谈起想要用编译期常量就要首先知道它们是什么,一般出现在哪里运行常量有什么区别,因此我...

    C++20都支持虚函数的constexpr了,我打算用三篇读文章讲清楚编译期常量和constexpr这个东西和编译期常量的关系,即为什么需要他来辅助解决这个问题。最后帮助读者在实际编码过程中能够有意识地去运用他们,这才是终极目标。这篇文章中会讲到隐藏在日常编程中的各种编译期常量,以及他们存在的意义。

    从编译期常量谈起

    想要用编译期常量就要首先知道它们是什么,一般出现在哪里和运行期常量有什么区别,因此我打算用第一篇文章重点分析编译期常量以及使用他们有什么好处。

    编译期常量(Compile-time constants)是C++中相当重要的一部分,整体而言他们有助提高程序的正确性,并提高程序的性能。这篇文章中出现的编译期常量都是在C++11之前就可以使用的,constexpr是C++11的新特性,所以各位不要有心理包袱。

    总有些东西是编译器要求编译期间就要确定的,除了变量的类型外,最频繁出现的地方就是数组、switch的case标签和模板了。

    数组中的编译期常量

    如果我们想要创建一个不是动态分配内存的数组,那么我们就必须给他设定一个size——这个size必须在编译期间就知道,因此静态数组的大小是编译期常量。

    int 

    只有这么做,编译器才能准确地解算出到底要分配给这个数组多少内存。如果这个数组在函数中,数组的内存就会被预留在该函数的栈帧中;如果这个数组是类的一个成员,那么编译器要确定数组的大小以确定这个类成员的大小——无论哪种情况,编译器都要知道这个数组具体的size。

    有些时候我们不用显示得指明数组的大小,我们用字符串或花括号来初始化数组的时候,编译器会实现帮我们数好这个数组的大小。

    int 

    模板中的编译期常量

    除了类型以外,数字也可以作为模板的参数。这些数值变量包括int,long,short,bool,char和弱枚举enum等。

    enum 

    Case labels

    既然编译器在初始化模板的时候必须知道模板的类型,那么这些模板的参数也必须是编译期常量。

    switch语句的分支判断也必须是编译期常量,和上边模板的情况非常类似。

    void 

    使用编译期常量有什么好处

    如果编译期常量的使用方法只有上边呈现的几种,那你大概会感觉有些无聊了。事实上,关于编译期常量我们能做的事情还有许多,他们能帮助我们去实现更高效的程序。

    更安全的程序

    编译期常量能让我们写出更有逻辑的代码——在编译期就体现出逻辑。比如矩阵相乘:

    class 

    我们都知道,两个矩阵相乘,当且仅当左矩阵的列数等于右矩阵的行数,如果不满足这个规则的话,那就完蛋了,所以针对上边矩阵的乘法,我们在函数中要做一些判断:

    Matrix 

    但是如果我们在编译期就知道了矩阵的size,那么我们就可以把上边的判断放在模板中完成——这样的话不同size的矩阵一下子就成了不同类型的变量了。这样我们的矩阵乘法也相应变得简单了一些:

    template 

    在这个例子中,编译器本身就阻止了错误的发生,还有很多其他的例子——更复杂的例子在编译期间使用模板。从C++11后有一堆这样的模板都定义在了标准库STL中,这个之后再说。所以大家不要觉得上边这种做法是脱裤子放屁,相当于我们把运行时的条件判断交给了编译期来做,前提就是矩阵的类型必须是编译期常量。你可能会问,除了像上边直接用常数来实例化矩阵,有没有其他方法来告诉编译器这是个编译期常量呢?请往下看。

    编译优化

    编译器能根据编译期常量来实现各种不同的优化。比如,如果在一个if判断语句中,其中一个条件是编译期常量,编译器知道在这个判断句中一定会走某一条路,那么编译器就会把这个if语句优化掉,留下只会走的那一条路。

    if 

    在上例中,编译器就会直接利用其中某一个cout语句来替换掉整个if代码块——反正运行代码的机器是32还是64位的又不会变。 另一个可以优化的地方在空间优化。总体来说,如果我们的对象利用编译期常数来存储数值,那么我们就不用在这个对象中再占用内存存储这些数。就拿本文之前的例子来举例:

    • someStruct结构中包含一个‘unsigned long’,一个‘char’,和一个‘color’,尽管如此他的实例对象却只占用一个byte左右的空间。
    • 矩阵相乘的时候,我们在矩阵中也没必要花费空间去存储矩阵的行数和列数了。

    结语

    这一篇文章只讲到了编译期常量,为了使编译器在编译期间计算出常量,我们在C++11标准之前和之后都采用了不同的方法去实现它。在第二篇文章中,我会将主要精力放在C++11标准之前的编译期计算的问题,通过展现一系列蹩脚的方法来引出我们的主角——constexpr。

    该系列的第二篇已经出来啦!点赞收藏后走这里哈:

    小天狼星不来客:C++干货系列——从编译期常量谈到constexpr(二)zhuanlan.zhihu.com
    668d468d0fce0255b2715a03500b676b.png

    这篇文章收录在我的专栏中哦:

    C++干货系列zhuanlan.zhihu.com
    f49ba1fcf194d7eb7ba3426f13f6f50e.png

    点个关注,及时获得最新干货。

    展开全文
  • 第一篇文章中,我把主要精力放在了什么是编译期常量,以及编译期常量有什么作用上。这一篇文章中,我将更详细地介绍编译期常量是如何产生的。之所以要把编译期常量了解的这么透彻,是因为他是编译期运算的基础。...
  • 在上面两篇文章我分别介绍了什么是编译期常量以及C++03标准中的编译期运算。这篇文章我将围绕在constexpr这个关键字...此外我们还不能复用代码,无论在哪里我们都要再复制粘贴一遍,不符合我们设计代码的准则。模...
  • 首先通过一张图了解 Java程序的执行流程:我们编写好的Java源代码程序,通过Java...加载完毕到执行过程中,JVM会将程序执行时用到的数据和相关信息存储在运行时数据区(Runtime Data Area),这块区域也就是我们常说...
  • 已经明确的一点是 **字符串常量池、静态变量** jdk7时从方法区移入**Java堆**中,那么运行时常量池呢? 我看了jdk6/7/8三版jvm文档,对运行时常量池的描述都是方法区的一部分 jdk6![图片说明]...
  • 4)字符串常量池和运行时常量池究竟去了哪里?4)元空间是什么?5)关于为什么移除永久代?5)补充         我们知道JDK1.8中取消了永久代,区而代之使用了元空间来实现...
  • 1.java常量池的介绍java中的常量池,通常指的是运行时常量池,它是方法区的一部分,一个jvm实例只有一个运行常量池,各线程间共享该运行常量池。java常量池简介:java常量池中保存了一份编译期间就已确定的数据。...
  • 为了更方便的使用对象,常量池是我们需要了解的必要一环,下面来看看常量的用处...在运行时常量池与静态常量池都会存放元空间中,但字符串常量池依然存放堆中。常量池的用处是?常量池,是指编译期被确定,并被...
  • 常量池在哪里

    2020-07-09 11:27:09
    Java6和6之前,常量池是存放方法区(永久代)中的。 Java7,将常量池是存放到了堆中。 Java8之后,取消了整个永久代区域,取而代之...运行时常量池和静态常量池存放元空间中,而字符串常量池依然存放堆中。 ...
  • 它就是用来存放编译期生成的各种字面量以及符号引用,这部分内容将类加载后,进入方法区的运行时常量池中存放。举个例子 基本数据类型和抽象数据类型的引用会放到哪里呢?我们之前所讲的内存区域,随着这个方法...
  • java虚拟机运行时数据区域的概括图如下所示...(1)因为CPU执行时只能处理一个线程的代码,但是可能有多个线程执行,所以需要记录每个线程执行到哪里,所以每个线程都有一个独立的程序计数器。所以这类区域是 “线...
  • JAVA常量池

    2015-10-12 17:46:09
    JVM为每个已加载的类维护一个常量池。用来保存 基本数据类型,String,对其他类,方法,字段的符号引用。 Java语言中不是这样,一切都是动态的。编译,如果发现对其它类方法的调用或者对其它类字段的引用的语句...
  • 理解归纳方法区和常量池

    千次阅读 2018-05-12 15:31:34
    前言 方法区用于存储已经被虚拟机加载的类信息(class文件是存入方法区的),常量,静态变量。 我对方法区的理解其实是个逻辑区,其是... 常量池:常量池,分为class常量池和运行时常量池,运行时的常量池是属于...
  • JDK1.7 及之后版本的 JVM 已经将运行时常量池从方法区中移了出来, Java 堆(Heap)中开辟了一块区域存放运行时常量池。JDK1.8开始,取消了Java方法区,取而代之的是位于直接内存的元空间(metaSpace)。已知:String A...
  • Constant Pool常量池的概念

    千次阅读 2014-04-15 15:52:23
    Constant Pool常量池的概念 Constant Pool常量池的概念: ...明白Constant Pool到底是个怎么样的东西,运行的时候存储在哪里,所以在这里先说一下Constant Pool的内容. String Pool是对应于在Constant Pool中存
  • 程序运行时,有六个地方都可以保存数据:1、 寄存器:这是最快的保存区域,因为它位于和其他所有保存方式不同的地方:处理器内部。然而,寄存器的数量十分有限,所以寄存器是根据需要由编译器分配。我们对此没有直接...
  • 下面,我们来看下这张图:运行时数据区分为:pc(program counter)寄存器、java 虚拟机栈、堆、方法区、运行时常量池、本地方法栈。其中,运行时常量池属于方法区,下面是各自的详细介绍。pc 寄存器又称:程...
  • JDK8之后-JVM运行时数据区域

    千次阅读 2018-03-05 21:12:40
    1.方法区(method area)只是JVM规范中定义的一个概念,用于存储类信息、常量池、静态变量、JIT编译后的代码等数据,具体放在哪里,不同的实现可以放在不同的地方。永久代是HotSpot虚拟机特有的概念,是对方法区的...
  • 基本数据类型不存在引用,直接保存数值。 局部基本数据类型,在 Stack Frame 创建时,数据...类基本数据类型,初始值也保存于Heap Space所属运行时数据区的常量池中,但是在经过类加载的初始化阶段后被保存在哪里
  • 原标题:一张图读懂jvm之运行时数据区-堆、栈、以及程序计数器你知道 ...下面,我们来看下这张图: 运行时数据区分为:pc(program counter)寄存器、java 虚拟机栈、堆、方法区、运行时常量池、本地方法栈。其中,运...
  • Context QQ上的发文,然后就一顿...class file被load *** 一堆操作,存储jvm的runtime-constant-pool(运行时常量池运行时常量池属于method area(运行时常量池) method area属于heap中的Perm Gen(永久代)...
  • Java方法区存的是什么?方法区堆中吗?最近一直被方法区里面存着什么东西困扰着,为了解开这个...2.class文件常量池和运行时常量池是什么关系。方法区存着类的信息,常量和静态变量,即类被编译后的数据。这个说法...
  • Java PermGen 去哪里了?

    2016-06-15 19:00:20
    Java PermGen 去哪里了?...Java虚拟机(JVM)内部,class文件中包括类的版本、字段、方法、接口等描述信息,还有运行时常量池,用于存放编译器生成的各种字面量和符号引用。 过去(自定义类加载器还不是很常见的时

空空如也

空空如也

1 2 3 4
收藏数 78
精华内容 31
关键字:

运行时常量池在哪里