字符串常量池,看这篇就够了(二)

简介: 哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。

哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。

手撸过JVM、内存池、垃圾回收算法、synchronized、线程池、NIO、三色标记算法…


这篇文章是谈字符串常量池的第二篇。如果上一篇你还没看,建议先回去看一下,再来看本篇


本篇文章的节奏会这样展开:首先会讲到两段Java代码创建的字符串在JVM中的存在形式,把涉及到的相关名词给大家介绍清楚。然后再引申到字符串常量池,即StringTable,详细介绍它的相关机制。最后引出上篇文章讲到的SymbolTable,把这两个Table之间的关系,及它们与常量池缓存之间的关系给大家介绍清楚。

开始……


String类

在讲硬核内容之前,我们先练个基本功:深入理解String类


这里面有两个点需要关注一下:

  1. 存储字符串内容用的是char数组
  2. 构造函数给value赋值用的是等于号,不是调用copy之类的函数,意味着两个String用的是同一个char数组,即两个String对象,字符串内容指向的是同一个

用的同一个char数组,字符串内容指向的是同一个,可能不太好理解,后面会讲到,带着这个疑问往后看吧。

练完了基本功,再来看下一个字符串在JVM中是如何存储的,看图

先不要管这个是什么样的Java代码生成的,后面会详细讲到。我们先来就这个图把相关名词解释一下,不然后面的内容就把诸位劝退了。

一个Java代码级别的字符串,在JVM中会创建两个C++对象:一个是new String对应的oop,还有一个是char数组对应的typeArrayOop。

不理解oop?你得在大脑中切换环境:new String是Java级别的对象,oop就是Java级别的对象在JVM中的存在形式。普通Java对象对应的就是oop对象,引用类型的数组对应的C++对象就是typeArrayOop。瓦特,还不理解?那你可以百度搜:oop klass

这一PA属于铺垫知识,接下来开始硬核干货…


字符串如何存储

看下这两段Java代码创建的字符串在JVM中是如何存储的

String s1 = "ziya"

其实上一PA的图就是这段Java代码创建的字符串在JVM中的存在形式,就不多解释了

JVM怎么知道要这么处理呢?当JVM执行到ldc指令就会这样处理。拓展一点,不细究:ldc指令是在编译时生成的,在link的rewrite阶段会重写为Hotspot特有的fast_ldc指令。这就是为什么有些小伙伴在debug Hotspot源码的时候在ldc指令对应的执行流下段却debug不到的原因。


String s2 = new String("ziya")

上面有说到两个String对象共用一个char数组,看这张图是不是就能理解了

这里面为什么会有两个String对象呢:new创建了一个String对象,ldc创建了一个String对象


这时候就有人要问了:你怎么证明呢?教你一招:IDEA debug窗口,打开这个

然后debug就可以看到JVM中创建了多少对象


为什么相等?为什么不相等?

先看这道面试题

大家先别往下滑,先自己想一想:为什么s1、s2是相等的,s3、s4是不相等的?

这里顺便说下==与equals的区别:==的意思是比较的两个字符串指向的是JVM中的同一个字符串,即内存地址相同。equals是比较的两个字符串的内容是否一样,在JVM中可能是不同的字符串。

我贴一张图,你马上就有结果了

当这四段创建字符串的Java代码执行完,在JVM中创建的对象是这样子的!可能跟你想象的不一样,少创建了很多对象。研究过的小伙伴就猜到了,是因为字符串常量池。


引入字符串常量池以后,创建字符串的流程是这样子的:先根据字符串,比如[ziya],去StringTable中查找有没有,如果有,对于s1、s2,直接返回。对于s3、s4,调用String的构造函数,完成String对象的创建。所以s1、s2相等,new String之类的代码,永远不可能相等!

如果StringTable中没有呢?将String对象保存到字符串常量池中。这里需要注意一下:字符串常量池底层也是hashtable,即用到的数据结构是上一篇说到的数组+链表,它的链表节点不是String对象,而是将String对象封装成HashtableEntry对象。我在图中没有体现这一点,因为图实在画不下了,但是大家要清楚这点。

这目前应该是全网分析字符串常量池最细致的文章不?其实你百度搜到的讲Java字符串的文章,到这个层面的,基本都是我学生写的,因为好像只要我从这个层面教过。^_^


与SymbolTable的关系

接下来说说StringTable与SymbolTable之间的关系

大家是否看到了一个熟悉的身影:intern!

这段代码是做什么呢?去运行时常量池缓存中去找String对象,找到了直接返回;如果没找到,就找字符串对应的Symbol对象,然后调用intern将Symbol对象转成String对象,并封装成HashtableEntry对象写入常量池。

本文篇幅已经够长,intern的底层实现留到下篇吧。SymbolTable与StringTable的细枝末节,都在intern函数中


想一想

顺便带个面试题吧

想想为什么?


瓦特?[子牙真帅]与[子牙真帅]不相等,是因为这句话本身就是谎言?



我是子牙老师,喜欢钻研底层,深入研究Windows、Linux内核、JVM。如果你也喜欢研究底层,欢迎关注我的公众号【硬核子牙】

相关文章
|
3月前
|
存储 Java 程序员
V8垃圾回收?看这篇就够了!
V8垃圾回收?看这篇就够了!
|
4月前
|
安全 C++
面试题:什么是引用?
面试题:什么是引用?
35 0
|
4月前
|
存储 算法 Java
垃圾回收的方法| 青训营笔记
垃圾回收的方法| 青训营笔记
52 0
|
11月前
|
存储 Java
【面试题精讲】字符串常量池的作用了解吗?
【面试题精讲】字符串常量池的作用了解吗?
|
11月前
|
存储 编译器 Linux
C++初阶之一篇文章让你掌握string类(了解和使用)(下)
3.5 string类对象的修改器(Modifiers) 函数名称 功能说明 operator+= 用于字符串的连接 append 在字符串末尾添加字符或字符序列
|
C++
C++ Primer Plus 第十二章答案 类和动态内存分配
只有聪明人才能看见的摘要~( ̄▽ ̄~)~
85 0
|
存储 算法 安全
【重学C/C++系列(三)】:这一次彻底搞懂指针和引用
相信学过C++都知道指针以及引用,C++中使用指针是为了兼容C语言,而使用引用是为了更加贯彻面向对象编程思想,今天小余就来和大家聊聊关于C++中指针以及引用。
【重学C/C++系列(三)】:这一次彻底搞懂指针和引用
|
JavaScript 前端开发 算法
学会JVM,从这篇开始
学会JVM,从这篇开始
149 0
学会JVM,从这篇开始
|
算法 Java Python
pyhon:(垃圾回收机制)超详细——一次搞懂
python采用的是引用计数机制为主,标记-清除和分代收集两种机制为辅的策略
|
存储 算法 安全
字符串常量池,看这篇就够了(一)
哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。
252 1
字符串常量池,看这篇就够了(一)