深入浅出 java 常量池

本贴最后更新于 2562 天前,其中的信息可能已经沧海桑田

jvm 虚拟内存分布:

程序计数器是 jvm 执行程序的流水线,存放一些跳转指令。
本地方法栈是 jvm 调用操作系统方法所使用的栈。
虚拟机栈是 jvm 执行 java 代码所使用的栈。
方法区存放了一些常量、静态变量、类信息等,可以理解成 class 文件在内存中的存放位置。
虚拟机堆是 jvm 执行 java 代码所使用的堆。

Java 中的常量池,实际上分为两种形态:静态常量池运行时常量池

所谓静态常量池,即 .class 文件中的常量池,class 文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用 class 文件绝大部分空间。这种常量池主要用于存放两大类常量:字面量(Literal)和符号引用量(Symbolic References),字面量相当于 Java 语言层面常量的概念,如文本字符串,声明为 final 的常量值等,符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

  • 类和接口的全限定名
  • 字段名称和描述符
  • 方法名称和描述符

运行时常量池,则是 jvm 虚拟机在完成类装载操作后,将 class 文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。

运行时常量池相对于 CLass 文件常量池的另外一个重要特征是具备动态性,Java 语言并不要求常量一定只有编译期才能产生,也就是并非预置入 CLass 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,这种特性被开发人员利用比较多的就是**String 类的 intern()**方法。

String 的 intern()方法会查找在常量池中是否存在一份 equal 相等的字符串,如果有则返回该字符串的引用,如果没有则添加自己的字符串进入常量池。

常量池的好处

常量池是为了避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
例如字符串常量池,在编译阶段就把所有的字符串文字放到一个常量池中。

(1)节省内存空间:常量池中所有相同的字符串常量被合并,只占用一个空间。
(2)节省运行时间:比较字符串时,==比 equals()快。对于两个引用变量,只用==判断引用是否相等,也就可以判断实际值是否相等。

接下来我们引用一些网络上流行的常量池例子,然后借以讲解。

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

首先说明一点,在 java 中,直接使用==操作符,比较的是两个字符串的引用地址,并不是比较内容,比较内容请用 String.equals()。

  s1 == s2 这个非常好理解,s1、s2 在赋值时,均使用的字符串字面量,说白话点,就是直接把字符串写死,在编译期间,这种字面量会直接放入 class 文件的常量池中,从而实现复用,载入运行时常量池后,s1、s2 指向的是同一个内存地址,所以相等。

  s1 == s3 这个地方有个坑,s3 虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此 String s3 = "Hel" + "lo";在 class 文件中被优化成 String s3 = "Hello",所以 s1 == s3 成立。只有使用引号包含文本的方式创建的 String 对象之间使用“+”连接产生的新对象才会被加入字符串池中。

  s1 == s4 当然不相等,s4 虽然也是拼接出来的,但 new String("lo")这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合字符串不变定理,鬼知道 s4 被分配到哪去了,所以地址肯定不同。对于所有包含 new 方式新建对象(包括 null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。
配上一张简图理清思路:

imagepng

  s1 == s9 也不相等,道理差不多,虽然 s7、s8 在赋值的时候使用的字符串字面量,但是拼接成 s9 的时候,s7、s8 作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,不能在编译期被确定,所以不做优化,只能等到运行时,在堆中创建 s7、s8 拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的 s1 地址相同。

imagepng

  s4 == s5 已经不用解释了,绝对不相等,二者都在堆中,但地址不同。

  s1 == s6 这两个相等完全归功于 intern 方法,s5 在堆中,内容为 Hello ,intern 方法会尝试将 Hello 字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了 Hello 字符串,所以 intern 方法直接返回地址;而 s1 在编译期就已经指向常量池了,因此 s1 和 s6 指向同一地址,相等。

  • 特例 1
public static final String A = "ab"; // 常量A public static final String B = "cd"; // 常量B public static void main(String\[\] args) { String s = A + B; // 将两个常量用+连接对s进行初始化 String t = "abcd"; if (s == t) { System.out.println("s等于t,它们是同一个对象"); } else { System.out.println("s不等于t,它们不是同一个对象"); } } s等于t,它们是同一个对象

  A 和 B 都是常量,值是固定的,因此 s 的值也是固定的,它在类被编译时就已经确定了。也就是说:String s=A+B; 等同于:String s="ab"+"cd";

  • 特例 2
public static final String A; // 常量A public static final String B; // 常量B static { A = "ab"; B = "cd"; } public static void main(String\[\] args) { // 将两个常量用+连接对s进行初始化 String s = A + B; String t = "abcd"; if (s == t) { System.out.println("s等于t,它们是同一个对象"); } else { System.out.println("s不等于t,它们不是同一个对象"); } } s不等于t,它们不是同一个对象

  A 和 B 虽然被定义为常量,但是它们都没有马上被赋值。在运算出 s 的值之前,他们何时被赋值,以及被赋予什么样的值,都是个变数。因此 A 和 B 在被赋值之前,性质类似于一个变量。那么 s 就不能在编译期被确定,而只能在运行时被创建了。

至此,我们可以得出三个非常重要的结论:

**  必须要关注编译期的行为,才能更好的理解常量池。**

**  运行时常量池中的常量,基本来源于各个 class 文件中的常量池。**

**  程序运行时,除非手动向常量池中添加常量(比如调用 intern 方法),否则 jvm 不会自动添加常量到常量池**

  以上所讲仅涉及字符串常量池,实际上还有整型常量池、浮点型常量池(java 中基本类型的包装类的大部分都实现了常量池技术,即 Byte,Short,Integer,Long,Character,Boolean_;_两种浮点数类型的包装类 Float,Double 并没有实现常量池技术) 等等,但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,(Byte,Short,Integer,Long,Character,Boolean)这 5 种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。

  例如在自动装箱时,把 int 变成 Integer 的时候,是有规则的,当你的 int 的值在-128-IntegerCache.high(127) 时,返回的不是一个新 new 出来的 Integer 对象,而是一个已经缓存在堆 中的 Integer 对象,(我们可以这样理解,系统已经把-128 到 127 之 间的 Integer 缓存到一个 Integer 数组中去了,如果你要把一个 int 变成一个 Integer 对象,首先去缓存中找,找到的话直接返回引用给你就 行了,不必再新 new 一个),如果不在-128-IntegerCache.high(127) 时会返回一个新 new 出来的 Integer 对象。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3201 引用 • 8217 回帖
  • 常量池
    1 引用

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
Ethan
从前现在过去不在回来,红红落叶望眼尘世外! 苏州

推荐标签 标签

  • Gzip

    gzip (GNU zip)是 GNU 自由软件的文件压缩程序。我们在 Linux 中经常会用到后缀为 .gz 的文件,它们就是 Gzip 格式的。现今已经成为互联网上使用非常普遍的一种数据压缩格式,或者说一种文件格式。

    9 引用 • 12 回帖 • 179 关注
  • 旅游

    希望你我能在旅途中找到人生的下一站。

    98 引用 • 903 回帖
  • IBM

    IBM(国际商业机器公司)或万国商业机器公司,简称 IBM(International Business Machines Corporation),总公司在纽约州阿蒙克市。1911 年托马斯·沃森创立于美国,是全球最大的信息技术和业务解决方案公司,拥有全球雇员 30 多万人,业务遍及 160 多个国家和地区。

    17 引用 • 53 回帖 • 142 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 656 关注
  • Mobi.css

    Mobi.css is a lightweight, flexible CSS framework that focus on mobile.

    1 引用 • 6 回帖 • 765 关注
  • Outlook
    1 引用 • 5 回帖 • 2 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 18 关注
  • Angular

    AngularAngularJS 的新版本。

    26 引用 • 66 回帖 • 562 关注
  • PostgreSQL

    PostgreSQL 是一款功能强大的企业级数据库系统,在 BSD 开源许可证下发布。

    22 引用 • 22 回帖 • 1 关注
  • HTML

    HTML5 是 HTML 下一个的主要修订版本,现在仍处于发展阶段。广义论及 HTML5 时,实际指的是包括 HTML、CSS 和 JavaScript 在内的一套技术组合。

    108 引用 • 295 回帖 • 2 关注
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 11 关注
  • 人工智能

    人工智能(Artificial Intelligence)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门技术科学。

    115 引用 • 318 回帖 • 1 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 644 关注
  • 百度

    百度(Nasdaq:BIDU)是全球最大的中文搜索引擎、最大的中文网站。2000 年 1 月由李彦宏创立于北京中关村,致力于向人们提供“简单,可依赖”的信息获取方式。“百度”二字源于中国宋朝词人辛弃疾的《青玉案·元夕》词句“众里寻他千百度”,象征着百度对中文信息检索技术的执著追求。

    63 引用 • 785 回帖 • 70 关注
  • Android

    Android 是一种以 Linux 为基础的开放源码操作系统,主要使用于便携设备。2005 年由 Google 收购注资,并拉拢多家制造商组成开放手机联盟开发改良,逐渐扩展到到平板电脑及其他领域上。

    336 引用 • 324 回帖
  • MongoDB

    MongoDB(来自于英文单词“Humongous”,中文含义为“庞大”)是一个基于分布式文件存储的数据库,由 C++ 语言编写。旨在为应用提供可扩展的高性能数据存储解决方案。MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似 JSON 的 BSON 格式,因此可以存储比较复杂的数据类型。

    91 引用 • 59 回帖 • 5 关注
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1708 回帖 • 3 关注
  • jsDelivr

    jsDelivr 是一个开源的 CDN 服务,可为 npm 包、GitHub 仓库提供免费、快速并且可靠的全球 CDN 加速服务。

    5 引用 • 31 回帖 • 108 关注
  • Hexo

    Hexo 是一款快速、简洁且高效的博客框架,使用 Node.js 编写。

    22 引用 • 148 回帖 • 8 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 606 关注
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 394 关注
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    36 引用 • 35 回帖 • 3 关注
  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 190 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2389 回帖 • 2 关注
  • Typecho

    Typecho 是一款博客程序,它在 GPLv2 许可证下发行,基于 PHP 构建,可以运行在各种平台上,支持多种数据库(MySQL、PostgreSQL、SQLite)。

    12 引用 • 67 回帖 • 443 关注
  • FlowUs

    FlowUs.息流 个人及团队的新一代生产力工具。

    让复杂的信息管理更轻松、自由、充满创意。

    1 引用 • 5 关注