首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >常量池和堆的区别_字符串常量池在堆中还是方法区

常量池和堆的区别_字符串常量池在堆中还是方法区

作者头像
全栈程序员站长
发布于 2022-09-19 01:14:52
发布于 2022-09-19 01:14:52
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话花开堪折直须折,莫待无花空折枝:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事,做自己以后不会留有遗憾的事,做自己觉得有意义的事,不浪费这大好的青春年华。博主写博客目的是记录所学到的知识并方便自己复习,在记录知识的同时获得部分浏览量,得到更多人的认可,满足小小的成就感,同时在写博客的途中结交更多志同道合的朋友,让自己在技术的路上并不孤单。>本篇博客由于比较深入的写进JVM底层,所以如果有错误希望可以指出咱们共同讨论

痛苦对我们来说,究竟意味着什么?司马迁在《报任安书》中一语道破,文王拘而演《周易》,仲尼厄而作《春秋》,屈原放逐乃赋《离骚》,左丘失明厥有《国语》。

目录: 1.常量池与Class常量池1.常量池与Class常量池2.运行时常量池 2.运行时常量池 运行时常量池的简介 运行时常量池的简介方法区的Class文件信息,Class常量池和运行时常量池的三者关系 方法区的Class文件信息,Class常量池和运行时常量池的三者关系3.字符串常量池 3.字符串常量池 字符串常量池的简介 字符串常量池的简介采用字面值的方式创建字符串对象 采用字面值的方式创建字符串对象 采用new关键字新建一个字符串对象 采用new关键字新建一个字符串对象 字符串池的优缺点 字符串池的优缺点4.字符串常量池和运行时常量池之间的藕断丝连 4.字符串常量池和运行时常量池之间的藕断丝连 常量池和字符串常量池的版本变化 常量池和字符串常量池的版本变化String.intern在JDK6和JDK7之后的区别(重难点) String.intern在JDK6和JDK7之后的区别(重难点) 字符串常量池里存放的是引用还是字面量

1.常量池

常量池,也叫 Class 常量池(常量池==Class常量池)。Java文件被编译成 Class文件,Class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项就是常量池,常量池是当Class文件被Java虚拟机加载进来后存放在方法区 各种字面量 (Literal)和 符号引用

在Class文件结构中,最头的4个字节用于 存储魔数 (Magic Number),用于确定一个文件是否能被JVM接受,再接着4个字节用于 存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池常量池主要用于存放两大类常量:字面量和符号引用量,字面量相当于Java语言层面常量的概念,如文本字符串,声明为final的常量值等,符号引用则属于编译原理方面的概念。如下

2.运行时常量池

2.1运行时常量池的简介

运行时常量池是方法区的一部分。运行时常量池是当Class文件被加载到内存后,Java虚拟机会 将Class文件常量池里的内容转移到运行时常量池里(运行时常量池也是每个类都有一个)。运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中

2.2方法区的Class文件信息,Class常量池和运行时常量池的三者关系

字符串常量池

3.1字符串常量池的简介

字符串常量池又称为:字符串池,全局字符串池,英文也叫String Pool。 在工作中,String类是我们使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,这就是我们今天要讨论的核心:字符串常量池。字符串常量池由String类私有的维护

我们理清几个概念:

在JDK7之前字符串常量池是在永久代里边的,但是在JDK7之后,把字符串常量池分进了堆里边。看下面两张图:

在堆中的字符串常量池: **堆里边的字符串常量池存放的是字符串的引用或者字符串(两者都有)**下面例子会有具体的讲解

符号引用表会在下面讲

我们知道,在Java中有两种创建字符串对象的方式:

  1. 采用字面值的方式赋值
  2. 采用new关键字新建一个字符串对象。这两种方式在性能和内存占用方面存在着差别。
3.2采用字面值的方式创建字符串对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package Oneday;
public class a { 
   
    public static void main(String[] args) { 
   
        String str1="aaa";
        String str2="aaa";
        System.out.println(str1==str2);   
    }
}
运行结果:
true

采用字面值的方式创建一个字符串时,JVM首先会去字符串池中查找是否存在”aaa”这个对象,如果不存在,则在字符串池中创建”aaa”这个对象,然后将池中”aaa”这个对象的引用地址返回给字符串常量str,这样str会指向池中”aaa”这个字符串对象;如果存在,则不创建任何对象,直接将池中”aaa”这个对象的地址返回,赋给字符串常量。

对于上述的例子:这是因为,创建字符串对象str2时,字符串池中已经存在”aaa”这个对象,直接把对象”aaa”的引用地址返回给str2,这样str2指向了池中”aaa”这个对象,也就是说str1和str2指向了同一个对象,因此语句System.out.println(str1== str2)输出:true

3.3采用new关键字新建一个字符串对象
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package Oneday;
public class a { 
   
    public static void main(String[] args) { 
   
        String str1=new String("aaa");
        String str2=new String("aaa");
        System.out.println(str1==str2);
    }
}
运行结果:
false

采用new关键字新建一个字符串对象时,JVM首先在字符串常量池中查找有没有”aaa”这个字符串对象,如果有,则不在池中再去创建”aaa”这个对象了,直接在堆中创建一个”aaa”字符串对象,然后将堆中的这个”aaa”对象的地址返回赋给引用str1,这样,str1就指向了堆中创建的这个”aaa”字符串对象;如果没有,则首先在字符串常量池池中创建一个”aaa”字符串对象,然后再在堆中创建一个”aaa”字符串对象,然后将堆中这个”aaa”字符串对象的地址返回赋给str1引用,这样,str1指向了堆中创建的这个”aaa”字符串对象。

对于上述的例子: 因为,采用new关键字创建对象时,每次new出来的都是一个新的对象,也即是说引用str1和str2指向的是两个不同的对象,因此语句 System.out.println(str1 == str2)输出:false

字符串池的实现有一个前提条件:String对象是不可变的。因为这样可以保证多个引用可以同时指向字符串池中的同一个对象。如果字符串是可变的,那么一个引用操作改变了对象的值,对其他引用会有影响,这样显然是不合理的。

3.4字符串池的优缺点

字符串池的优点就是避免了相同内容的字符串的创建,节省了内存,省去了创建相同字符串的时间,同时提升了性能;另一方面,字符串池的缺点就是牺牲了JVM在常量池中遍历对象所需要的时间,不过其时间成本相比而言比较低。

4字符串常量池和运行时常量池之间的藕断丝连

博主为啥要把他俩放在一起讲呢,主要是随着JDK的改朝换代,字符串常量池有很大的变动,和运行时常量池有关。而且网上众说纷纭,我真的在看的时候ctm了,所以博主花很长时间把这一块讲明白,如果有错误或者异议可以通知博主。博主一定会在第一时间参与讨论

4.1常量池和字符串常量池的版本变化
  • 在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
  • 在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说 字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
  • 在JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)
4.2String.intern在JDK6和JDK7之后的区别(重点)

JDK6和JDK7中该方法的功能是一致的,不同的是常量池位置的改变(JDK7将常量池放在了堆空间中),下面会具体说明。intern的方法返回字符串对象的规范表示形式。其中它做的事情是:首先去判断该字符串是否在常量池中存在,如果存在返回常量池中的字符串,如果在字符串常量池中不存在,先在字符串常量池中添加该字符串,然后返回引用地址

例子1:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String s1 = new String("1");
s1.intern();
String s2 = "1";
System.out.println(s1 == s2);

运行结果:
JDK6运行结果:false
JDK7运行结果:false

我们首先看一张图:

上边例子中s1是new出来对象存放的位置的引用,s2是存放在字符串常量池的字符串的引用,所以两者不同

例子2:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String s1 = new String("1");
System.out.println(s1.intern() == s1);

运行结果:
JDK6运行结果:false
JDK7运行结果:false

上边例子中s1是new出来对象存放的位置的引用,s1.intern()返回的是字符串常量池里字符串的引用

例子3:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String s1 = new String("1") + new String("1");
s1.intern();
String s2 = "11";
System.out.println(s1 == s2);

运行结果:
JDK6运行结果:false
JDK7运行结果:true

JDK6中,s1.intern()运行时,首先去常量池查找,发现没有该常量,则在常量池中开辟空间存储”11″,返回常量池中的值(注意这里也没有使用该返回值),第三行中,s2直接指向常量池里边的字符串,所以s1和s2不相等。有可能会有小伙伴问为啥s1.intern()发现没有该常量呢,那是因为:

String s1 = new String(“1”) + new String(“1”);这行代码实际操作是,创建了一个StringBuilder对象,然后一路append,最后toString,而toString其实是又重新new了一个String对象,然后把对象给s1,此时并没有在字符串常量池中添加常量

JDK7中,由于字符串常量池在堆空间中,所以在s1.intern()运行时,发现字符串 常量池没有常量,则添加堆中“11”对象的引用到字符串常量池,这个引用返回堆空间“11”地址(注意这里也没有使用该返回值),这时s2通过查找字符串常量池中的常量,查到的是s1.intern()存在字符串常量池里的“11”对象的引用,既然都是指向堆上的“11”对象,所以s1和s2相等。

例子4:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
String s1 = new String("1") + new String("1");
System.out.println(s1.intern() == s1);

JDK6中,常量池在永久代中,s1.intern()去常量池中查找”11″,发现没有该常量,则在常量池中开辟空间存储”11″,返回常量池中的值,s1指向堆空间地址,所以二者不相等。

JDK7中,常量池在堆空间,s1.intern()去常量池中查找”11″,发现没有该常量,则在字符串常量池中开辟空间,指向堆空间地址,则返回字符串常量池指向的堆空间地址,s1也是堆空间地址,所以二者相等。

另外美团的团队写了一篇关于intern()的博客,我觉得很好可以参考一下 深入解析String#intern

4.3字符串常量池里存放的是引用还是字面量

我在例子3中讲了在JDK7中字符串常量池在堆上,仔细看看例3啥时候会放引用

那么啥时候会放字面量在字符串常量池呢,那就是在我们new一个String对象的时候如果字符串常量池里边有字面量那么就不会放,如果字符串常量池没有就会放字面量。看一个例子:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package Oneday;
import java.util.HashSet;
import java.util.Set;
public class a { 
   
    public static void main(String[] args) { 
   
        String str1= new String("123");
        String str2=new String("123");
        System.out.println(str1==str2);
        System.out.println(str1.intern()==str2.intern());    
    }
}

运行结果:

首先 String str1= new String("123");会在堆中创建一个对象,返回这个对象的引用给str1,同时它还会在字符串常量池中检查有没有有没有123这个对象,如果没有就==再创建一个对象(也就是123这个字面量)==在字符串常量池中

注意这里是创建了两个对象

但是当我们字符串常量池里边有123这个对象,那么就不用继续创建了

上面例子的false那是因为堆中的123对象不是同一个对象,但是第二个str1.intern和s2.intern指的都是字符串常量池里的123对象所以是true

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
面试题系列第6篇:JVM字符串常量池及String的intern方法详解?
关于字符串的比较在前面文章中已经详解过,本篇文章基于字符串常量池的存储及在使用intern方法时所引起的内存变化进行一步深层次的讲解。
程序新视界
2020/09/07
7090
JVM - 深入剖析字符串常量池
看 1.8 , 疯狂的intern, 抛出了 heap oom ,由此可以推断出 1.8中的字符串常量池 是在堆中。
小小工匠
2021/08/17
6760
运行时常量池与字符串常量池_字符串常量池在堆中还是方法区
常量池(Constant Pool),也叫 class 常量池(Class Constant Pool)。
全栈程序员站长
2022/09/19
1.4K0
运行时常量池与字符串常量池_字符串常量池在堆中还是方法区
Java字符串String那些事
众所周知在java里面除了8种基本数据类型的话,还有一种特殊的类型String,这个类型是我们每天搬砖都基本上要使用它。
java金融
2020/12/14
5010
实战:OutOfMemoryError 异常(三) -- 方法区和运行时常量池溢出
运行时常量池溢出,在 OutOfMemoryError 后面跟随的提示信息 是“PermGen space”,说明运行时常量池属于方法区(HotSpot 虚拟机中的永久代)的一部 分。
Li_XiaoJin
2022/06/10
2520
实战:OutOfMemoryError 异常(三) -- 方法区和运行时常量池溢出
String:字符串常量池
作为最基础的引用数据类型,Java 设计者为 String 提供了字符串常量池以提高其性能,那么字符串常量池的具体原理是什么,我们带着以下三个问题,去理解字符串常量池:
全栈程序员站长
2021/06/17
7820
String:字符串常量池
深入探究JVM之内存结构及字符串常量池
Java作为一种平台无关性的语言,其主要依靠于Java虚拟机——JVM,我们写好的代码会被编译成class文件,再由JVM进行加载、解析、执行,而JVM有统一的规范,所以我们不需要像C++那样需要程序员自己关注平台,大大方便了我们的开发。另外,能够运行在JVM上的并只有Java,只要能够编译生成合乎规范的class文件的语言都是可以跑在JVM上的。而作为一名Java开发,JVM是我们必须要学习了解的基础,也是通向高级及更高层次的必修课;但JVM的体系非常庞大,且术语非常多,所以初学者对此非常的头疼。本系列文章就是笔者自己对于JVM的核心知识(内存结构、类加载、对象创建、垃圾回收等)以及性能调优的学习总结,另外未特别指出本系列文章都是基于HotSpot虚拟机进行讲解。
夜勿语
2020/09/07
5190
JDK1.8关于运行时常量池, 字符串常量池的要点[通俗易懂]
网上关于jdk 1.8的各种实验, 结论鱼龙混杂 , 很多都相矛盾,网上有的实验也被后人测试出了不同的结果
全栈程序员站长
2022/09/19
9440
彻底弄懂java中的常量池
class文件是一组以字节为单位的二进制数据流,在java代码的编译期间,我们编写的java文件就被编译为.class文件格式的二进制数据存放在磁盘中,其中就包括class文件常量池。 class文件中存在常量池(非运行时常量池),其在编译阶段就已经确定,jvm规范对class文件结构有着严格的规范,必须符合此规范的class文件才能被jvm任何和装载。为了方便说明,我们写个简单的类
秃头哥编程
2019/06/24
22.9K13
彻底弄懂java中的常量池
常量池之字符串常量池String.intern()
运行时常量池是方法区(PermGen)的一部分。 需要提前了解: 1. JVM内存模型。 2. JAVA对象在JVM中内存分配 常量池的好处 常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。 Java的自动装箱中其实就使用到了运行时常量池。详见:Java 自动装箱与拆箱的实现原理 还有字符串常量池。 字符串进入到常量池的两种方法: 1. new String()的实例调用intern()方法。     执行intern()方法时,若常量池中不存在等值的字符串,JVM就会在常
java404
2018/05/18
1.2K0
字符串常量池理解「建议收藏」
在JVM中,为了减少字符串对象的重复创建,维护了一块特殊的内存空间,这块内存就被称为字符串常量池。
全栈程序员站长
2022/09/19
7160
字符串常量池理解「建议收藏」
JDK字符串存储机制及String#intern方法深入研究
上面的测试代码进行了两组对比,如果你完全能理解执行的结果,那么恭喜你,这篇博客你没必要看了;反之,这篇博客接下来的内容就是你的菜。不过,即使你能答对执行结果,也建议你阅读下本文对常量池及字符串创建的分析,因为关于这块解读的博客确实非常多,但可惜的是,大多说法都是错误的!!!
saintyyu
2021/11/22
3440
彻底弄懂字符串常量池等相关问题
  在平时我们使用字符串一般就是拿来直接搞起,很少有深入的去想过这方面的知识,导致别人在考我们的时候,会问 String str = new String("123"); 这个一行代码执行创建了几个对象, String str1= str + new String("456");这行代码中str1存储在内存的哪个位置,堆or 字符串常量区(方法区)? 会把我们问的哑口无言了;哈哈哈哈,其实也不是水平问题,是我们平时可以仔细的去总结该类问题,下面就详细的对这类问题进行总结;
小勇DW3
2018/08/30
7110
从字符串到常量池,一文看懂String类
这道题就算你没做过也肯定看到,总所周知,它创建了两个对象,一个位于堆上,一个位于常量池中。
cxuan
2020/06/28
1K0
从字符串到常量池,一文看懂String类
Class常量池、运行时常量池、字符串常量池的一些思考
java代码经过编译之后都成了xxx.class文件,这是java引以为傲的可移植性的基石。class文件中,在CAFEBABE、主次版本号之后就是常量池入口了,入口是一个u2类型的数据,也就是占据2个字节,用来给常量池的容量计数,假设这个u2的数字为0x0016,那么对应十进制为22,那么常量池中右21个常量,1-21,其中第0个用于表达“不引用任何一个常量”。在这两个字节之后就是编译器为我们生成的常量了,这些常量包含了两大类:字面量和符号引用,通过一个例子看一下:
huofo
2022/03/18
3700
Class常量池、运行时常量池、字符串常量池的一些思考
彻底弄懂java中的常量池
JVM常量池主要分为Class文件常量池、运行时常量池,全局字符串常量池,以及基本类型包装类对象常量池。
用户8062311
2021/01/10
1.1K0
再议String-字符串常量池与String.intern()
来源:blog.csdn.net/gcoder_/article/details/106644312
Java小咖秀
2021/08/05
3880
再议String-字符串常量池与String.intern()
字符串常量池深入解析[通俗易懂]
在分析字符串常量池之前,先来分析一下java的内存区域,然后再各种的情况分析一下各种情况下的情况;
全栈程序员站长
2022/09/19
4930
深入理解字符串常量池
在JVM中,为了减少字符串对象的重复创建,维护了一块特殊的内存空间,这块内存就被称为字符串常量池。
烟雨星空
2020/06/16
1.4K0
java常量池在方法区还是堆_JAVA常量池
要是没有实践过别人书本上的理论的话,就还是会说常量池在方法区里面,要是知道方法区已经随jdk升级,被逐步干掉的话,额,也不能说被干掉,只是被优化了,这又体现了看书的程度深浅了,就会看到有的文章说常量池移动到heap堆里面了,还有极少的说移动到Metaspace里面了,产生了分歧。这个时候就需要实践出真知了。
全栈程序员站长
2022/09/19
3.1K1
java常量池在方法区还是堆_JAVA常量池
相关推荐
面试题系列第6篇:JVM字符串常量池及String的intern方法详解?
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档