java中讲的常量池,通常指的是运行时常量池,它是方法区的一部分,一个jvm实例只有一个运行常量池,各线程间共享该运行常量池。

java内存模型中将内存分为堆和栈,其中堆为线程间共享的内存数据区域,栈为线程间私有的内存区域。堆又包括方法区以及非方法区部分,栈包括本地方法栈、虚拟机栈等,如下图所示:

为什么需要常量池

jvm 在栈帧(frame) 中进行操作数和方法的动态链接(link),为了便于链接,jvm 使用常量池来保存跟踪当前类中引用的其他类及其成员变量和成员方法。

每个栈帧(frame)都包含一个运行常量池的引用,这个引用指向当前栈帧需要执行的方法,jvm使用这个引用来进行动态链接。

在 c/c++ 中,编译器将多个编译期编译的文件链接成一个可执行文件或者dll文件,在链接阶段,符号引用被解析为实际地址。java 中这种链接是在程序运行时动态进行的。

常量池探秘

每个 java 文件编译为 class 文件后,都将产生当前类独有的常量池,我们称之为静态常量池。class 文件中的常量池包含两部分:字面值(literal)和符号引用(Symbolic Reference)。其中字面值可以理解为 java 中定义的字符串常量、final 常量等;符号引用指的是一些字符串,这些字符串表示当前类引用的外部类、方法、变量等的引用地址的抽象表示形式,在类被jvm装载并第一次使用这些符号引用时,这些符号引用将会解析为直接引用。符号常量包含:

  • 类和接口的全限定名

  • 字段的名称和描述符

  • 方法的名称和描述符

jvm在进行类装载时,将class文件中常量池部分的常量加载到方法区中,此时方法区中的保存常量的逻辑区域称之为运行时常量区。

使用javap -verbose 命令可以查看class字节码的详细信息,其中包含了编译期确定的静态常量池。

public class StringTest {public static void main(String[] args){String s = new String("abc");String s2 = s.intern();System.out.println(s2 == s);String s3 = (s + s2);System.out.println(s3 == s3.intern());}
}

上述代码javap -verbose后得到(只拿出常量池部分):

major version: 52
Constant pool:#1 = Methodref          #13.#26        // java/lang/Object."<init>":()V#2 = Class              #27            // java/lang/String#3 = String             #28            // abc#4 = Methodref          #2.#29         // java/lang/String."<init>":(Ljava/lang/String;)V#5 = Methodref          #2.#30         // java/lang/String.intern:()Ljava/lang/String;#6 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;#7 = Methodref          #33.#34        // java/io/PrintStream.println:(Z)V#8 = Class              #35            // java/lang/StringBuilder#9 = Methodref          #8.#26         // java/lang/StringBuilder."<init>":()V#10 = Methodref          #8.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#11 = Methodref          #8.#37         // java/lang/StringBuilder.toString:()Ljava/lang/String;#12 = Class              #38            // StringTest#13 = Class              #39            // java/lang/Object#14 = Utf8               <init>#15 = Utf8               ()V#16 = Utf8               Code#17 = Utf8               LineNumberTable#18 = Utf8               main#19 = Utf8               ([Ljava/lang/String;)V#20 = Utf8               StackMapTable#21 = Class              #40            // "[Ljava/lang/String;"#22 = Class              #27            // java/lang/String#23 = Class              #41            // java/io/PrintStream#24 = Utf8               SourceFile#25 = Utf8               StringTest.java#26 = NameAndType        #14:#15        // "<init>":()V#27 = Utf8               java/lang/String#28 = Utf8               abc#29 = NameAndType        #14:#42        // "<init>":(Ljava/lang/String;)V#30 = NameAndType        #43:#44        // intern:()Ljava/lang/String;#31 = Class              #45            // java/lang/System#32 = NameAndType        #46:#47        // out:Ljava/io/PrintStream;#33 = Class              #41            // java/io/PrintStream#34 = NameAndType        #48:#49        // println:(Z)V#35 = Utf8               java/lang/StringBuilder#36 = NameAndType        #50:#51        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#37 = NameAndType        #52:#44        // toString:()Ljava/lang/String;#38 = Utf8               StringTest#39 = Utf8               java/lang/Object#40 = Utf8               [Ljava/lang/String;#41 = Utf8               java/io/PrintStream#42 = Utf8               (Ljava/lang/String;)V#43 = Utf8               intern#44 = Utf8               ()Ljava/lang/String;#45 = Utf8               java/lang/System#46 = Utf8               out#47 = Utf8               Ljava/io/PrintStream;#48 = Utf8               println#49 = Utf8               (Z)V#50 = Utf8               append#51 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;#52 = Utf8               toString

我们可以看到,常量池共包含52个常量。#1 是一个类中方法的符号引用,它由 #13#26 两个utf8编码的字符串构成;#3 是程序中定义的 String 类型的字面值 "abc",它包含指向一个utf8编码字符串 "abc" 的索引 #28

方法的调用、成员变量的访问最终都是通过运行时常量池来查找具体地址的。

String 常量池

运行时常量池有一种 String 类型的常量,即通常我们所说的字符串字面值,所有的字符串字面值组成一个 String 常量表。String常量表并不是一成不变的,程序运行时可以动态添加字符串常量,使用String的intern()可以动态的添加String常量。但

jvm 确保两个在值上完全相等的字符串字面值(即其中包含的字符序列是相同的,使用equals()来判断)指向同一个 String 实例。

如:

String s1 = "abc";String s2 = "abc";System.out.println(s1 == s2); // true

上述代码中的字符串 s1 和 s2 将指向同一个 String 实例。实际上通过查看class文件,我们可以看到,在编译后,静态常量池中已经包含了一个 String 类型的字面值 "abc",程序运行时只是从常量池中获取这个String字面值的引用地址,并赋值给变量 s1 和变量 s2。

Constant pool:#1 = Methodref          #6.#19         // java/lang/Object."<init>":()V#2 = String             #20            // abc······#20 = Utf8               abcpublic static void main(java.lang.String[]);······Code:stack=3, locals=3, args_size=10: ldc           #2                  // String abc2: astore_13: ldc           #2                  // String abc

其中,ldc 表示将一个常量加载到操作数栈。

String 的 intern() 是一个native方法,返回的是一个String对象的标准表示。当调用该方法时,如果运行时常量池中已经存在与之相等(equal())的字符串,则直接返回常量池中的字符串引用,否则将此字符串添加到池中,并返回。

String s1 = "abc";String s2 = new String("abc");System.out.println(s1 == s2);           //返回 falseSystem.out.println(s1.equals(s2));      //返回 trueSystem.out.println(s1 == s2.intern());  //返回 true

上述代码中,虽然 s1 和 s2 中的值是相同的,但是他们指向的并不是同一个对象,但 s2 的标准化表示和s1是同一个 String 对象,都是编译期确定的常量池中的 "abc"。

java 为什么需要常量池相关推荐

  1. 好好说说Java中的常量池之Class常量池

    前言 在Java中,常量池的概念想必很多人都听说过.这也是面试中比较常考的题目之一.在Java有关的面试题中,一般习惯通过String的有关问题来考察面试者对于常量池的知识的理解,几道简单的Strin ...

  2. Java当中的常量池

    本文转载公众号  达叔与他的朋友们 Java当中的常量池 在Java虚拟机jvm中,内存分布为:虚拟机堆,程序计数器,本地方法栈,虚拟机栈,方法区. 程序计数器是jvm执行程序的流水线,是用来存放一些 ...

  3. 好好说说Java中的常量池之Class常量池 1

    转载自   好好说说Java中的常量池之Class常量池 在Java中,常量池的概念想必很多人都听说过.这也是面试中比较常考的题目之一.在Java有关的面试题中,一般习惯通过String的有关问题来考 ...

  4. java 为什么需要常量池 1

    转载自  java 为什么需要常量池 java中讲的常量池,通常指的是运行时常量池,它是方法区的一部分,一个jvm实例只有一个运行常量池,各线程间共享该运行常量池. java内存模型中将内存分为堆和栈 ...

  5. Java中整数常量池的概念

    Java中整数常量池的概念: java中为了提高程序的执行效率,将[-128, 127]之间256个整数所有的包装对象提前创建好了,类加载时就已经创好了,放在了一个方法区的"整数常量池&qu ...

  6. 第46节:Java当中的常量池

    Java当中的常量池 在Java虚拟机jvm中,内存分布为:虚拟机堆,程序计数器,本地方法栈,虚拟机栈,方法区. 程序计数器是jvm执行程序的流水线,是用来存放一些指令的,本地方法栈是jvm操作系统方 ...

  7. stringbuilder调用tostring常量池_彻底弄懂java中的常量池

    作者:tracy_666链接:https://www.jianshu.com/p/55f65dac1b4b JVM常量池主要分为Class文件常量池.运行时常量池,全局字符串常量池,以及基本类型包装类 ...

  8. Java的字符串常量池

    字符串对象创建过程 先看一道面试题 // 这段代码创建了几个对象? String s = new String("卓卓"); 使用new关键字创建对象时,Java虚拟机会先在字符串 ...

  9. java字节码常量池_java字节码常量池处理说明

    1. 根据java的字节码格式说明,常量池中每一项的大小不一样的.运行时,若要通过数组索引获取具体的一项时, 必须要经过一定的处理才能根据数组下标进行处理,具体的实现原理实际上就是空间换时间,可以参考 ...

最新文章

  1. 16个matplotlib绘图实用小技巧!
  2. 2021年中国数据中心行业发展现状与数据需求分析 互联网产业发展带来良好发展机遇...
  3. 问题 D: 二叉树求高度
  4. sql开启mysql远程连接_SQLServer2008设置开启远程连接
  5. 计算机编程老鸟的心得,java入门123——一个老鸟java学习心得.docx
  6. Analyzer报表结果行
  7. java中panel显示不出来_为什么我的JPanel中的某些项目没有显示?
  8. 达梦数据库存储过程调用
  9. ios android 通用字体,教你如何在iOS项目中设置各种字体
  10. 信息学竞赛中的直觉与证明 - 刘汝佳
  11. Pr 音频效果参考:其它
  12. C语言程序确定闰月,怎样计算闰月
  13. 从安装到使用——Odoo常见问题及故障处理
  14. ch341a i2c 安卓_CH341A安卓平板OTG连接成功但读取不到数据
  15. C++——计算x的n次幂
  16. 【Unity实战100例】文件压缩Zip和ZIP文件的解压
  17. 自然语言处理nltk分词断句(2)
  18. 【商业信息】PNP ID注册名单 2019-05-21
  19. 【kimol君的无聊小发明】—用python写图片格式批量处理工具
  20. iCal及iCalendar说明

热门文章

  1. 还分不清 Cookie、Session、Token、JWT?
  2. Spring Boot 实现定时任务的动态增删启停
  3. Netty、Kafka中的零拷贝技术到底有多牛?
  4. 警惕,MyBatis的size()方法竟然有坑!
  5. AAAI 2021 顶会论文开源,OCR方向最火开源项目已超1万 star!
  6. CMU赵越:异常检测的算法、案例和落地
  7. 特征工程(六)lsa和lda
  8. 每天生产45亿词!GPT-3已渗透进300+应用中,网友:边吃边拉
  9. 0基础玩转CV的利器绝了,还送万元礼金,机不可失!
  10. 硬币(计算n分有几种表示法)