java 常量池和运行时常量池_常量池在jvm的哪个部分

java 常量池和运行时常量池_常量池在jvm的哪个部分前言一直在《深入理解JVM》对常量池只有一个浅薄的了解,之前也遇到过这种题目,今天还是要挑出来进行一次全方位的了解。常量池分类常量池大体可以分为:静态常量池,运行时常量池。静态常量池存在于class文件中,比如经常使用的javap-verbose中,常量池总是在最前面把?运行时常量池呢,就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池值的…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

前言

一直在《深入理解JVM》对常量池只有一个浅薄的了解,之前也遇到过这种题目,今天还是要挑出来进行一次全方位的了解。

常量池分类

常量池大体可以分为:静态常量池,运行时常量池。

  • 静态常量池 存在于class文件中,比如经常使用的javap -verbose中,常量池总是在最前面把?

  • 运行时常量池呢,就是在class文件被加载进了内存之后,常量池保存在了方法区中,通常说的常量池 值的是运行时常量池。所以呢,讨论的都是运行时常量池

字符串常量池

最最最流行的、最典型的就是字符串了

典型范例:

String a = "abc";
String b = new String("abc");
System.out.println(a == b);

----*----
结果:false

这里写图片描述
这个是第一个需要理解的地方,a指向哪片内存,b又指向哪片内存呢?对象储存在堆中,这个是不用质疑的,而a作为字面量一开始储存在了class文件中,之后运行期,转存至方法区中。它们两个就不是同一个地方存储的。知道了它之后我们就可以通过实例直接进一步了解了

实例

    String s1 = "Hello";
    String s2 = "Hello";
    String s3 = "Hel" + "lo";
    String s4 = "Hel" + new String("lo");
    String s5 = new String("Hello");
    String s6 = s5.intern();
    String s7 = "H";
    String s8 = "ello";
    String s9 = s7 + s8;

    System.out.println(s1 == s2);  // true
    System.out.println(s1 == s3);  // true
    System.out.println(s1 == s4);  // false
    System.out.println(s1 == s9);  // false
    System.out.println(s4 == s5);  // false
    System.out.println(s1 == s6);  // true

分析:
1、s1 = = s2 很容易可以判断出来。s1 和 s2 都指向了方法区常量池中的Hello。
2、s1 = = s3 这里要注意一下,因为做+号的时候,会进行优化,自动生成Hello赋值给s3,所以也是true
3、s1 = = s4 s4是分别用了常量池中的字符串和存放对象的堆中的字符串,做+的时候会进行动态调用,最后生成的仍然是一个String对象存放在堆中。
这里写图片描述
4、s1 = = s9 在JAVA9中,因为用的是动态调用,所以返回的是一个新的String对象。所以s9和s4,s5这三者都不是指向同一块内存
这里写图片描述
5、s1 = = s6 为啥s1 和 s6地址相等呢? 归功于intern方法,这个方法首先在常量池中查找是否存在一份equal相等的字符串如果有的话就返回该字符串的引用,没有的话就将它加入到字符串常量池中,所以存在于class中的常量池并非固定不变的,可以用intern方法加入新的

需要注意的特例

1、常量拼接

    public static final String a = "123";
    public static final String b = "456";

    public static void main(String[] args)
    {
        String c = "123456";
        String d = a + b;
        System.out.println(c == d);
    }

------反编译结果-------
         0: ldc           #2                  // String 123456
         2: astore_1
         3: ldc           #2                  // String 123456
         5: astore_2
         6: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;

我们可以发现,对于final类型的常量它们已经在编译中被确定下来,自动执行了+号,把它们拼接了起来,所以就相当于直接”123” + “456”;

2、static 静态代码块

    public static final String a;
    public static final String b;

    static {
        a = "123";
        b = "456";
    }

    public static void main(String[] args)
    {
        String c = "123456";
        String d = a + b;
        System.out.println(c == d);
    }

------反编译结果-------
         3: getstatic     #3                  // Field a:Ljava/lang/String;
         6: getstatic     #4                  // Field b:Ljava/lang/String;
         9: invokedynamic #5,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;

上个例子是在编译期间,就已经确定了a和b,但是在这段代码中,编译期static不执行的,a和b的值是未知的,static代码块,在初始化的时候被执行,初始化属于类加载的一部分,属于运行期。看看反编译的结果,很明显使用的是indy指令,动态调用返回String类型对象。一个在堆中一个在方法区常量池中,自然是不一样的。

包装类的常量池技术(缓存)

简单介绍

相信学过java的同学都知道自动装箱和自动拆箱,自动装箱常见的就是valueOf这个方法,自动拆箱就是intValue方法。在它们的源码中有一段神秘的代码值得我们好好看看。除了两个包装类Long和Double 没有实现这个缓存技术,其它的包装类均实现了它。

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

分析:我们可以看到从-128~127的数全部被自动加入到了常量池里面,意味着这个段的数使用的常量值的地址都是一样的。一个简单的实例

Integer i1 = 40;
Integer i2 = 40;
Double i3 = 40.0Double i4 = 40.0;

System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i3=i4 " + (i3 == i4));

-----结果----
true
false

原理如下:
1、== 这个运算在不出现算数运算符的情况下 不会自动拆箱,所以i1 和 i 2它们不是数值进行的比较,仍然是比较地址是否指向同一块内存

2、它们都在常量池中存储着,类似于这样
这里写图片描述

3、编译阶段已经将代码转变成了调用valueOf方法,使用的是常量池,如果超过了范围则创建新的对象

 2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;

复杂实例[-128~127]

  Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);

  System.out.println("i1=i2 " + (i1 == i2));
  System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
  System.out.println("i1=i4 " + (i1 == i4));
  System.out.println("i4=i5 " + (i4 == i5));
  System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
  System.out.println("40=i5+i6 " + (40 == i5 + i6));

----结果----
(1)i1=i2   true
(2)i1=i2+i3   true
(3)i1=i4   false
(4)i4=i5   false
(5)i4=i5+i6   true
(6)40=i5+i6   true

它们的内存分布大概如下
这里写图片描述
注意点
1、当出现运算符的时候,Integer不可能直接用来运算,所以会进行一次拆箱成为基本数字进行比较

2、==这个符号,既可以比较普通基本类型,也可以比较内存地址看比较的是什么了

分析:
(1)号成立不用多说
(2)号成立是因为运算符自动拆箱
(3)(4)号是因为内存地址不同
(5)(6)号都是自动拆箱的结果

PS:equals方法比较的时候不会处理数据之间的转型,比如Double类型和Integer类型。

超过范围

假设一下,如果超出了这个范围之后呢?正如前文所言,所有的都将成为新的对象

  Integer i1 = 400;
  Integer i2 = 400;
  Integer i3 = 0;
  Integer i4 = new Integer(400);
  Integer i5 = new Integer(400);
  Integer i6 = new Integer(0);
  Integer i7 = 1;
  Integer i8 = 2;
  Integer i9 = 3;

  System.out.println("i1=i2 " + (i1 == i2));
  System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
  System.out.println("i1=i4 " + (i1 == i4));
  System.out.println("i4=i5 " + (i4 == i5));
  System.out.println("i4=i5+i6 " + (i4 == i5 + i6));   
  System.out.println("400=i5+i6 " + (400 == i5 + i6));


----结果----
i1=i2   false
i1=i2+i3   true
i1=i4   false
i4=i5   false
i4=i5+i6   true
400=i5+i6   true

总结

关于常量池部分的总结到这里,通过实际的例子和绘图来熟悉了下字符串常量池和包装类的常量池的使用。其中还包括了装箱和拆箱的小知识。收获还是丰厚的,终于明白了常量池的内容了。~happy-。-,如有笔误,还望纠正

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/195427.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • GTSAM中ISAM2和IncrementalFixedLagSmoother说明[通俗易懂]

    GTSAM中ISAM2和IncrementalFixedLagSmoother说明[通俗易懂]GTSAM

    2025年6月22日
    3
  • UML图中类之间的关系:依赖,关联,聚合,组合,泛化,实现

    UML图中类之间的关系:依赖,关联,聚合,组合,泛化,实现类与类图1)类(Class)封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性、操作、关系的对象集合的总称。2)在系统中,每个类具有一定的职责,职责指的是类所担任的任务,即类要完成什么样的功能,要承担什么样的义务。一个类可以有多种职责,设计得好的类一般只有一种职责,在定义类的时候,将类的职责分解成为类的属性和操作(即方法)。3)类的属性即类的数据职责,类的操作即类的行……

    2022年7月17日
    8
  • 启用shift后门的方法_服务器可以拿来干什么

    启用shift后门的方法_服务器可以拿来干什么提权工具如下:cmd.exeChurrasco.exenc.exe提权前提:Wscript组件成功开启如果Wscript组件被关闭,则使用以下方法开启:源代码:&lt;objectrunat=serverid=oScriptlhnscope=pageclassid="clsid:72C24DD5-D70A-438B-8A42-98424B88AFB8"&gt;&lt;/…

    2022年9月18日
    3
  • java发送邮件-模板

    java发送邮件-模板今天写完了一个关于使用模板发送邮件的代码,作为例子保存着,希望以后用得着,也希望能够帮助到需要帮助的人以163网易邮箱为例,使用java发送邮件,发送以邮件时使用模板(.ftl文件转换为html)发送邮件内容,并附带上附件,可抄送给多个人。项目的结构目录如下邮箱配置文件mail.properties参数如下#mailsendersettings#forexample:smtp.1

    2022年5月15日
    32
  • vue分页功能实现_vue登录功能

    vue分页功能实现_vue登录功能vue分页功能

    2022年9月27日
    3
  • JS中的prototype[通俗易懂]

    JS中的prototype[通俗易懂]JS中的phototype是JS中比较难理解的一个部分本文基于下面几个知识点:1原型法设计模式在.Net中可以使用clone()来实现原型法原型法的主要思想是,现在有1个类A,我想要创建一个类B,这

    2022年7月1日
    25

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号