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

所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。

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

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

Strings1 = “Hello”; Strings2 = “Hello”; Strings3 = “Hel”+ “lo”; Strings4 = “Hel”+ newString(“lo”); Strings5 = newString( “Hello”); Strings6 = s5.intern(); Strings7 = “H”; Strings8 =“ello”; Strings9 = s7 + s8; System.out.println(s1 == s2); // trueSystem.out.println(s1 == s3);// trueSystem.out.println(s1 == s4); // falseSystem.out.println(s1 == s9); // falseSystem.out.println(s4 == s5); // falseSystem.out.println(s1 == s6); // trueJava学习交流QQ群: 589809992我们一起学Java!

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

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

s1 == s3这个地方有个坑,s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = “Hel” + “lo”;在class文件中被优化成String s3 = “Hello”;,所以s1 == s3成立。

s1 == s4当然不相等,s4虽然也是拼接出来的,但new String(“lo”)这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合字符串不变定理,鬼知道s4被分配到哪去了,所以地址肯定不同。配上一张简图理清思路:

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

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

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

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

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

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

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

以上所讲仅涉及字符串常量池,实际上还有整型常量池、浮点型常量池等等,但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量范围:-128~127,只有这个范围的数字可以用到常量池。

实践

说了这么多理论,接下来让我们触摸一下真正的常量池。

前文提到过,class文件中存在一个静态常量池,这个常量池是由编译器生成的,用来存储java源文件中的字面量(本文仅仅关注字面量),假设我们有如下java代码:

1Strings = “hi”;

为了方便起见,就这么简单,没错!将代码编译成class文件后,用winhex打开二进制格式的class文件。如图:

简单讲解一下class文件的结构,开头的4个字节是class文件魔数,用来标识这是一个class文件,说白话点就是文件头,既:CA FE BA BE。

紧接着4个字节是java的版本号,这里的版本号是34,因为笔者是用jdk8编译的,版本号的高低和jdk版本的高低相对应,高版本可以兼容低版本,但低版本无法执行高版本。所以,如果哪天读者想知道别人的class文件是用什么jdk版本编译的,就可以看这4个字节。

接下来就是常量池入口,入口处用2个字节标识常量池常量数量,本例中数值为00 1A,翻译成十进制是26,也就是有25个常量,其中第0个常量是特殊值,所以只有25个常量。

常量池中存放了各种类型的常量,他们都有自己的类型,并且都有自己的存储规范,本文只关注字符串常量,字符串常量以01开头(1个字节),接着用2个字节记录字符串长度,然后就是字符串实际内容。本例中为:01 00 02 68 69。

接下来再说说运行时常量池,由于运行时常量池在方法区中,我们可以通过jvm参数:-XX:PermSize、-XX:MaxPermSize来设置方法区大小,从而间接限制常量池大小。

假设jvm启动参数为:-XX:PermSize=2M -XX:MaxPermSize=2M,然后运行如下代码:

//保持引用,防止自动垃圾回收List《String》list=newArrayList 《String》(); int i =0; while(true){ //通过intern方法向常量池中手动添加常量list.add( String.valueOf(i ++) .intern()); }

程序立刻会抛出:ExcepTIon in thread “main” java.lang.outOfMemoryError: PermGen space异常。PermGen space正是方法区,足以说明常量池在方法区中。

在jdk8中,移除了方法区,转而用Metaspace区域替代,所以我们需要使用新的jvm参数:-XX:MaxMetaspaceSize=2M,依然运行如上代码,抛出:java.lang.OutOfMemoryError: Metaspace异常。同理说明运行时常量池是划分在Metaspace区域中。具体关于Metaspace区域的知识,请读者自行搜索。

本文所有代码均在jdk7、jdk8下测试通过,其他版本jdk可能会略有差异,请读者自行探索。

参考文献:《深入理解java虚拟机———jvm高级特性与最佳实践》

java静态池_java 常量池静态变量详解相关推荐

  1. java静态多态_Java静态方法不具有多态性详解

    动态绑定机制使得基类的引用能够指向正确的子类对象,从而使得面向基类编程成为可能. 然而动态绑定在以下两种情况会失效. 1.基类方法是private或final修饰的 这个很好理解,因为private说 ...

  2. java闭合数据_java多线程中线程封闭详解

    线程封闭的概念 访问共享变量时,通常要使用同步,所以避免使用同步的方法就是减少共享数据的使用,这种技术就是线程封闭. 实现线程封闭的方法 1:ad-hoc线程封闭 这是完全靠实现者控制的线程封闭,他的 ...

  3. java代码讲解_Java基础系列-代码块详解

    注:本文出自博主 Chloneda:个人博客 | 博客园 | Github | Gitee | 知乎 前言 Java基础系列,尽量采用通俗易懂.循序渐进的方式,让大家真正理解Java基础知识! 代码块 ...

  4. java lombok 视频_Java开发神器Lombok使用详解

    最近正在写SpringBoot系列文章和录制视频教程,每次都要重复写一些Getter/Setter.构造器方法.字符串输出的ToString方法和Equals/HashCode方法等.甚是浪费时间,也 ...

  5. java多线程教程_java 基础教程之多线程详解及简单实例

    java 多线程详解 在这篇文章里,我们关注多线程.多线程是一个复杂的话题,包含了很多内容,这篇文章主要关注线程的基本属性.如何创建线程.线程的状态切换以及线程通信. 线程是操作系统运行的基本单位,它 ...

  6. java 类参数_Java的数据类型和参数传递(详解)

    Java提供的数据类型主要分为两大类:基本数据类型和引用数据类型. Java中的基本数据类型 名称 大小 取值范围 byte型 (字节) 8bit -128-127 (-2^7到2^7-1) shor ...

  7. java linkedlist实例_Java Linkedlist原理及实例详解

    这篇文章主要介绍了Java Linkedlist原理及实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 定义:linkedlist属于链表结构 ...

  8. java observer模式_Java观察者模式(Observer)详解及应用

    Java观察者模式(Observer)详解及应用 (2011-12-15 14:03:30) 标签: 杂谈 Java观察者模式(Observer)详解及应用 由于网站带有弱sns功能,因此需要设计关注 ...

  9. java 链表逆转_java 实现单链表逆转详解及实例代码

    java 实现单链表逆转详解 实例代码: class Node { Node next; String name; public Node(String name) { this.name = nam ...

  10. java迭代例子_Java 迭代器的代码实例详解

    一.摘要 迭代器模式是与集合共生共死的.一般来说,我们只要实现一个容器,就需要同时提供这个容器的迭代器.使用迭代器的好处是:封装容器的内部实现细节,对于不同的集合,可以提供统一的遍历方式,简化客户端的 ...

最新文章

  1. oracle--查看表空间大小以及修改表空间大小
  2. python快乐编程—基础入门-Python零基础快乐学习之旅(K12实战训练)
  3. C/S,B/S的应用的区别
  4. (python的坑,坑的我头晕,下行循环写后根遍历)
  5. 关于C#中将数字转换为指定格式
  6. mysql用binlog恢复数据_利用mysql的binlog恢复数据
  7. 微信暂停新用户注册标志一个时代的结束
  8. FreeEIM 2013 SDK 部分源码
  9. What is Closure
  10. 计量模型 | 固定效应与交互固定效应
  11. PAT乙级-1055 集体照 (25分)
  12. android 简单的exoplayer全景播放器
  13. 【谷歌浏览器实用技巧】谷歌浏览器的大量书签丢失的找回方法,亲测有用!!
  14. 美国音乐学院计算机音乐专业排名2015年,美国音乐学院排名小提琴专业排名大全(本科)...
  15. The server time zone value ‘� й ��� ׼ʱ ��‘ is unrecognized or represents more than one time zone.
  16. Android 和 iOS 开发的寒冬了?
  17. 蓄电池与超级电容混合储能并网matlab simulink仿真模型
  18. 云计算原来是IT界工资最高的
  19. Vue 开发 UI 组件库
  20. 整理的selenium WebDriver提供的8种元素定位方法

热门文章

  1. 登录注册的业务逻辑流程梳理
  2. 《漫画算法2》2021全新进阶版来袭!
  3. 推荐一款能够将爱奇艺qsv、腾讯qlv、优酷kux完美转换成mp4的三合一全能格式转换器
  4. FPGA驱动ESP8266 WiFi模块
  5. PMP考试【6】三点估算法 PERT计划评审技术
  6. 类型多样的建筑场景unity3d模型素材,速来收藏
  7. 决定要收藏,让你白嫖阿里矢量图(iconfont矢量图使用)
  8. CATIA软件常见问题解答
  9. Maven使用 | 多模块下的打包问题
  10. 强制卸载Edge(Chromium内核版本)