美文网首页技术栈
Java 内存—常量池

Java 内存—常量池

作者: 烟雨乱平生 | 来源:发表于2019-10-17 18:13 被阅读0次

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

相关文章

  • java基础知识

    java 内存区域、JMM、JAVA线程模型、硬件内存模型 java内存区域分为共享区域(堆、方法区常量池)、私有...

  • Java 内存—常量池

    Java中的常量池分为两种型态: 静态常量池 运行时常量池 静态常量池 所谓静态常量池是指class文件中的常量池...

  • Java--String类和常量池

      在Java的内存分析中,我们会经常听到关于“常量池”的描述,实际上常量池也分了以下三种:1.全局字符串常量池(...

  • java基础类型、String类理解、版本对比、1.8新特性

    1、java基本数据类型及长度 2、jvm的常量池: JVM常量池浅析Java常量池理解与总结 Java中的常量池...

  • 【String类】对象内存分配详解

    关于内存分区参见:Java内存详解 - 内存分区 本文重点描述String对象创建时的内存分配方式。 字符串常量池...

  • 细说Java常量池

      Java中的常量池有:class常量池、运行时常量池、String常量池。 为什么要使用常量池?   避免频繁...

  • JVM入门

    Java虚拟机内存包括: 程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、运行时常量池、直接内存。 ...

  • java__常量池

    java的常量池分为两种型态:静态常量池和运行常量池 静态常量池: 即class文件中的常量池,这种常量池主要用于...

  • 常量池、运行时常量池、字符串常量池

    常量池、运行时常量池、字符串常量池 Java里包含各种常量池,经常傻傻分不清楚,下面就简单梳理下Java中的池们。...

  • JVM(六)JVM常量池

    1.常量池类型 Java中的常量池分为三种: 类文件常量池(静态常量池)(The Constant Pool)运行...

网友评论

    本文标题:Java 内存—常量池

    本文链接:https://www.haomeiwen.com/subject/pgbrmctx.html