【JVM】剖析字符串与数组的底层实现(一)

ops/2024/11/9 16:58:26/

剖析字符串与数组的底层实现

字符数组的存储方式

在这里插入图片描述
在这里插入图片描述

JVM有三种模型:

  • 1.Oop模型:Java对象对应的C++对象
  • 2.Klass模型:Java类在JVM对应的C++对象
  • 3.handle模型

字符串常量池

在这里插入图片描述

String Pool,但是JVM中对应的类是StringTable,底层实现是一个hashtable,如代码所示

JVM有三种常量池:

  • 1.静态常量池(通过字节码方式查到地引用都是间接引用)
  • 2.运行时常量池
  • 3.字符串常量池->StringTable
    key生成规则->String内容+长度生成哈希值,然后将hash值取模转为key
    value生成规则->将Java地String类的实例InstanceOopDesc封装成HashtableEntry

字符串常量池位置

JDK1.6及之前:有永久代,运行时常量池在永久代,运行时常量池包含字符串常量池,Perm区域只有4m,一旦常量池大量使用intern很容易发生永久代的OOM
JDK1.7:有永久代,但已经逐步"去永久代",字符串常量池从永久代里的运行时常量池分离到堆里
JDK1.8及之后,无永久代,运行时常量池在元空间,字符串常量池依然在堆里

字符串常量池的设计思想:

  • 1.字符串的分配,和其他的对象分配一样,耗费高昂的时间与空间代价,作为最基础的数据类型,大量频繁的创建字符串,极大程度地影响程序地性能
  • 2.JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化
    2.1 为字符串开辟一个字符串常量池,类似于缓存区
    2.2 创建字符串常量时,首先查询字符串常量池是否存在该字符串
    2.3 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中

字符串常量池设计原理

在这里插入图片描述

字符串常量池底层时HotSpot的C++实现的。底层类似一个Hashtable,保存的本质上是字符串对象的引用,来看一道比较常见的案例,图中的代码创建了多少个String对象
// JDK6:false 创建了6个对象
// JDK7及以上:true 创建了5个对象

为什么输出会有这些变化呢?主要还是字符串从永久代中脱离、移入堆区的原因,intern()方法也相应发生了变化
同时也解释了JDK1.6中字符串溢出会抛出OutOfMemoryError:PermGen Space.而在JDK1.7及以上版本会抛出OutOfMemoryError:Java heap space

在这里插入图片描述

在JDK1.6中,调用intern()首先会在字符串池中寻找equals相等的字符串,加入字符串存在就返回该字符串在字符串池中的引用,假如字符串不存在,虚拟机会重新在永久代上创建一个实例,将StringTable的一个表项指向这个新创建的实例
在这里插入图片描述

在JDK1.7(及以上版本)中,由于字符串池不在永久代了,intern()做了一些修改,更方便地利用堆中的对象。字符串存在时和JDK1.6一样,但是字符串不存在时不再需要重新创建实例,可以直接指向堆上的实例

我们再来看一个例子
在这里插入图片描述
JDK1.7以上:false true
JDK1.6: false false

在这里插入图片描述

jdk1.6代码图
在JDK1.6中上述的所有打印都是false,因为jdk6的常量池是放在Perm区中的,Perm区和正常的Java Heap区域是完全分开的。如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而new出来的String对象是放在Java Heap区域。所以拿一个Java Heap区域的对象地址和字符串常量池的对象地址进行比较比较肯定是不相同的,即使调用String.inern方法也是没有关系的

在这里插入图片描述

jdk1.7代码图
这里要明确一点的是,在Jdk6以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用intern是会直接产生java.lang.OutOfMemoryError:PermGen Space错误的。Perm区域太小是一个主要原因,当然在1.8中已经直接取消了Perm区域,而新建立了一个元区域。应该是jdk开发者认为Perm区域已经不适合现在Java的发展了。
正是因为字符串常量池移动到Java Heap区域后,再看下面解释。

  • 1.先看s3和s4字符串,String s3 = new String(“1”) + new String(“1”);这句代码中现在生成了两个对象,一个是字符串常量池中的"1",另一个是Java Heap中的s3引用指向的对象。中间还有2个匿名的new String(“1”),不去讨论他们,此时s3引用对象内容是"11",但此时常量池中是没有"11"对象的
  • 2.接下来,s3.intern(); 这一句代码,是将s3中的"11"字符串放入String常量池中,因为此时常量池中不存在"11"字符串,因此常规做法是跟jdk6图中所示的一样,再常量池中生成一个"11"的对象,关键点是jdk7中常量池不在Perm区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。也就是说引用地址是相同的
  • 3.最后String s4 = “11”;这句代码中"11"是显式声明的,因此会直接区常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向s3引用对象的一个引用。所以s4引用就指向和s3一样了,因此最后的比较s3 == s4 是true
  • 4.再看s和s2对象,String s = new String(“1”);第一句代码,生成了2个对象。常量池中的"1"和Java Heap中的字符串对象 s.intern()这一句是s对象区常量池中寻找后发现"1"已经再常量池里了
  • 5.接下来String s2 = “1”;这句代码是生成一个s2的引用指向常量池中的"1"对象。结果就是s和s2引用地址明显不同

例子二
在这里插入图片描述

JDK1.7以上:false false
JDK1.6 false false

在这里插入图片描述

  • 1.代码一和代码二的改变就是s3.intern的顺序是放在了String s4 = "11"后了,这样,首先执行String s4 = “11”;声明s4的时候常量池中是不存在"11"对象的。执行完毕后"11"对象是s4声明产生的对象。然后再执行s3.intern时,发现常量池中"11"对象已经存在了,因此s3和s4的引用时不同的
  • 2.s和s2代码中,s.intern()这一句往后放也不会有什么影响了,因为对象池中执行第一句代码String s = new String(“1”);的时候已经生成"1"对象的了,下边的s2声明都是直接从常量池中取地址引用的,s和s2的引用地址是不会相等的

key的生成方式

在这里插入图片描述

1.通过String的内容 + 长度生成hash值
2.将hash值转为key
在这里插入图片描述

hash生成方式

在这里插入图片描述

通过hash计算索引

value的生成方式

将Java的String类的实例InstanceOopDesc封装成HashtableEntry
在这里插入图片描述

在这里插入图片描述


http://www.ppmy.cn/ops/102733.html

相关文章

数据结构:栈和队列

目录 栈 顺序栈 结构体 创建 判断是否满栈 判断是否是空栈 入栈 出栈 遍历打印 销毁 链式栈 入栈: 出栈: 队列 链式队列 入队列: 出队列: 栈和队列 栈 栈: FILO 先进后出,后进先出 &…

BaseCTF-web-Week1

写在前面: 题目类型还是比较全,也都是基础题型,适合刚入门 CTF 的萌新学习,我之前在学校实验室预备队招新赛中也有出过一些类似的基础题,欢迎大家参考。 SNERT预备队招新CTF体验赛-Web(SWCTF)ht…

uniapp(微信小程序如何使用单选框、复选框)

一、先看效果 二、数据结构 说明:selected用来记录每次用户选择的值,当是单选的时候属性中的selected属性需要设置成字符串,当是复选框的时候,此时选择的是数组,selected属性应设置为数组。type用来区分当前是单选还是…

在vue3中封装WebSocket

下载websocket npm install websocket 或 yarn add websocket 一、新建webSockte.js文件 // webSocket.js // 自定义组合式函数,用于管理 WebSocket 连接 import { ref, onMounted, onBeforeUnmount } from "vue"; const useWebSocket (url, reco…

Vue3-win7搭建vue3环境

Vue3-win7搭建vue3环境 官方要求的信息是是node.js 18.03以上。而我的环境:win7 x64, vscode 1.34。 参考网址: 0、基本的安装 https://blog.csdn.net/m0_49139268/article/details/126159171 a、这里有各种安装包的下载路径(镜…

src-登陆框的常见测试思路

常见的登陆形式 第三方平台 OAuth 认证 用户名 密码 手机号 短信验证码 邮箱 邮件验证码 登陆框的常见测试思路 弱口令 弱口令指的是人为设定、复杂度较低的密码口令 为系统账户(尤其是管理员账户)设置弱口令会使得整个系统的身份认证模块…

vscode调试代码2(提前运行程序)

今天遇到一个很想解决的问题哈,就是如何使用vscode在调试前运行一个脚本,这个相当于提前运行的任务 之所以之前这个问题没有解决,是因为我搜索的 How to run a script before running/debugging a python script by vscode?To run a scrip…

AI模型应该追求全能还是专精

目录 1.概述 2.AI模型的全面评估和比较 2.1. 精度 (Accuracy) 2.2. 速度 (Speed) 2.3. 鲁棒性 (Robustness) 2.4. 可扩展性 (Scalability) 2.5. 解释性/可解释性 (Interpretability) 2.6.应用场景分析 3.AI模型的专精化和可扩展性 3.1. 模型构架选择 3.2. 训练策略 …