Java 内存—常量池

Java中的常量池分为两种型态:

  • 静态常量池
  • 运行时常量池

静态常量池

所谓静态常量池是指class文件中的常量池,存在于文件中而非内存里面,包括字面量符号引用量

  • 字面量:是Java语言层面的常量的概念,比如字符串和final修饰的常量值。
  • 符号引用量:偏属于编译原理方面的概念,比如类和接口的全限定名,字段名称和描述符,方法名称和描述符。

运行时常量池

运行时常量池是虚拟机在类加载后将class文件中的常量池载入到内存里面,是内存的一部分。

Java语言并不要求常量一定只有在编译期才能产生,也就是并非预置入class文件中的常量池的内容才能进入运行时常量池,运行期间也可以将新的常量放入常量池中,比如Sting类的intern()方法)

运行时常量池引发的"=="比较问题

包装类和常量池

如果把常量池理解为常量的一种贡献方案,缓存到内存里面的数据也可以看做是常量池

final Integer a1 = 20;
final Integer a2 = 20;
System.out.println(a1==a2);//true

final Integer b1 = 200;
final Integer b2 = 200;
System.out.println(b1==b2);//false

final Integer c1 = new Integer(20);
final Integer c2 = new Integer(20);
System.out.println(c1==c2);//false

final Integer d1 = 30;
final int d2 = 30;
System.out.println(d1==d2);//true

final Integer e1 = 200;
final int e2 = 200;
System.out.println(e1==e2);//true

要想理解以上的结果,必须弄清楚三个概念:

  1. “==”运算符比较的是什么
  2. Java的自动装箱和拆箱
  3. 数值缓存
  • ==运算符
    对于==运算符,如果符号两边是引用类型,那么比较的是两个引用对象的地址;如果符号两边是基本类型,那么比较的是值的大小。
  • 自动装箱和拆箱
    自动装箱和拆箱从Java 1.5开始引入,自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。
    自动装箱时编译器调用valueOf将原始类型值转换成对象,自动拆箱时,编译器通过调用类似intValue()这类的方法将对象转换成原始类型值。
  • 数值缓存
    Byte,Short,Integer,Long,Character,Boolean这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。

对于Integer value = 20这样的代码,Java在编译的时候会直接将代码封装成Integer value = Integer.valueOf(20)

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

valueOf()方法可以知道,当创建的Integer对象的值在[IntegerCache.low,IntegerCache.high]之间时,会从IntegerCachecache中获取值,而当创建的Integer对象超过了[IntegerCache.low,IntegerCache.high]这个区域,将会创建一个新的对象。

IntegerCache的实现

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
                sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

IntegerCache是一个内部类,会在使用的时候加载并初始化变量cache的值。

所以对于a变量而言,由于值的范围在[-128,127]之间,所以从cache中获取值,所以a1和a2指向同一片内存地址;对b变量而言,由于数值超出了缓存值的范围,所以会创建一个新的对象,即b1和b2是两个不同的对象;对于c变量而言,由于使用new关键字会创建新的对象,所以c1和c2s是两个不同的对象;对于d变量而言,由于==右边是一个基本类型,所以==左边的包装类型会自动拆箱,==比较的是两个基本类型的大小;对于e变量原理等同于d变量。

字符串类和常量池

由于编译期间,编译器会把字符串常量编译到class文件的常量池中,在解析阶段虚拟机会把class文件中的常量池加载到内存的运行时常量池里面,所以字符串的创建一般有两种方式:

  • 1.是直接从常量池里拿对象的引用
  • 2.在堆里新建一个对象并返回对象的引用

一个比较经典的问题String s1 = new String("xyz")创建了几个对象?

  1. 改行代码在编译期间,编译器发现有一个字符串常量“xyz”,于是便把这个常量放到class文件的常量池里面,在虚拟机解析阶段,虚拟机会创建一个常量放入堆中,并将对象的引用放到运行时常量池,这是第一个阶段,该阶段创建了一个对象
  2. 当程序运行到改行代码的时候,遇见new关键字,会创建一个对象。

所以综上,可以发现该行代码生成了两个对象,一个是在加载类的时候解析阶段,一个是在程序运行的阶段。

String类的intern()方法

String的intern()方法会查找在常量池中是否存在一份equal相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

String类对运算符的重载

唯一被重载的运算符就是字符串的拼接相关的++=,除此之外,Java设计者不允许重载其他的运算符。

  1. 只有使用引号包含文本的方式创建的String对象之间使用“+”连接产生的新对象才会被加入字符串池中。

  2. 对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。

String s1 = "wang";
String s2 = "wang";
System.out.println(s1==s2);//true

String t1 = "shanghai";
String t2 = "shang"+"hai";
System.out.println(t1==t2);//true

String f1 = "shanghai";
String f2 = "shanghai"+new String();
System.out.println(f1==f2);//false

String c1 = "bei";
String c2 = "jing";
String c3 = c1 + c2;
String c4 = "beijing";
System.out.println(c3==c4);//false

final String o1 = "chong";
final String o2 = "qing";
String o3 = o1+o2;
String o4 = "chongqing";
System.out.println(o3==o4);//true
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,699评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,124评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,127评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,342评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,356评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,057评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,654评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,572评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,095评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,205评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,343评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,015评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,704评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,196评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,320评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,690评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,348评论 2 358

推荐阅读更多精彩内容