新闻| 文章| 资讯| 行情| 企业| wap手机版| article文章| 首页|会员中心|保存桌面|手机浏览
普通会员

易达信息科技

R大(RednaxelaFX的说法的原文在知乎的一个问题里(链接:JVM 常量池中存储的是对象还是引用呢? - 知乎 , 回答原文如下

如果您说的确实是runtime constant pool(而不是interned string pool / StringTable之类的其他东西)的话,其中的引用类型常量(例如CONSTANT_String、CONSTANT_Class、CONSTANT_MethodHandle、CONSTANT_MethodType之类都存的是引用,实际的对象还是存在Java heap上的

还有评论区中R大的精彩回答: 

:那如果是字符串常量池呢,jdk1.7的话,是存的对象吗? 答用于管理interned String的那个string pool / StringTable?那个也只是存引用而不是存对象。 问:哦哦,那这个是在哪里规定的?虚拟机规范里面吗? 答虚拟机规范里把存储Java对象的地方定义为Java heap,其它地方是不会存有Java对象的实体的(有的话那根据定于也要算Java heap的一部分:传说中的R大出现了,再问一下StringTable本身又存在哪里呢,有人说是方法区,又有人说是native memory? 答:HotSpot VM的StringTable的本体在native memory里。它持有String对象的引用而不是String对象的本体。被引用的String还是在Java heap里。一直到JDK6,这些被intern的String在permgen里,JDK7开始改为放在普通Java heap里。

## 关于《String s = new String("abc")会产生几个String对象?》的问题(答:两个String对象

String s = new String("abc")创建对象的时候会创建两个String对象而且两个对象都在堆中如下: ①"abc"先在堆中创建一个String对象String("abc")然后把引用存储到字符串常量池stringTable本体中(而不是像大部分网友说的:在字符串常量池中创建对象(解释:如文章前面描述,字符串常量池实现类是stringTable本体,其中存储的是引用,而不是对象) ②然后会再次在堆中创建new一个String("abc")对象。

具体情况可以通过查看.class字节码文件知晓

代码证明

 重点——如果还有疑问,请查看这两篇文章,保证把字符串常量池了解的非常透彻

知乎文章从字符串到常量池,一文看懂String类! - 知乎 (zhihu.com)  当然还可以再更深层次了解一下jvm从HotSpot VM源码看字符串常量池(StringTable)和intern()方法_HD243608836的博客-CSDN博客

## 说一下String.intern()的问题

JDK6 中,将这个字符串对象尝试放入串池。

JDK7 起,将这个字符串对象尝试放入串池。

代码证明

重点——如果还有疑问,请查看这两篇文章,保证把字符串常量池了解的非常透彻

知乎文章从字符串到常量池,一文看懂String类! - 知乎 (zhihu.com)  当然还可以再更深层次了解一下jvm从HotSpot VM源码看字符串常量池(StringTable)和intern()方法_HD243608836的博客-CSDN博客

## 关于《字符串字面量(String literals)何时进入到字符串常量池中?》的问题

(参考链接:Java字符串字面量是何时进入到字符串常量池中的_TomAndersen的博客-CSDN博客

字符串字面量(String literals和其他基本类型的字面量或常量不同并不会在类加载中的解析(resolve 阶段填充并驻留在字符串常量池中而是以特殊的形式存储在 运行时常量池(Run-Time Constant Pool) 中。而是只有当此字符串字面量被调用时(如对其执行ldc字节码指令,将其添加到栈顶,HotSpot VM才会对其进行resolve,为其在字符串常量池中创建对应的String实例

不同jdk版本中,字符串字面量(String literals)的特殊存放形式如下

三个阶段中,字符串字面量(String literals状态如下

  1. 编译期,字符串字面量以"CONSTANT_String_info"+"CONSTANT_Utf8_info"的形式存放在class文件的 常量池(Constant Pool) 中
  2. 类加载之后,字符串字面量以"JVM_CONSTANT_UnresolvedString(JDK1.7)"或者"JVM_CONSTANT_String(JDK1.8)"的形式存放在 运行时常量池(Run-time Constant Pool) 中
  3. 首次使用某个字符串字面量时,字符串字面量以真正的String对象的方式存放在 字符串常量池(String Pool) 中。

示例代码

## 一些其它问题

为什么要有字符串常量池

String为什么是不可变的

至于“String为什么是不可变的?为什么要有字符串常量池?”的详细分析请移步博客

https://blog.csdn.net/HD243608836/article/details/126589892

什么是字面量?什么是符号引用(参考:终于搞懂了 Java 8 的内存结构,再也不纠结方法区和常量池了!_业余草-商业新知

java代码在编译过程中是无法构建引用的,字面量就是在编译时对于数据的一种表示

int a=1; // 这个1便是字面量

String b="iloveu"; // iloveu便是字面量

由于在编译过程中并不知道每个类的地址,因为可能这个类还没有加载,所以如果你在一个类中引用了另一个类,那么你完全无法知道他的内存地址,那怎么办,我们只能用他的类名作为符号引用,在类加载完后再用这个符号引用去获取他的内存地址。

例子:我在com.demo.Solution类中引用了com.test.Quest,那么我会把 com.test.Quest 作为符号引用存到类常量池,等类加载完后,拿着这个引用去方法区找这个类的内存地址。

## 常量值

常量值又称为字面常量,它是通过数据直接表示

按照数据类型分类有如下六种

① 整型常量值 如 123

② 浮点型常量值 如 3.14、3.14F

(这里的实,表示实数。实数定义为与数轴上点相对应的数。实数可以直观地看作有限小数与无限小数

③ 布尔型常量值(boolean 如 true、false

④ 字符常量值(char'a'、'2'、'啊'、' '(回车)、' '(换行)、'''(单引号字符)、'\'(反斜杠字符引号修饰的一个字符或者转义字符(可以是英文字母、数字、标点符号,以及由转义序列来表示的特殊字符)

ps:除了以上所述形式的字符常量值之外,Java 还允许使用一种特殊形式的字符常量值来表示一些难以用一般字符表示的字符,这种特殊形式的字符是以开头的字符序列,称为转义字符。

⑤ 字符串常量值(String"a"、"abc"、"ab "(结尾回车)、"ab\cde"(中间夹杂着反斜杠字符引号修饰的一个或多个字符或者转义字符

⑥ null常量值 null常量只有一个值null,表示对象的引用为空。

## 常量

注意:常量不同于常量值,不要混淆。给常量初始化时赋的值,即常量值。

常量与变量之间的关系

常量Java 语言中,用final修饰变量表示常量值一旦给定就无法改变类成员常量、静态常量、局部常量三种。例如 final int y = 10;变量:有类成员变量、静态变量、局部变量种。例如 int y = 10;

 

二者对比

常量有三种类型成员常量、静态常量、局部常量

public class HelloWorld {

    // 声明并初始化成员常量     final int y = 10;

    // 声明并初始化静态常量     public static final double PI = 3.14;

    public static void main(String[] args) {         // 声明并初始化局部常量         final double x = 3.3;     }

}



除了字符串常量池,Java的基本类型的封装类大部分8大基本数据类型中的6大类型)也都实现了常量池。包括

注意,浮点数据类型没有封装类常量池。

封装类的常量池是在各自内部类中实现的,比如(的内部类)。

## 取值范围

要注意的是,这些封装类常量池是有范围的

所以基本数据类型的包装类之间比较时,尽量使用equals()。 因为范围内的是new一个Integer放在池中,范围内的都可以复用,所以==判断为true。 但是范围外的则都是新new的Integer,所以为false。

## 调用方法(指定valueof()方法

另外强调,注意

必须调用包装类的valueof()方法才能加入封装类常量池。正常new的构造方法不会加入封装类常量池。

查看Integer源代码可以验证这一说法

valueof()方法

正常的构造方法





《深入理解Java虚拟机》书中对方法区(Method Area)存储内容描述如下

它用于存储已被虚拟机加载的类信息、方法信息、常量(final、静态变量(static)、即时编译器编译后的代码缓存(JIT)等。

关于字符串常量池和运行时常量池的位置说明: 

注意:其中“常量(final)”被我划掉了!后面“两个问题”中有解释

## 两个很重要的问题

1. 由final修饰的常量存放在哪里

final 关键字并不影响在内存中的位置与其无关

具体位置请参考下一问题

2. 成员变量、局部变量、类变量分别存储在内存的什么地方?

类变量是用static修饰符修饰, 定义在方法外的变量,随着java进程产生和销毁。

位置 在jdk7之前,静态变量存放于方法区。在jdk7开始,转移存放在中。

成员变量是定义在类中,但是没有static修饰符修饰的变量, 随着类的实例产生和销毁,是类实例的一部分。

位置

由于是实例的一部分,在类初始化的时候,从运行时常量池取出直接引用或者值,与初始化的对象一起放入

局部变量是定义在类的方法中的变量。

位置

在所在方法被调用时放入虚拟机栈的栈帧中,方法执行结束后从虚拟机栈中弹出,所以存放在虚拟机

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

另外,援引一段JavaGuide中的原文:Java 内存区域详解 | JavaGuide 

《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,方法区到底要如何实现那就是虚拟机自己要考虑的事情了。也就是说,在不同的虚拟机实现上,方法区的实现是不同的。

当虚拟机要使用一个类时,它需要读取并解析 Class 文件获取相关信息,再将信息存入到方法区。方法区会存储已被虚拟机加载的 类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据

另外,周志明老师在《深入理解 Java 虚拟机(第 3 版)》 Page 272中原文注意其中类变量指的是static修饰的成员变量!我后面有解释

【JDK 7之前】,HotSpot使用永久代来实现方法区时,实现是完全符合这种逻辑概念的; 【而在JDK 7及之后】类变量则会随着Class对象一起存放在Java堆中

进一步解释一下

Java中的“成员变量”分为两种

两种“成员变量”的存放位置

ps: 注意:“类加载过程”与“创建对象”是两码事——

java在new一个对象的时候



简单总结一下虚拟机规范的内容

  具体详细内容参看:从Java的《jvm虚拟机规范》看HotSpot虚拟机的内存结构和变迁_HD243608836的博客-CSDN博客


 以上,就是我这三天多来对jvm内存研究的心血

企业列表
新闻列表
推荐企业新闻
联系方式
  • 联系人:依依
友情链接
  • 暂无链接
首页 > 新闻中心 > jvm不同版本(jdk6、jdk7、jdk8)之间的class常量池、运行时常量池、字符串常量池与堆、方法区的种种关系
新闻中心
jvm不同版本(jdk6、jdk7、jdk8)之间的class常量池、运行时常量池、字符串常量池与堆、方法区的种种关系
发布时间:2024-11-11        浏览次数:22        返回列表

jvm不同版本(jdk6、jdk7、jdk8)之间的class常量池、运行时常量池、字符串常量池与堆、方法区的种种关系

这几天研究了一下JVM底层原理。其中的内存分配前前后后看了三天,感觉还是没太看透。 先研究到这,做个阶段性的笔记,感兴趣的小伙伴们欢迎大家评论区共同讨论

查阅了各种博客,长篇大论,例证太多,不清晰。本文主要目的精简浓缩一下,感兴趣的去文中参考的原文链接中自行查看吧~



jvm运行加载class文件到内存中(即class常量池 -> 运行时常量池,期间需要到字符串常量池中获取“符号引用 -> 直接引用”的映射关系,然后把符号引用替换为直接引用

jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。

  • ① class常量池:class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。
  • 运行时常量池当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中由此可知,运行时常量池也是每个类都有一个
  • ③ 字符串常量池:经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是我们所说的字符串常量池StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

ps:所以简单来说,运行时常量池就是用来存放class常量池中的内容的。

我们将三者进行一个比较,如图: 



一个类一个class文件,一个class文件中包含一个class常量池,其实就是我们编译后的“.class文件”中【constant pool】属性中的信息

class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。


一个类一个运行时常量池。运行时常量池是每个类/接口的字节码文件中的运行时实现

当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。


英文名即String Constant Pool,又叫做String Pool,或String Literals Pool,或String Intern Pool,还有叫String Table。

字符串常量池,用于存放字符串常量的运行时的对象的引用。 其中所指的字符串常量,可以是编译期在源码中显式的字符串字面量(string literals)在被加载到内存中使用时创建的String字符串对象,也可以是之后在程序运行时创建的String字符串对象。

加载class文件到内存中,经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是我们所说的字符串常量池StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

 ## PS(重点 

字符串常量池的底层实现类是C++的stringTable.cpp类。而stringTable的底层实现为C++语言中的Hashtable(与Java中的HashTable不同,类似于java的HashSet

stringTable没在java内存结构里,它是c++写的,放在native memory的。stringTable里不放对象,它里面放的是对象的引用,而堆里放的才是真正的字符串对象。  可以采用《hashmap中的value存储的是引用》同样的方式来验证,链接:java的hashmap中value存放的是引用_HD243608836的博客-CSDN博客

 底层实现源码实现

  • jdk8 实现类symbolTable.cpp,路径hotspot/src/share/vm/classfile/symbolTable.cpp,openjdk链接:jdk/symbolTable.cpp at jdk8-b120 · openjdk/jdk · GitHub
  • jdk11 实现类stringTable.cpp,路径:/src/hotspot/share/classfile/stringTable.cpp,openjdk链接:jdk/stringTable.cpp at jdk-11+28 · openjdk/jdk · GitHub

重点——如果还有疑问,请查看这两篇文章,保证把字符串常量池了解的非常透彻

知乎文章:从字符串到常量池,一文看懂String类! - 知乎 (zhihu.com)  当然还可以再更深层次了解一下:jvm从HotSpot VM源码看字符串常量池(StringTable)和intern()方法_HD243608836的博客-CSDN博客

## 存放的位置 

  • Java 6 及以前,字符串常量池存放在永久代(永久代是方法区概念在jdk6中的实现)。(jdk6时,堆与方法区隔离,互不干扰。甚至方法区有个官方别名,叫Non-heap
  • Java 7 开始 Oracle 的工程师对字符串池的逻辑做了很大的改变,即将字符串常量池的位置调整到 Java 堆内(补充:同时还有static静态常量等等也一并被从方法区中转移出去放到堆中了)。 这样做的好处是所有的字符串都保存在堆(Heap)中,和其他普通对象一样,这样可以让你在进行调优应用时,仅需要调整堆大小就可以了

## 关于《JVM 字符串常量池中存储的是对象还是引用呢?》这个问题,网上分为两种说法

  • 一种是JavaGuide为主,认为字符串池里既存了引用也存了对象
  • 另一种是以R大为主的认为字符串池存储的只是字符串对象的引用。

有个文章专门归总写了一下,感兴趣的可以去看一下。链接:关于字符串池存储的是引用还是对象的争议_@baseException的博客-CSDN博客_字符串常量池放引用还是对象

不过还好,在我千辛万苦,坚持不懈下这件事终于有头绪了

我辛苦做了什么

用各种搜索引擎搜索各种国内国外论坛、博客。

还翻阅了Oracle官网提供的jdk8的的“JLS3.10.5. String Literals”对字符串字面量的描述的章节和的“2.5.4. Method Area和2.5.5. Run-Time Constant Pool”章节。但是我并没有在官方文档中看到有关“字符串常量池”的相关英文词汇,鄙人不禁怀疑:难道是“社会程序员自创的”?有看到的可以评论区留言一下具体词汇位置,万分感谢

JavaGuide在他自己的github上回答了这个争议性问题

  • 承认了自己在这个问题上的观点是错误的这事还得感谢github网友@popsicle256踊跃的对JavaGuide大佬提出质疑,该问题才得以解决(链接翻到最后就能看见:JVM 垃圾清理 标记-清除算法 常量池移除问题 · Issue #747 · Snailclimb/JavaGuide · GitHub)。
  • 并修改了相关的所有文章(两篇文章链接:JavaGuide/memory-area.md at main · Snailclimb/JavaGuide · GitHub与https://github.com/Snailclimb/JavaGuide/blob/main/docs/java/basis/java-basic-questions-02.md)。
R大说的是对的(字符串池存储的只是字符串对象的引用
JDK版本方法区实现变化
jdk1.6永久代字符串常量池、运行时常量池、静态变量都是在永久代中
jdk1.7永久代字符串常量池和静态变量被移动到了堆当中, 运行时常量池还是在永久代中
jdk1.8元空间 字符串常量池和静态变量(static)仍然在堆当中运行时常量池、类型信息、常量(final、字段、方法都被移动到了元空间