彻底搞清楚class常量池、运行时常量池、字符串常量池

常量池-静态常量池

也叫 class文件常量池,主要存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References)

  • 字面量:例如文本字符串、fina修饰的常量。
  1. int b = 2;
  2. int c = "abcdefg";
  • 符号引用:例如类和接口的全限定名、字段的名称和描述符、方法的名称和描述符
  1. // 第3部分,常量池信息
  2. Constant pool:

常量池-运行时常量池

  • 当类加载到内存中后,JVM就会将class常量池中的内容存放到运行时常量池中;运行时常量池里面存储的主要是编译期间生成的字面量、符号引用等等。
  • 类加载在链接环节的解析过程,会符号引用转换成直接引用(静态链接)。此处得到的直接引用也是放到运行时常量池中的。
  • 运行期间可以动态放入新的常量。

常量池-字符串常量池

字符串常量池,也可以理解成运行时常量池分出来的一部分。类加载到内存的时候,字符串会存到字符串常量池里面。利用池的概念,避免大量频繁创建字符串。

  • JDK6时字符串常量池位于运行时常量池,JDK7挪到堆中。

Hotspot8之前,使用持久代实现方法区,由于持久代内存不好估算,很容易到值OOM:Perm Gen异常。而元空间是本地内存,取决于操作系统分配内存。

字符串常量池位置变迁

Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池

Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到堆里

Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在堆里

创建字符串操作

  • 字面量赋值
  1. String s = "lzp";

创建字符串对象,存放到字符串常量池中。s指向常量池中对象引用。

  • new String对象
  1. String c = new String("lzp");

new 新字符串对象,会在堆和字符串常量池中都创建对象。

  • intern方法

String中的intern方法是一个 native 的方法,当调用 intern方法时,如果池已经包含一个等于此String对象的字符串(用equals(oject)方法确定),则返回池中的字符串。否则,返回堆中String对象的引用(jdk1.6是将 堆中的String对象 复制到字符串常量池,再返回常量池中的引用)。

  1. String c = new String("lzp");
  2. String d = c.intern();
  3. System.out.println(c == d); // false

c指向堆对象,d指向常量池对象,因此必然不相等。

  1. String s1 = new String("he") + new String("llo");
  2. String s2 = s1.intern(); 
  3. System.out.println(s1 == s2); // true
  4. // 在 JDK 1.6 下输出是 false,创建了 6 个对象

JDK7以后会创建2个字符串常量池对象“he","llo",new 3个堆对象”he","llo","hello",字符串常量池没有hello对象引用。调s1的intern方法,hello指向new出来的hello对象。因此JDK7版本创建了5个对象。s1调intern()方法,返回堆中对象引用。

当然,很多博客中也说字符串常量池中保存的是堆对象的引用,即堆中有5个对象2个he,2个llo,1个hello。字符串常量池底层是hotspot的C++实现的,底层类似一个 HashTable, 保存的本质上是字符串对象的引用

众说纷纭,不好确定。但是两种情况的外在表现是一致的,字符串字面量对象在常量池中。

编译期优化

  1. String a = "awecoder";
  2. String b = "awe" + "coder";
  3. System.out.println(a == b); // true
  4. // 下面的也可以优化
  5. "a" + 1 == "a1"
  6. "a" + 3.4 = "a3.4"

b也是字面量,由于"awe"和"coder"在编译期已确定,JVM编译期将其优化为一个字符串字面量。

  1. String a = "awecoder";
  2. String b = "awe";
  3. final String finalb = "awe";
  4. System.out.println(a == b + "coder"); // false
  5. System.out.println(a == finalb + "coder"); // true

编译期确定不了,例如new对象便不能优化。对于连接符"+"周围是否有变量,能够优化还是取决于变量是否确定。两者底层实现不同,一个是编译期优化成一个字面量,另一个底层使用StringBuilder的append()方法实现(反编译字节码文件可以观察到)。

彻底搞清楚class常量池、运行时常量池、字符串常量池的更多相关文章

  1. java中的编译时常量与运行时常量

    常量是程序运行期间恒定不变的量,许多程序设计语言都有某种方式,向编译器告知一块数据是恒定不变的,例如C++中的const和Java中的final. 根据编译器的不同行为,常量又分为编译时常量和运行时常 ...

  2. EF6 Create Different DataContext on runtime(运行时改变连接字符串)

    引言   在使用EF时,有时我们需要在程序运行过程中动态更改EF的连接字符串,但不幸的时EF是否对 ConfigurationManager.RefreshSection("xxx" ...

  3. 对JVM运行时常量池的一些理解

    1.JVM运行时常量池在内存的方法区中(在jdk8中,移除了方法区) 2.JVM运行时常量池中的内容主要是从各个类型的class文件的常量池中获取,对于字符串常量,可以调用intern方法人为添加,而 ...

  4. JVM详解之:运行时常量池

    目录 简介 class文件中的常量池 运行时常量池 静态常量详解 String常量 数字常量 符号引用详解 String Pool字符串常量池 总结 简介 JVM在运行的时候会对class文件进行加载 ...

  5. 类的加载,链接和初始化——1运行时常量池(来自于java虚拟机规范英文版本+本人的翻译和理解)

    加载(loading):通过一个特定的名字,找到类或接口的二进制表示,并通过这个二进制表示创建一个类或接口的过程. 链接:是获取类或接口并把它结合到JVM的运行时状态中,以让类或接口可以被执行 初始化 ...

  6. Class常量池、运行时常量池、字符串常量池的一些思考

    Class常量池.运行时常量池.字符串常量池 class常量池 java代码经过编译之后都成了xxx.class文件,这是java引以为傲的可移植性的基石.class文件中,在CAFEBABE.主次版 ...

  7. 1.2 - C#语言习惯 - 用运行时常量readonly而不是编译期常量const

    C#中有两种类型的常量:编译期常量和运行时常量.二者有着截然不同的行为,使用不当将会带来性能上或正确性上的问题. 这两个问题最好都不要发生,不过若难以同时避免的话,那么一个略微慢一些但能保证正确的程序 ...

  8. 《C#高效编程》读书笔记02-用运行时常量(readonly)而不是编译期常量(const)

    C#有两种类型的常量:编译期常量和运行时常量.两者有截然不同的行为,使用不当的话,会造成性能问题,如果没法确定,则使用慢点,但能保证正确的运行时常量. 运行时常量使用readonly关键字声明,编译期 ...

  9. JDK1.8-Java虚拟机运行时数据区域和HotSpot虚拟机的内存模型

    目录 介绍 官方文档规定的运行时数据区域 程序计数器 Java虚拟机栈 本地方法栈 虚拟机栈和本地方法栈溢出 Java堆 演示堆内存溢出 方法区 运行时常量池 演示方法区溢出 HotSpot虚拟机的内 ...

随机推荐

  1. MobaXterm远程连接Linux图形用户界面

    目标: 在自己的Windows桌面打开运行在Linux上的firefox浏览器, 使用MobaXterm终端工具在命令行直接打开图像化界面. 工具: Windows: MobaXterm Linux: ...

  2. .NET C#教程初级篇 1-1 基本数据类型及其存储方式

    .NET C# 教程初级篇 1-1 基本数据类型及其存储方式 全文目录 (博客园).NET Core Guide (Github).NET Core Guide 本节内容是对于C#基础类型的存储方式以 ...

  3. spring cloud --- 使用 actuator 热更新【刷新】单机配置文件

    1.前言 分布式微服务想要热更新配置文件,还需要 消息中间件 配合使用 ,一般使用 rabbitMQ 或 Kafka ,这里不解释 . 这篇随笔 只讲解 底层的 单机热更新配置文件 2.环境 spri ...

  4. 使用yum安装php*时报错的解决办法

    # yum -y install php* 注意: php53-odbc64-5.3.3-2.el5.x86_64 from base has depsolving problems  -->  ...

  5. boot项目打包剔除配置文件(打包优化)

    背景: 最近在项目开发中,在本地开发和线上部署的时候总是切换dev和pro环境,项目多了改起来还是很麻烦的,以下记录下boot项目的打包优化,打包的时候剔除配置文件,然后将配置文件手动放到线上,线上项 ...

  6. Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  7. 《剑指offer》面试题24. 反转链表

    问题描述 定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点. 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4-> ...

  8. ecos matlab版本安装

    官网链接 github地址 1.注意不仅要下载matlab版本,同时还要下载c版本,因为matlab版本缺少第三方软件,将两个版本解压缩后将c版本下的文件夹external,ecos_bb,inclu ...

  9. 开发 IDEA Plugin 引入探针,基于字节码插桩获取执行SQL

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 片面了! 一月三舟,托尔斯泰说:"多么伟大的作家,也不过就是在书写自己的片 ...

  10. ctfshow萌新 web1-7

    ctfshow萌新 web1 1.手动注入.需要绕过函数inval,要求id不能大于999且id=1000,所以用'1000'字符代替数字1000 2.找到?id=" "处有回显 ...