详解JVM常量池、Class常量池、运行时常量池、字符串常量池(心血总结)
写在前面:博主是一位普普通通的19届双非软工在读生,平时最大的爱好就是听听歌,逛逛B站。博主很喜欢的一句话
花开堪折直须折,莫待无花空折枝
:博主的理解是头一次为人,就应该做自己想做的事,做自己不后悔的事,做自己以后不会留有遗憾的事,做自己觉得有意义的事,不浪费这大好的青春年华。博主写博客目的是记录所学到的知识并方便自己复习,在记录知识的同时获得部分浏览量,得到更多人的认可,满足小小的成就感,同时在写博客的途中结交更多志同道合的朋友,让自己在技术的路上并不孤单。>本篇博客由于比较深入的写进JVM底层,所以如果有错误希望可以指出咱们共同讨论
痛苦对我们来说,究竟意味着什么?司马迁在《报任安书》中一语道破,文王拘而演《周易》,仲尼厄而作《春秋》,屈原放逐乃赋《离骚》,左丘失明厥有《国语》。
目录:
1.常量池与Class常量池
2.运行时常量池
运行时常量池的简介
方法区的Class文件信息,Class常量池和运行时常量池的三者关系
3.字符串常量池
字符串常量池的简介
采用字面值的方式创建字符串对象
采用new关键字新建一个字符串对象
字符串池的优缺点
4.字符串常量池和运行时常量池之间的藕断丝连
常量池和字符串常量池的版本变化
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中有两种创建字符串对象的方式:
- 采用字面值的方式赋值
- 采用new关键字新建一个字符串对象。这两种方式在性能和内存占用方面存在着差别。
3.2采用字面值的方式创建字符串对象
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关键字新建一个字符串对象
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:
String s1 = new String("1");
s1.intern();
String s2 = "1";
System.out.println(s1 == s2);运行结果:
JDK6运行结果:false
JDK7运行结果:false
我们首先看一张图:
上边例子中s1是new出来对象存放的位置的引用,s2是存放在字符串常量池的字符串的引用,所以两者不同
例子2:
String s1 = new String("1");
System.out.println(s1.intern() == s1);运行结果:
JDK6运行结果:false
JDK7运行结果:false
上边例子中s1是new出来对象存放的位置的引用,s1.intern()返回的是字符串常量池里字符串的引用
例子3:
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:
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对象的时候如果字符串常量池里边有字面量那么就不会放,如果字符串常量池没有就会放字面量。看一个例子:
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
详解JVM常量池、Class常量池、运行时常量池、字符串常量池(心血总结)相关推荐
- Java虚拟机详解----JVM常见问题总结
[正文] 声明:本文只是做一个总结,有关jvm的详细知识可以参考本人之前的系列文章,尤其是那篇:Java虚拟机详解04----GC算法和种类.那篇文章和本文是面试时的重点. 面试必问关键词:JVM垃圾 ...
- 详解JVM类加载机制
详解JVM类加载机制 笔者的笔记都记录在有道云里面,因为公司原因办公电脑无法使用有道云,正好借此机会整理下以前的笔记顺便当做巩固复习了,也因为记笔记的时候不会记录这些知识来源何地,所以如果发现原创后可 ...
- Java开发常见面试题详解(JVM)_2
Java开发常见面试题详解(JVM)_2 JVM 问题 详解 JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots link 你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认 ...
- 详解JVM内存管理与垃圾回收机制2 - 何为垃圾
随着编程语言的发展,GC的功能不断增强,性能也不断提高,作为语言背后的无名英雄,GC离我们的工作似乎越来越远.作为Java程序员,对这一点也许会有更深的体会,我们不需要了解太多与GC相关的知识,就能很 ...
- 详解JVM内存管理与垃圾回收机制5 - Java中的4种引用类型
在Java语言中,除了基础数据类型的变量以外,其他的都是引用类型,指向各种不同的对象.在前文我们也已经知道,Java中的引用可以是认为对指针的封装,这个指针中存储的值代表的是另外一块内存的起始地址(对 ...
- 详解JVM和GC垃圾回收
文章目录 JVM架构图分析 Java虚拟机运行时数据区 对象的创建方式有几种? 创建对象的过程 对象的访问定位 垃圾回收机制 如何判断一个对象是否可以回收 垃圾回收算法都有哪些? 对象分代 垃圾回收器 ...
- java 标量替换_详解jvm中的标量替换
概述 通常在java中创建一个对象,大家都认为是在堆中创建. 在jdk6开始有逃逸分析,标量替换等技术,关于在堆中创建对象不再绝对. 关于标量替换,通过以下几点进行概述: 逃逸分析 标量替换是什么 测 ...
- 帧中继和路由协议详解-在帧中继多点子接口上运行EIGRP
上一篇我们介绍了<帧中继和路由协议详解-在帧中继点到点子接口上运行EIGRP>,本篇我们开始介绍<在帧中继多点子接口上运行EIGRP>的配 置.相关概念,如子接口.IARP.D ...
- 深度 | 详解可视化利器t-SNE算法:数无形时少直觉
T 分布随机近邻嵌入(T-Distribution Stochastic Neighbour Embedding)是一种用于降维的机器学习方法,它能帮我们识别相关联的模式.t-SNE 主要的优势就是保 ...
最新文章
- R语言主成分分析PCA和因子分析EFA、主成分(因子)个数、主成分(因子)得分、主成分(因子)旋转(正交旋转、斜交旋转)、主成分(因子)解释
- R假设检验之Grubbs异常检测(Grubbs’ Test)
- intellij 快捷键整理
- OpenCV视频加速Video acceleration的实例(附完整代码)
- Tomcat的web项目部署方式
- java peer_Java PeerConnection.getStats方法代码示例
- oracle表压缩比,oracle的compress 特性介绍
- FTP and Firewalls
- mysql sql语句集合
- C#调用java类、jar包方法
- robots笔记以免忘记
- 从SQL到NoSQL—如何使用表格存储
- Sk32k144:生成hex文件和烧写(jflash)
- 步步为赢,做好数据分析的7个步骤
- RASP-监控应用的底层,来从根本上发现攻击行为的产生
- 【数据结构-源码分析】HashMap源码分析(超级详细)
- Android shape画一个圆角虚线框
- MATLAB——新建、删除或移动文件夹
- 找规律万能公式_数字规律题有万能求解公式吗,只要能找出一种规律就行...?
- 19美亚个人赛复盘2(手机取证)
热门文章
- 下列python语言、返回结果不是uc_智慧职教云课堂Python程序设计基础(九江职业技术学院)考试答案...
- 2022年云计算的趋势有哪些
- 【推荐谷歌地球替代软件——图新地球简介(LocaSpace Viewer)】
- 深度学习有哪些实用又好玩的应用?
- 黑鹰s耳机可以听到自己的声音
- 工业电柜空调 工业电柜空调Eco模式 拓展温度冷热板 机架式循环冷却装置 水冷式冷却器 热交换与冷藏 热电冷却 热电冷却器 热电冷板 热电外壳冷却器 热电板冷却器 热电空调 电热冷却台 电热电柜空调
- Virbox 编译器,支持全平台全架构的源代码加密
- java中调用js代码
- Matlab入门 (2 )编程基础
- at89s51数码管秒表c语言程序,基于AT89C51单片机的一个2位的LED数码显示作为“秒表”设计【基于单片机系统的00-99s的定时器】...