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

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


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.0;
Double 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)
全栈程序员-站长的头像全栈程序员-站长

相关推荐

  • pycharm激活码永久[在线序列号]

    pycharm激活码永久[在线序列号],https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月20日
  • JQuery的Ajax跨域请求的

    JQuery的Ajax跨域请求的

    2022年2月2日
  • c语言将时速转换成配速,配速与时速换算(跑步配速和时速换算)

    c语言将时速转换成配速,配速与时速换算(跑步配速和时速换算)时间除以路程,一般时间单位用分,路程单位用千米。比如20分钟跑了3千米,那配速就是20/3=6.66,也就是6分40秒每公里,一般说成640的配速。配速是针对长跑来说的,对短跑和中跑是不采用这个概念的。马拉松运动讲究匀速,在匀速的状态下才能更好地发挥自己的实力。很多跑步爱好者很注意控制速度。他们根.时速的意思是当时跑步的速度,配速是平均每公里耗时几分钟消耗是指跑步消耗的热量相当于每小时跑多少千…

    2022年6月28日
  • 贵金属黄金投资基础知识及技术交易篇「建议收藏」

    贵金属黄金投资基础知识及技术交易篇「建议收藏」贵金属黄金投资基础知识及技术交易篇  一、影响贵金属价格波动的因素  我们通常所说的贵金属主要指黄金和白银,由于它们的属性有很多共同点,所以价格走势基本相同,影响价格的因素也大同小异,皇玛hmcfds贵金属交易平台小编讲讲贵金属交易的基础知识:  1、供需关系:任何商品价格的波动本质因素都是供需关系的变化,贵金属也不例外。如果产量大幅增加就可能导致贵金属价格的下跌,如果投资交易、实物首…

    2022年5月8日
  • 网页幻灯片轮播代码_怎么快速实现对幻灯片的统一修改

    网页幻灯片轮播代码_怎么快速实现对幻灯片的统一修改   NetCMS有两种幻灯片显示方式:Flash幻灯片和轮换幻灯片。Flash幻灯片是通过将图片新闻中的图片合成Flash后再在页面上显示。轮换幻灯片则是使用脚本进行控制(准确地说,是使用VBScript)。   其实,这两种显示形式差不多,只不过Flash幻灯片是通过Flash实现图片的过渡效果,而轮换幻灯片是利用IE提供的Filter属性实现图片过渡效果的。   鉴于轮换幻灯片

    2022年9月30日
  • bfs是什么意思_bfs轮

    bfs是什么意思_bfs轮Rubik 先生在发明了风靡全球的魔方之后,又发明了它的二维版本——魔板。这是一张有 8 个大小相同的格子的魔板:1 2 3 48 7 6 5我们知道魔板的每一个方格都有一种颜色。这 8 种颜色用前 8 个正整数来表示。可以用颜色的序列来表示一种魔板状态,规定从魔板的左上角开始,沿顺时针方向依次取出整数,构成一个颜色序列。对于上图的魔板状态,我们用序列 (1,2,3,4,5,6,7,8) 来表示,这是基本状态。这里提供三种基本操作,分别用大写字母 A,B,C 来表示(可以通过这些操作改变魔板

    2022年8月9日

发表回复

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

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