JVM常用概念之String.intern()
问题
String.intern()的工作原理?我们应该如何使用它?
基础知识
字符串池(String Pool)
String类在我们日常编程工作中是使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维护了一块特殊的内存空间,即字符串池(String Pool)。字符串池由String类私有的维护。
字符串驻留(String Interning)
VM(Java虚拟机)在运行时将字符串常量或字符串对象存储在一个特殊的池(String Pool)中,以便重用相同的字符串对象,而不是为每个相同的字符串分配新的内存空间。这样可以减少内存的使用,提高效率,特别是在处理大量字符串时。
自动驻留(String Literal Pooling)
当你使用双引号直接定义字符串常量时,JVM会自动将这个字符串加入到字符串池中。如果池中已经存在相同的字符串,JVM会返回池中已存在的那个引用,而不是创建一个新的字符串对象。
手动驻留(String.intern() 方法)
如果你有一个字符串对象,但希望它被加入到字符串池中(即使它不是通过字面量直接创建的),你可以使用String类的intern()方法。
intern()
public String intern()
当调用 intern 方法时,如果池中已经包含一个由 equals(Object) 方法确定的与此 String 对象相等的字符串,则返回池中的字符串。否则,将此 String 对象添加到池中并返回对此 String 对象的引用。— JDK Javadoc
这样设计本质就是为了优化内存的使用,然而这样做却也有缺点:在 OpenJDK 中, String.intern()是本地方法的,它实际上调用 JVM,将 String 驻留在本地 JVM String 池中。这是因为当本地 VM 和 JDK 代码都必须就特定 String 对象的身份达成一致时,String 驻留是 JDK-VM 接口的一部分。那这么做有什么意义呢?其意义如下:
- 每次intern()时都需要跨 JDK-JVM 接口,这会浪费周期。
- 性能取决于本地的HashTable 实现,这可能会落后于高性能 Java 领域中的实现,尤其是在并发访问的情况下。
- 由于 Java 字符串是来自本地 VM 结构的引用,因此它们成为 GC 根集的一部分。在许多情况下,这需要在 GC 暂停期间进行额外的工作来处理。
实验
大量字符串场景下的吞吐量。
源码-字符串驻留用例
@State(Scope.Benchmark)
public class StringIntern {@Param({"1", "100", "10000", "1000000"})private int size;private StringInterner str;private CHMInterner chm;private HMInterner hm;@Setuppublic void setup() {str = new StringInterner();chm = new CHMInterner();hm = new HMInterner();}public static class StringInterner {public String intern(String s) {return s.intern();}}@Benchmarkpublic void intern(Blackhole bh) {for (int c = 0; c < size; c++) {bh.consume(str.intern("String" + c));}}public static class CHMInterner {private final Map<String, String> map;public CHMInterner() {map = new ConcurrentHashMap<>();}public String intern(String s) {String exist = map.putIfAbsent(s, s);return (exist == null) ? s : exist;}}@Benchmarkpublic void chm(Blackhole bh) {for (int c = 0; c < size; c++) {bh.consume(chm.intern("String" + c));}}public static class HMInterner {private final Map<String, String> map;public HMInterner() {map = new HashMap<>();}public String intern(String s) {String exist = map.putIfAbsent(s, s);return (exist == null) ? s : exist;}}@Benchmarkpublic void hm(Blackhole bh) {for (int c = 0; c < size; c++) {bh.consume(hm.intern("String" + c));}}
}
运行结果
Benchmark (size) Mode Cnt Score Error UnitsStringIntern.chm 1 avgt 25 0.038 ± 0.001 us/op
StringIntern.chm 100 avgt 25 4.030 ± 0.013 us/op
StringIntern.chm 10000 avgt 25 516.483 ± 3.638 us/op
StringIntern.chm 1000000 avgt 25 93588.623 ± 4838.265 us/opStringIntern.hm 1 avgt 25 0.028 ± 0.001 us/op
StringIntern.hm 100 avgt 25 2.982 ± 0.073 us/op
StringIntern.hm 10000 avgt 25 422.782 ± 1.960 us/op
StringIntern.hm 1000000 avgt 25 81194.779 ± 4905.934 us/opStringIntern.intern 1 avgt 25 0.089 ± 0.001 us/op
StringIntern.intern 100 avgt 25 9.324 ± 0.096 us/op
StringIntern.intern 10000 avgt 25 1196.700 ± 141.915 us/op
StringIntern.intern 1000000 avgt 25 650243.474 ± 36680.057 us/op
由上述运行结果可知,伴随着测试字符串数量的增大,String.intern()变得越来越慢,这和我们之前期望的提高效率背道而驰。
通过perf record -g进一步查看结果,如下:
- 6.63% 0.00% java [unknown] [k] 0x00000006f8000041- 0x6f8000041- 6.41% 0x7faedd1ee354- 6.41% 0x7faedd170426- JVM_InternString- 5.82% StringTable::intern- 4.85% StringTable::intern0.39% java_lang_String::equals0.19% Monitor::lock+ 0.00% StringTable::basic_add- 0.97% java_lang_String::as_unicode_stringresource_allocate_bytes0.19% JNIHandleBlock::allocate_handle0.19% JNIHandles::make_local
由上述时间占比可知,在StringTable相关的处理逻辑占用的时间尤其明显,而通过-XX:+PrintStringTableStatistics进一步查看结果,如下:
StringTable statistics:
Number of buckets : 60013 = 480104 bytes, avg 8.000
Number of entries : 1002714 = 24065136 bytes, avg 24.000
Number of literals : 1002714 = 64192616 bytes, avg 64.019
Total footprint : = 88737856 bytes
Average bucket size : 16.708 ; <---- !!!!!!
由上述结果可知,链式哈希表中每个桶 16 个元素意味着严重过载了。更糟糕的是,该字符串表不可调整大小,但是也可以通过-XX:StringTableSize来设置StringTable的默认大小,比如设置为10M,重新运行结果如下:
Benchmark (size) Mode Cnt Score Error Units# Default, copied from above
StringIntern.chm 1 avgt 25 0.038 ± 0.001 us/op
StringIntern.chm 100 avgt 25 4.030 ± 0.013 us/op
StringIntern.chm 10000 avgt 25 516.483 ± 3.638 us/op
StringIntern.chm 1000000 avgt 25 93588.623 ± 4838.265 us/op# Default, copied from above
StringIntern.intern 1 avgt 25 0.089 ± 0.001 us/op
StringIntern.intern 100 avgt 25 9.324 ± 0.096 us/op
StringIntern.intern 10000 avgt 25 1196.700 ± 141.915 us/op
StringIntern.intern 1000000 avgt 25 650243.474 ± 36680.057 us/op# StringTableSize = 10M
StringIntern.intern 1 avgt 5 0.097 ± 0.041 us/op
StringIntern.intern 100 avgt 5 10.174 ± 5.026 us/op
StringIntern.intern 10000 avgt 5 1152.387 ± 558.044 us/op
StringIntern.intern 1000000 avgt 5 130862.190 ± 61200.783 us/op
通过上述运行结果的对比,可发现当将StringTableSize设置为10M时,性能有一定程度的提升,而这种处理方式需要依据实际的预估字符串的规模,机器的内存等资源情况,进行合理的规划和设计,并且盲目地将 StringTableSize 表大小设置为大值,并且不使用它,你将浪费内存。即使你充分利用了大型 StringTable,本地调用成本仍然会消耗周期。
垃圾回收停顿
原生 StringTable最显著的特点是它是 GC 根的一部分,它应该由垃圾收集器专门扫描/更新。在 OpenJDK 中,这意味着在暂停期间要进行艰苦的工作。事实上,对于Shenandoah来说,暂停主要取决于 GC 根集大小,StringTable中只有 1M 条记录会产生以下结果:
$ ... StringIntern -p size=1000000 --jvmArgs "-XX:+UseShenandoahGC -Xlog:gc+stats -Xmx1g -Xms1g"
...
Initial Mark Pauses (G) = 0.03 s (a = 15667 us) (n = 2) (lvls, us = 15039, 15039, 15039, 15039, 16260)
Initial Mark Pauses (N) = 0.03 s (a = 15516 us) (n = 2) (lvls, us = 14844, 14844, 14844, 14844, 16088)Scan Roots = 0.03 s (a = 15448 us) (n = 2) (lvls, us = 14844, 14844, 14844, 14844, 16018)S: Thread Roots = 0.00 s (a = 64 us) (n = 2) (lvls, us = 41, 41, 41, 41, 87)S: String Table Roots = 0.03 s (a = 13210 us) (n = 2) (lvls, us = 12695, 12695, 12695, 12695, 13544)S: Universe Roots = 0.00 s (a = 2 us) (n = 2) (lvls, us = 2, 2, 2, 2, 2)S: JNI Roots = 0.00 s (a = 3 us) (n = 2) (lvls, us = 2, 2, 2, 2, 4)S: JNI Weak Roots = 0.00 s (a = 35 us) (n = 2) (lvls, us = 29, 29, 29, 29, 42)S: Synchronizer Roots = 0.00 s (a = 0 us) (n = 2) (lvls, us = 0, 0, 0, 0, 0)S: Flat Profiler Roots = 0.00 s (a = 0 us) (n = 2) (lvls, us = 0, 0, 0, 0, 0)S: Management Roots = 0.00 s (a = 1 us) (n = 2) (lvls, us = 1, 1, 1, 1, 1)S: System Dict Roots = 0.00 s (a = 9 us) (n = 2) (lvls, us = 8, 8, 8, 8, 11)S: CLDG Roots = 0.00 s (a = 75 us) (n = 2) (lvls, us = 68, 68, 68, 68, 81)S: JVMTI Roots = 0.00 s (a = 0 us) (n = 2) (lvls, us = 0, 0, 0, 0, 1)
如上述的String Table Roots 所示,由于我们决定在根集中放入更多内容,因此每次暂停就会增加 +13 毫秒左右。这促使一些 GC 实现仅在执行繁重工作时才执行StringTable清理。例如,从 JVM 的角度来看,如果类未卸载,则清理StringTable毫无意义 — — 因为已加载的类是驻留字符串的主要来源。因此,这种工作负载至少在 G1 和 CMS 中会表现出有趣的行为,以下面的测试用例为例:
public class InternMuch {public static void main(String... args) {for (int c = 0; c < 1_000_000_000; c++) {String s = "" + c + "root";s.intern();}}
}
运行结果
$ java -XX:+UseConcMarkSweepGC -Xmx2g -Xms2g -verbose:gc -XX:StringTableSize=6661443 InternMuchGC(7) Pause Young (Allocation Failure) 349M->349M(989M) 357.485ms
GC(8) Pause Initial Mark 354M->354M(989M) 3.605ms
GC(8) Concurrent Mark
GC(8) Concurrent Mark 1.711ms
GC(8) Concurrent Preclean
GC(8) Concurrent Preclean 0.523ms
GC(8) Concurrent Abortable Preclean
GC(8) Concurrent Abortable Preclean 935.176ms
GC(8) Pause Remark 512M->512M(989M) 512.290ms
GC(8) Concurrent Sweep
GC(8) Concurrent Sweep 310.167ms
GC(8) Concurrent Reset
GC(8) Concurrent Reset 0.404ms
GC(9) Pause Young (Allocation Failure) 349M->349M(989M) 369.925ms
那如果禁用类卸载会发生什么呢?其实在禁用类卸载时,也会禁用常规 GC 周期中的字符串表清理,执行结果如下:
$ java -XX:+UseConcMarkSweepGC -Xmx2g -Xms2g -verbose:gc -XX:-ClassUnloading -XX:StringTableSize=6661443 InternMuchGC(11) Pause Young (Allocation Failure) 273M->308M(989M) 338.999ms
GC(12) Pause Initial Mark 314M->314M(989M) 66.586ms
GC(12) Concurrent Mark
GC(12) Concurrent Mark 175.625ms
GC(12) Concurrent Preclean
GC(12) Concurrent Preclean 0.539ms
GC(12) Concurrent Abortable Preclean
GC(12) Concurrent Abortable Preclean 2549.523ms
GC(12) Pause Remark 696M->696M(989M) 133.920ms
GC(12) Concurrent Sweep
GC(12) Concurrent Sweep 175.949ms
GC(12) Concurrent Reset
GC(12) Concurrent Reset 0.463ms
GC(14) Pause Full (Allocation Failure) 859M->0M(989M) 1541.465ms <---- !!!
GC(13) Pause Young (Allocation Failure) 859M->0M(989M) 1541.515ms
有上述执行结果可知,发生Stop The World GC。而对于CMS而言,有 ExplicitGCInvokesConcurrentAndUnloadsClasses 可以缓解这种情况,假设用户有时会调用System.gc() 。
总结
String.intern()的使用需要综合考虑吞吐量、内存占用、暂停时间问题,这些问题会直接影响JVM运行的性能,其实手动重复数据删除器/interner可能更能帮助我们获得理想的效果。
相关文章:
JVM常用概念之String.intern()
问题 String.intern()的工作原理?我们应该如何使用它? 基础知识 字符串池(String Pool) String类在我们日常编程工作中是使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维…...
DeepLabv3+改进6:在主干网络中添加SegNext_Attention|助力涨点
🔥【DeepLabv3+改进专栏!探索语义分割新高度】 🌟 你是否在为图像分割的精度与效率发愁? 📢 本专栏重磅推出: ✅ 独家改进策略:融合注意力机制、轻量化设计与多尺度优化 ✅ 即插即用模块:ASPP+升级、解码器 PS:订阅专栏提供完整代码 目录 论文简介 步骤一 步骤二…...
亚信安全发布2024威胁年报和2025威胁预测
在当今数字化时代,网络空间已成为全球经济、社会和国家安全的核心基础设施。随着信息技术的飞速发展,网络连接了全球数十亿用户,推动了数字经济的蓬勃发展,同时也带来了前所未有的安全挑战。2024年,网络安全形势愈发复…...
深入理解 DOM 元素
深入理解 DOM 元素:构建动态网页的基石 在网页开发的世界里,DOM(Document Object Model,文档对象模型)元素宛如一座桥梁,连接着静态的 HTML 结构与动态的 JavaScript 交互逻辑。它让原本呆板的网页变得鲜活…...
[数据分享第七弹]全球洪水相关数据集
洪水是一种常见的自然灾害,在全球范围内造成了极为严重的威胁。近年来,针对洪水事件的检测分析,以及对于洪水灾害和灾后恢复能力的研究日渐增多,也产生了众多洪水数据集。今天,我们一起来收集整理一下相关数据集。&…...
SpringBoot POST和GET请求
1. 什么是 HTTP 请求? HTTP 协议:超文本传输协议,用于客户端和服务器之间的通信。 常见 HTTP 方法: GET:获取资源POST:提交数据PUT:更新资源DELETE:删除资源 2. GET 请求详解 作…...
MySQL 面试篇
MySQL相关面试题 定位慢查询 **面试官:**MySQL中,如何定位慢查询? 我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了2秒以上,因为我们当时的系统部署了运维的监控系统Skywalking ,在展示的报表中可以看到…...
【Andrej Karpathy 神经网络从Zero到Hero】--2.语言模型的两种实现方式 (Bigram 和 神经网络)
目录 统计 Bigram 语言模型质量评价方法 神经网络语言模型 【系列笔记】 【Andrej Karpathy 神经网络从Zero到Hero】–1. 自动微分autograd实践要点 本文主要参考 大神Andrej Karpathy 大模型讲座 | 构建makemore 系列之一:讲解语言建模的明确入门,演示…...
Android MVC、MVP、MVVM三种架构的介绍和使用。
写在前面:现在随便出去面试Android APP相关的工作,面试官基本上都会提问APP架构相关的问题,用Java、kotlin写APP的话,其实就三种架构MVC、MVP、MVVM,MVC和MVP高度相似,区别不大,MVVM则不同&…...
python使用django搭建图书管理系统
大家好,你们喜欢的梦幻编织者回来了 随着计算机网络和信息技术的不断发展,人类信息交流的方式从根本上发生了改变,计算机技术、信息化技术在各个领域都得到了广泛的应用。图书馆的规模和数量都在迅速增长,馆内藏书也越来越多,管理…...
JavaScript系列06-深入理解 JavaScript 事件系统:从原生事件到 React 合成事件
JavaScript 事件系统是构建交互式 Web 应用的核心。本文从原生 DOM 事件到 React 的合成事件,内容涵盖: JavaScript 事件基础:事件类型、事件注册、事件对象事件传播机制:捕获、目标和冒泡阶段高级事件技术:事件委托、…...
大话机器学习三大门派:监督、无监督与强化学习
以武侠江湖为隐喻,系统阐述了机器学习的三大范式:监督学习(少林派)凭借标注数据精准建模,擅长图像分类等预测任务;无监督学习(逍遥派)通过数据自组织发现隐藏规律,…...
win11编译llama_cpp_python cuda128 RTX30/40/50版本
Geforce 50xx系显卡最低支持cuda128,llama_cpp_python官方源只有cpu版本,没有cuda版本,所以自己基于0.3.5版本源码编译一个RTX 30xx/40xx/50xx版本。 1. 前置条件 1. 访问https://developer.download.nvidia.cn/compute/cuda/12.8.0/local_…...
FY-3D MWRI亮温绘制
1、FY-3D MWRI介绍 风云三号气象卫星(FY-3)是我国自行研制的第二代极轨气象卫星,其有效载荷覆 盖了紫外、可见光、红外、微波等频段,其目标是实现全球全天候、多光谱、三维定量 探测,为中期数值天气预报提供卫星观测数…...
Codeforces1929F Sasha and the Wedding Binary Search Tree
目录 tags中文题面输入格式输出格式样例输入样例输出说明 思路代码 tags 组合数 二叉搜索树 中文题面 定义一棵二叉搜索树满足,点有点权,左儿子的点权 ≤ \leq ≤ 根节点的点权,右儿子的点权 ≥ \geq ≥ 根节点的点权。 现在给定一棵 …...
HBuilder X 使用 TortoiseSVN 设置快捷键方法
HBuilder X 使用 TortoiseSVN 设置快捷键方法 单文件:(上锁,解锁,提交,更新) 安装好 TortoiseSVN ,或者 按图操作: 1,工具栏中 【自定义快捷键】 2,点击 默认的快捷键设置&…...
Java jar包后台运行方式详解
目录 一、打包成 jar 文件二、后台运行 jar 文件三、示例四、总结在 Java 开发中,我们经常需要将应用程序打包成可执行的 jar 文件,并在后台运行。这种方式对于部署长时间运行的任务或需要持续监听事件的应用程序非常重要。本文将详细介绍如何实现 Java jar 包的后台运行,并…...
Refreshtoken 前端 安全 前端安全方面
网络安全 前端不需要过硬的网络安全方面的知识,但是能够了解大多数的网络安全,并且可以进行简单的防御前两三个是需要的 介绍一下常见的安全问题,解决方式,和小的Demo,希望大家喜欢 网络安全汇总 XSSCSRF点击劫持SQL注入OS注入请求劫持DDOS 在我看来,前端可以了解并且防御前…...
Mysql5.7-yum安装和更改mysql数据存放路径-2020年记录
记录下官网里用yum rpm源安装mysql, 1 官网下载rpm https://dev.mysql.com/downloads/repo/yum/ https://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html(附官网操作手册) wget https://repo.mysql.com//mysql80-community-release…...
[项目]基于FreeRTOS的STM32四轴飞行器: 七.遥控器按键
基于FreeRTOS的STM32四轴飞行器: 七.遥控器 一.遥控器按键摇杆功能说明二.摇杆和按键的配置三.按键扫描 一.遥控器按键摇杆功能说明 两个手柄四个ADC。 左侧手柄: 前后推为飞控油门,左右推为控制飞机偏航角。 右侧手柄: 控制飞机飞行方向&a…...
Android15使用FFmpeg解码并播放MP4视频完整示例
效果: 1.编译FFmpeg库: 下载FFmpeg-kit的源码并编译生成安装平台库 2.复制生成的FFmpeg库so文件与包含目录到自己的Android下 如果没有prebuiltLibs目录,创建一个,然后复制 包含目录只复制arm64-v8a下...
numpy常用函数详解
在深度神经网络代码中经常用到numpy库的一些函数,很多看过之后很容易忘记,本文对经常使用的函数进行归纳总结。 np.arange arange是numpy一个常用的函数,该函数主要用于创建等差数列。它的使用方法如下所示: numpy.arange([star…...
安装树莓派3B+环境(嵌入式开发)
一、环境配置 1、下载树莓派镜像工具 点击进入下载连接 进入网站,点击下载即可。 2、配置wifi及ssh 将SD卡插入读卡器,再接入电脑,随后打开Raspberry Pi Imager下载工具, 选择Raspberry Pi 3 选择64位的操作系统 选择SD卡 选择…...
深度学习/强化学习调参技巧
深度调优策略 1. 学习率调整 技巧:学习率是最重要的超参数之一。过大可能导致训练不稳定,过小则收敛速度慢。可以使用学习率衰减(Learning Rate Decay)或自适应学习率方法(如Adam、RMSprop)来动态调整学习…...
p5.js:sound(音乐)可视化,动画显示音频高低变化
本文通过4个案例介绍了使用 p5.js 进行音乐可视化的实践,包括将音频振幅转化为图形、生成波形图。 承上一篇:vite:初学 p5.js demo 画圆圈 cd p5-demo copy .\node_modules\p5\lib\p5.min.js . copy .\node_modules\p5\lib\addons\p5.soun…...
Linux下安装elasticsearch(Elasticsearch 7.17.23)
Elasticsearch 是一个分布式的搜索和分析引擎,能够以近乎实时的速度存储、搜索和分析大量数据。它被广泛应用于日志分析、全文搜索、应用程序监控等场景。 本文将带你一步步在 Linux 系统上安装 Elasticsearch 7.17.23 版本,并完成基本的配置࿰…...
plt和cv2有不同的图像表示方式和颜色通道顺序
在处理图像时,matplotlib.pyplot (简称 plt) 和 OpenCV (简称 cv2) 有不同的图像表示方式和颜色通道顺序。了解这些区别对于正确处理和显示图像非常重要。 1. 图像形状和颜色通道顺序 matplotlib.pyplot (plt) 形状:plt 通常使用 (height, width, cha…...
【The Rap of China】2018
中国新说唱第一季,2018 2018年4月13日,该节目通过官方微博宣布,其第二季将更名为《中国新说唱》。 《中国新说唱2018》由张震岳、MC Hotdog、潘玮柏、邓紫棋、WYF 担任明星制作人; 艾热获得冠军、那吾克热玉素甫江获得亚军、ICE…...
通义万相2.1开源版本地化部署攻略,生成视频再填利器
2025 年 2 月 25 日晚上 11:00 通义万相 2.1 开源发布,前两周太忙没空搞它,这个周末,也来本地化部署一个,体验生成效果如何,总的来说,它在国内文生视频、图生视频的行列处于领先位置,…...
YOLOv10改进之MHAF(多分支辅助特征金字塔)
YOLOv10架构 YOLOv10的架构主要由 主干网络、特征金字塔和预测头 三部分组成。主干网络采用改进的Darknet结构,增强特征提取能力。特征金字塔模块使用多尺度特征融合技术,提高对不同大小目标的检测效果。预测头则负责生成最终的检测结果。这种结构设计使得YOLOv10在保持高效…...
