JVM常量池最全详解-常量池/运行时常量池/字符串常量池/基本类型常量池,看这一篇就够了!

  • 常量池详解
    • 1. 字面量和符号引用
      • 1.1 字面量
      • 1.2 符号引用
    • 2. 常量池vs运行时常量池
    • 3. 常量池(静态常量池)
    • 4. 运行时常量池
    • 5. 字符串常量池
      • 5.1 设计思想
      • 5.2 三种字符串操作(JDK1.7及以上版本)
      • 5.3 字符串常量池位置
      • 5.4 字符串常量池设计原理
      • 5.5 String常量池常见问题
    • 6. 八大基本类型常量池

常量池详解

本文涉及常量池、运行时常量池、字符串常量池、基本类型常量池详解,涉及重点包括:

  • 常量池与运行时常量池的关系,字面量存储位置
  • 字符串常量池的原理,不同jdk版本对比,intern方法详解
  • 基本类型常量池与int类型比较

1. 字面量和符号引用

1.1 字面量

字面量就是指由字母、数字构成的字符串或者数值常量,只可以右值出现,即等号右边的值

1.2 符号引用

符号引用是编译原理中的概念,是相对于直接引用来说的,主要包含以下三类常量

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符
int a =1;
int b = 2;
int c = "abcdefg";
int d = "abcdefg";
上面的a,b就是字段名称,就是一种符号引用,还有String类常量池里的java.lang.String是类的全限定名,
main是方法名称,()是一种UTF8格式的描述符,这些都是符号引用

2. 常量池vs运行时常量池

  • 方法区,内部包含了运行时常量池
  • 字节码文件,内部包含了常量池
  • 要弄清楚方法区,需要理解清楚ClassFile,因为加载类的信息都在方法区
  • 要弄清楚方法区的运行时常量池,需要理解清楚ClassFile中的常量池

3. 常量池(静态常量池)

这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装入内存就变成运行时常量池,对应的符号引用在程序加载(解析过程)中变为直接引用,或运行时会被转变变为被加载到内存区域的代码的直接引用(即动态链接)。

  • 一个有效的字节码文件除了包含类的版本信息、字段、方法以及接口等描述符信息外,还包含一项信息就是常量池表(Constant Pool Table),包含各种字面量和对类型、域、方法的符号引用
  • 为什么需要常量池:
    • 项目一个java源文件中的类、接口,编译后产生一个字节码文件。二java中的字节码文件需要数据支持,通常这种数据会很大以至于不能直接存到字节码这里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态连接的时候也会用到运行时常量池
  • 常量池中有什么
    • 数量值、字符串值、类引用、字段引用、方法引用
  • 常量池可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型

4. 运行时常量池

  • 运行时常量池(Runtime Constant Pool)是方法区的一部分
  • 常量池表是Class文件的一部分,用于存放编译器生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
  • 运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池
  • JVM为每个已加载的类型都维护一个常量池,通过索引访问
  • 运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换成真实地址
  • 运行时常量池,相对于Class文件常量池的另一重要特性:具备动态性
  • 运行时常量池类似于传统编程语言中的符号表,但是它所包含数据却比符号表要更加丰富一些
  • 当创建类或接口的运行时常量池时,如果构造运运行时常量池所需的内存空间超过了方法区所能提供的最大值,则抛KKM

5. 字符串常量池

5.1 设计思想

  • 字符串的分配,和其他对象分配一样,耗费高昂的时间和空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度的影响程序的性能
  • JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
    • 为字符串开辟一个字符串常量池,类似于缓存区
    • 在创建字符串常量时,首先查询字符串常量池是否存在该字符串
    • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中

5.2 三种字符串操作(JDK1.7及以上版本)

  1. 直接赋值字符串
    a. 访问StringTable,有则返回,没有则新建再返回
   String s4 = "a" + "b" + "c";String s5 = "abc";System.out.println(s4 == s5); //true,静态拼接,JVM会自动优化
  1. String s = new String(“test”)
    a. 首先检查StringTable中是否存在test字面量,没有则新建;
    b. 然后在堆中创建一个"test"对象,并返回该堆中的对象地址
 String s = "hello";  String s1 = new String("hello"); //s1创建两个对象,分别位于字符串常量池和堆中,字符串常量池中的对象本次不使用,堆中的对象仅供本次使用String s2 = new String("he") + new String("llo");String s3 = new String("he") + new String("llo");System.out.println(s == s1);   // falseSystem.out.println(s1 == s2); // false,涉及动态拼接,底层调用StringBuilder.append("he").append("llo").toString();System.out.println(s2 == s3);   // false,StringBuilder.append().toString()会new一个String对象

注意:StringBuilder拼接后的toString()方法不会将字符串存入常量池,只会在堆中创建新对象

  1. s.intern方法(native方法)
    a. JDK1.6及以前:StringTable不存在会复制一份,然后返回堆中对象
    b. JDK1.7及以后:StringTable不存在不会复制,直接返回堆中对象

适当地使用intern() 在正常实现功能的情况下,可以有效降低String对象产生量

    public static void test() {String s1 = new String("he") + new String("llo");String s2 = s1.intern();System.out.println(s1 == s2);   // jdk1.7及以后true,jDK1.6及以前false}// StringBuilder拼接后的toString()方法不会将字符串存入常量池,所以上述JDK1.7为true,s1.intern()返回的仍是s1的引用地址public static void test1() {String s = "hello";String s1 = new String("he") + new String("llo");String s2 = s1.intern();System.out.println(s1 == s2);   // jdk1.7及以后false,jDK1.6及以前false}

5.3 字符串常量池位置



5.4 字符串常量池设计原理

字符串常量池底层是hotspot的C++实现的,底层类似于一个HashTable,保存的本质上是字符串对象的引用,看一道常见的面试题

 String s1 = new String("he") + new String("llo");String s2 = s1.intern();System.out.println(s1 == s2);// 在 JDK 1.6 下输出是 false,创建了 6 个对象// 在 JDK 1.7 及以上的版本输出是 true,创建了 5 个对象,原因详见上述intern分析// 当然我们这里没有考虑GC,但这些对象确实存在

5.5 String常量池常见问题

示例1:静态拼接,编译器可确定,均从常量池取值
String s0="hello";
String s1="hello";
String s2="he" + "llo";
System.out.println( s0==s1 ); //true
System.out.println( s0==s2 ); //true示例2:涉及动态拼接,编译器无法确定
String s0="hello";
String s1=new String("hello");
String s2="he" + new String("llo");
System.out.println( s0==s1 ); // false
System.out.println( s0==s2 ); // false
System.out.println( s1==s2 ); // false示例3:静态拼接,编译器可确定,均从常量池取值
String a = "a1";
String b = "a" + 1;
System.out.println(a == b); // true
String a = "atrue";
String b = "a" + "true";
System.out.println(a == b); // true
String a = "a3.4";
String b = "a" + 3.4;
System.out.println(a == b); //true示例4:涉及变量,动态拼接,编译器无法优化,编译器无法确定
String a = "ab";
String bb = "b";
String b = "a" + bb;
System.out.println(a == b); //false示例5:常量值拼接,编译器可确定
String a = "ab";
final String bb = "b";
String b = "a" + bb;
System.out.println(a ==b); //true示例6:调用方法,编译器无法确定,只有程序运行期间调用方法后,将方法的返回值和"a"来动态链接并分配地址为b
String a = "ab";
final String bb = getBB();
String b = "a" + bb;
System.out.println(a == b); // false
private static String getBB()
{return "b";
}示例7:
//字符串常量池:"计算机"和"技术" 堆内存:str1引用的对象"计算机技术"
//堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
String str2 = new StringBuilder("计算机").append("技术").toString(); //没有出现"计算机技术"字面量,所以不会在常量池里生成"计算机技术"对象
System.out.println(str2 == str2.intern()); //true
//"计算机技术" 在池中没有,但是在heap中存在,则intern时,会直接返回该heap中的引用//字符串常量池:"ja"和"va" 堆内存:str1引用的对象"java"
//堆内存中还有个StringBuilder的对象,但是会被gc回收,StringBuilder的toString方法会new String(),这个String才是真正返回的对象引用
String str1 = new StringBuilder("ja").append("va").toString(); //没有出现"java"字面量,所以不会在常量池里生成"java"对象
System.out.println(str1 == str1.intern()); //false
//java是关键字,在JVM初始化的相关类里肯定早就放进字符串常量池了String s1=new String("test");
System.out.println(s1==s1.intern()); //false
//"test"作为字面量,放入了池中,而new时s1指向的是heap中新生成的string对象,s1.intern()指向的是"test"字面量之前在池中生成的字符串对象
String s2=new StringBuilder("abc").toString();
System.out.println(s2==s2.intern()); //false
//同上

6. 八大基本类型常量池

Java中的基本类型的包装类的大部分都实现了常量池技术(严格来说应该叫对象池,在堆上),这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。因为一般这种比较小的数用到的概率相对较。

  • 基本类型常量池示例代码
public class Test {public static void main(String[] args) {//5种整形的包装类Byte,Short,Integer,Long,Character的对象,//在值小于127时可以使用对象池Integer i1 = 127; //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池Integer i2 = 127;System.out.println(i1 == i2);//输出true//值大于127时,不会从对象池中取对象Integer i3 = 128;Integer i4 = 128;System.out.println(i3 == i4);//输出false//用new关键词新生成对象不会使用对象池Integer i5 = new Integer(127);Integer i6 = new Integer(127);System.out.println(i5 == i6);//输出false//Boolean类也实现了对象池技术Boolean bool1 = true;Boolean bool2 = true;System.out.println(bool1 == bool2);//输出true//浮点类型的包装类没有实现对象池技术Double d1 = 1.0;Double d2 = 1.0;System.out.println(d1 == d2);//输出false}
}
  • Integer.valueOf源码实现
    public static Integer valueOf(int i) {if (i >= IntegerCache.low && i <= IntegerCache.high)return IntegerCache.cache[i + (-IntegerCache.low)];return new Integer(i);}
  • IntegerCache实战详解:
public class TestIntegerCache {public static void main(String[] args) {Integer i1 = 127;Integer i2 = 127;Integer i3 = new Integer(127);Integer i4 = Integer.valueOf(127);int i9 = 127;System.out.println(i1 == i2);       // trueSystem.out.println(i1 == i3);       // falseSystem.out.println(i1 == i4);       // true,Integer.valueOf会先从IntegerCache中取值,取不到再新建System.out.println(i9 == i2);       // true,int和Integer比较会自动拆箱System.out.println(i9 == i3);       // trueSystem.out.println(i9 == i4);       // trueSystem.out.println(i3 == i4);       // falseInteger i5 = 127;Integer i6 = 127;Integer i7 = new Integer(127); Integer i8 = Integer.valueOf(127);   System.out.println(i5 == i6);       // trueSystem.out.println(i5 == i7);       // falseSystem.out.println(i5 == i8);       // trueSystem.out.println(i7 == i8);       // false// Integer i5 = 127;走的是Integer.valueOf(127)),所以i5==i6为true,i5==i7为false}
}

JVM常量池最全详解-常量池/运行时常量池/字符串常量池/基本类型常量池,看这一篇就够了相关推荐

  1. python元组元素的提取比_Python 元组数据类型(tuple)详解 [访问元组元素][修改元组元素][删除元组][学习 Python 必备基础知识][看此一篇就够了]...

    您的"关注"和"点赞",是信任,是认可,是支持,是动力...... 如意见相佐,可留言. 本人必将竭尽全力试图做到准确和全面,终其一生进行修改补充更新. 1 元 ...

  2. Linux_进程管理详解《鸟哥的Linux私房菜》学习笔记(极其详细,看完这篇就够了)

    前言 当一个程序被载入到内存中运行,那么在内存中的那个程序就被称为进程(process).进程是操作系统上非常重要的概念, 所有系统上面跑的数据都会以进程的形态存在. 那么系统的进程有哪些状态?不同的 ...

  3. python的常量和变量_python中的常量和变量代码详解

    局部和全局变量: # name='lhf' # def change_name(): # # global name # name='帅了一比' # print('change_name',name) ...

  4. JVM(Java虚拟机)详解(JVM 内存模型、堆、GC、直接内存、性能调优)

    JVM(Java虚拟机) JVM 内存模型 结构图 jdk1.8 结构图(极简) jdk1.8 结构图(简单) JVM(Java虚拟机): 是一个抽象的计算模型. 如同一台真实的机器,它有自己的指令集 ...

  5. 并发编程五:java并发线程池底层原理详解和源码分析

    文章目录 java并发线程池底层原理详解和源码分析 线程和线程池性能对比 Executors创建的三种线程池分析 自定义线程池分析 线程池源码分析 继承关系 ThreadPoolExecutor源码分 ...

  6. JVM堆 栈 方法区详解

    一.栈 每当启用一个线程时,JVM就为他分配一个JAVA栈,栈是以帧为单位保存当前线程的运行状态 栈是由栈帧组成,每当线程调用一个java方法时,JVM就会在该线程对应的栈中压入一个帧 只有在调用一个 ...

  7. JVM内幕:Java虚拟机详解

    这篇文章解释了Java 虚拟机(JVM)的内部架构.下图显示了遵守 Java SE 7 规范的典型的 JVM 核心内部组件. 上图显示的组件分两个章节解释.第一章讨论针对每个线程创建的组件,第二章节讨 ...

  8. java线程池ThreadPoolExecutor类详解

    线程池有哪些状态 1. RUNNING:  接收新的任务,且执行等待队列中的任务 Accept new tasks and process queued tasks  2. SHUTDOWN: 不接收 ...

  9. Linux-shell-完全详解

    Linux-shell-完全详解(1) 一. Shell简介:什么是Shell,Shell命令的两种执行方式1 二. 几种常见的Shell1 三. Shell脚本语言与编译型语言的差异2 四.什么时候 ...

最新文章

  1. (转载)JavaScript一些实用技巧(http://it.chinawin.net/softwaredev/article-261f.html)
  2. 图形学实验之显示一个飞机(C++实现)
  3. Android中使用WebView加载本地html并支持运行JS代码和支持缩放
  4. 【NLP】文本生成?还不快上知识库
  5. java 面板 选择颜色_[代码全屏查看]-java颜色选择器
  6. python3 集合运算_Python 集合与集合运算
  7. Python学习笔记:生成器(Generator)
  8. field list什么意思_时序数据库有什么不一样?
  9. IntelliJ IDEA 优化总结 适用于clion
  10. ExcelToHtmlTable转换算法:将Excel转换成Html表格并展示(项目源码+详细注释+项目截图)...
  11. (译)如何制作一个类似tiny wings的游戏:第一部分
  12. 按值传递时 php必须复制值,PHP开发笔试题及答案(一)
  13. 阶段3 1.Mybatis_09.Mybatis的多表操作_8 mybatis多对多操作-查询角色获取角色下所属用户信息...
  14. Qt4--加密日记本(子例化QMainWindow文本加密解密)
  15. 【樽海鞘算法】基于樽海鞘算法求解多目标问题附matlab代码
  16. 装系统比较好用的PE工具推荐
  17. unity 角色控制器CharaterController脚本
  18. 适合学计算机用的机械键盘,一款好用的机械键盘应该怎么选?看完这篇就明白了...
  19. 高速计数器转RS485Modbus RTU模块IBF150
  20. Android手机模拟器旋转快捷键

热门文章

  1. python----页面自动跳转
  2. 40句话简读Messari万字研报
  3. 【Linux】win10/ubuntu 双系统安装遇到 gnu grub 2.0.3 或者 error symbol ‘grub_calloc‘ not found 的解决方法
  4. 期货的持仓量和交易量(期货中持仓量)
  5. 自己去创建一个编程语言吧(3)
  6. linux下载sra数据库,NCBI SRA数据库使用详解
  7. 宏录制流程-制作工资条
  8. java 数组合并 去重_Java集合与数组去重
  9. 爱对人比爱上人更重要
  10. 几种常见的JSON数据格式化