JVM篇:字符串常量池

String类型字符串常量池问题

public class demo2 {public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "ab";}
}

对以上代码进行编译得到字节码文件后使用javap -c [字节码文件]反汇编得到以下信息

Constant pool: //常量池
#1 = Methodref #6.#24 // java/lang/Object."<init>":()V
#2 = String #25 // a
#3 = String #26 // b
#4 = String #27 // ab
#5 = Class #28 // demo2
#6 = Class #29 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Ldemo2;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Utf8 args
#17 = Utf8 [Ljava/lang/String;
#18 = Utf8 s1
#19 = Utf8 Ljava/lang/String;
#20 = Utf8 s2
#21 = Utf8 s3
#22 = Utf8 SourceFile
#23 = Utf8 demo2.java
#24 = NameAndType #7:#8 // "<init>":()V
#25 = Utf8 a
#26 = Utf8 b
#27 = Utf8 ab
#28 = Utf8 demo2
#29 = Utf8 java/lang/Object
{ //方法区
public demo2(); //构造方法
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ldemo2;
public static void main(java.lang.String[]); //主方法
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=4, args_size=1
0: ldc #2 // String a//ldc的意思是去常量池中加载一个对象,加载的标号为#2
2: astore_1 //存入局部变量,编号为1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: return
LineNumberTable:
line 3: 0
line 4: 3
line 5: 6
line 6: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 args [Ljava/lang/String;
3 7 1 s1 Ljava/lang/String;
6 4 2 s2 Ljava/lang/String;
9 1 3 s3 Ljava/lang/String;
}
SourceFile: "demo2.java"

以上便是反汇编的数据,需要注意的是,常量池(也叫class文件常量池,主要作用是对照表)中的信息都会被加载到运行时常量池(JVM在加载类时会将常量池的所有信息加载到运行时常量池)中,在未执行到ldc时,运行时常量池中的符号并不是java的字符串对象,仅仅是一个符号,执行完ldc后才会将符号转化为对应的字符串对象。当读取完一个字符串对象后,会将该字符串存储在字符串常量池(hashtable结构,不能扩容),方便下次直接获取。

再看一下字符串拼接的反汇编结果

public class demo2 {public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "ab";String s4 = s1 + s2;}
}
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=5, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: return

可以看到,字符串拼接实际上是用了StringBuilder类中的append方法然后使用toString方法返回。去查看StringBuilder源码

实际上是创建了一个String对象。那么通过创建对象实例化出来的字符串对象与直接赋值的字符串对象地址相同吗?

public class demo2 {public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "ab";String s4 = s1 + s2;System.out.println(s3 == s4);}
}

运行结果如下 

false

由此可知,s4引用的是新的字符串对象(存储在堆中)而s3引用的是StringTable中的ab对象

由此可知,通过new创建的字符串对象即使值一样但内存还是不相同的。这是因为创建出来的对象均放在堆内存中

public class demo2 {public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "ab";String s4 = s1 + s2;String s5 = "a" + "b";System.out.println(s3 == s5);}
}

那么如果是字符a与字符b拼接反汇编结果是什么呢

Code:
stack=3, locals=6, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: ldc #4 // String ab
31: astore 5
33: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
36: aload_3
37: aload 5
39: if_acmpne 46
42: iconst_1
43: goto 47
46: iconst_0
47: invokevirtual #10 // Method java/io/PrintStream.println:(Z)V
50: return

可以看出来直接从字符串常量池中获取了ab值,而不是通过StringBuilder拼接,原因是:对于字符串对象拼接,这是两个可变对象,在运行时可能会动态更改值,因此需要动态拼接,但是对于硬编码写死的字符串拼接,在编译期间已经确定了s5的值,因此直接区字符串常量池里面查找ab的值即可。

字符串延迟加载体现

public class demo3 {public static void main(String[] args) {System.out.println("1");System.out.println("2");System.out.println("3");System.out.println("4");System.out.println("5");System.out.println("6");System.out.println("7");System.out.println("8");System.out.println("9");System.out.println("0");System.out.println("1");System.out.println("2");System.out.println("3");System.out.println("4");}
}

在第一行打断点,去查看String类型的数量来判断字符串是否是延时加载

由上图可以看出,初始的字符串数量为2312个

而在走到第二个断点时,字符串数量以及变成了2322个,加了十个字符串数量。走到第三个断点处字符串数量仍为2322个,因此可以知道,如果字符串常量池中存在该字符串的话会直接返回该字符串的地址,并不会再次存储在字符串常量池。

intern()

public class demo3 {//StringTable [a,b,bc]public static void main(String[] args) {String x = "abc";String s = new String("a") + new String("b");// 这个步骤相当于new String("ab")//堆 new String("a")  new String("b") new String("ab")String s1 = new String("a")+"bc";//intern()方法作用是,主动将堆内存中的字符串对象放入字符串常量池中,//如果不存在则放入,如果存在不会放入,然后返回StringTable中的对象给intren变量//也就是说返回的变量一定是StringTable中的对象String intern = s.intern();System.out.println(s == "ab");String intern1 = s1.intern();System.out.println(s1 == x);System.out.println(intern1 == x);}
}

运行结果如下 

true

false

true

字符串常量池中并没有存在字符串ab,于是执行intern()方法将堆内存中的字符串对象放入字符串常量池中(即将s放入),但是字符串abc已经存在了,因此s1的intern()方法返回的对象是字符串常量池中的,s1本身并没有放入字符串常量池。

public class demo3 {//StringTable [a,b]public static void main(String[] args) {String s = new String("a") + new String("b");String intern = s.intern();System.out.println(s == "ab");}
}

运行结果如下 

false

因为在1.6以下的版本intern()并不是将堆内存中的字符串对象放入字符串常量池中,而是复制一份相同的对象放入,因此即使是调用intern方法也不会将自身放入字符串常量池。

StringTable的位置

在JDK1.6之前,StringTable的位置是在方法区中的永久代当中,但在1.6之后,StringTable被存储在堆内存当中。这样做的好处就是更容易触发GC,减轻字符串对内存的占用情况。

StringTable垃圾回收

/*** 演示 StringTable 垃圾回收* -Xmx10m -XX:+PrintStringTableStatistics -XX:+PrintGCDetails -verbose:gc*/
public class demo4 {public static void main(String[] args) {int i = 0;try {for(int j = 0; j < 10000; j++) { // j = 100, j = 10000String.valueOf(j).intern();i++;}}catch (Exception e) {e.printStackTrace();}finally {System.out.println(i);}}
}

设置运行环境后运行结果为

[GC (Allocation Failure) [PSYoungGen: 2048K->488K(2560K)] 2048K->756K(9728K), 0.0006322 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

10000

Heap

PSYoungGen total 2560K, used 923K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)

eden space 2048K, 21% used [0x00000000ffd00000,0x00000000ffd6cec0,0x00000000fff00000)

from space 512K, 95% used [0x00000000fff00000,0x00000000fff7a020,0x00000000fff80000)

to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)

ParOldGen total 7168K, used 268K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)

object space 7168K, 3% used [0x00000000ff600000,0x00000000ff643040,0x00000000ffd00000)

Metaspace used 3492K, capacity 4498K, committed 4864K, reserved 1056768K

class space used 387K, capacity 390K, committed 512K, reserved 1048576K

SymbolTable statistics:

Number of buckets : 20011 = 160088 bytes, avg 8.000

Number of entries : 14107 = 338568 bytes, avg 24.000

Number of literals : 14107 = 601088 bytes, avg 42.609

Total footprint : = 1099744 bytes

Average bucket size : 0.705

Variance of bucket size : 0.707

Std. dev. of bucket size: 0.841

Maximum bucket size : 6

StringTable statistics:

Number of buckets : 60013 = 480104 bytes, avg 8.000

Number of entries : 10563 = 253512 bytes, avg 24.000

Number of literals : 10563 = 580520 bytes, avg 54.958

Total footprint : = 1314136 bytes

Average bucket size : 0.176

Variance of bucket size : 0.194

Std. dev. of bucket size: 0.441

Maximum bucket size : 3

第一行显示了执行力GC垃圾回收。StringTable statistics中显示了桶数量为60013为默认数量。字符串常量池中存储了10563(应该是11783的)个对象。说明进行了回收操作。

当堆内存中内存不够时,会进行GC垃圾回收操作,字符串常量池中会释放一些没有被引用的字符串对象。

StringTable调优

1、能够调优的原理从StringTable的结构看起。StringTable的实现原理是HashTable(Hash表加链表形式实现)。那么Hash表只要足够大那么地址冲突的发生概率就会变小,减少查询链表的时间。

更改Hash表大小时间上就是更改桶大小:-XX:StringTableSize=桶个数(最少设置为 1009 以上)

2、对于重复创建的字符串,使用intern()入池,而不是创建出String对象存放在堆内存中,这样可以让相同的字符串对象引用一个相同的地址。

总结

常量池中的字符串仅是符号,第一次用到时才会变为对象

利用串池的机制,来避免重复创建字符串对象

字符串变量拼接的原理是StringBuilder(1.8之后)

字符串常量拼接的原理是编译期优化

可以使用intern方法,主动将串池中还没有的字符串对象放入串池

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/599947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自定义标记

章节目录&#xff1a; 一、概述二、使用自定义标记三、注册自定义标记3.1 创建文件3.2 修改文本编码格式 四、执行测试五、结束语 一、概述 pytest 可以支持自定义标记&#xff0c;自定义标记可以把一个 web 项目划分多个模块&#xff0c;然后指定模块名称执行。譬如我可以标明…

企业邮箱发送会议邀请:简单高效的邮件安排技巧与指南

通过电子邮件安排会议是工作中的常见做法。这也是确认口头安排的会议的一种有益方式。在本视频中&#xff0c;我们将详细介绍此类电子邮件的基本部分&#xff01; 您可能出于多种原因需要安排会议&#xff0c;例如安排面试、跟进业务主管或与潜在客户探讨项目。通过电子邮件有效…

HarmonyOS4.0系统性深入开发14AbilityStage组件容器

AbilityStage组件容器 AbilityStage是一个Module级别的组件容器&#xff0c;应用的HAP在首次加载时会创建一个AbilityStage实例&#xff0c;可以对该Module进行初始化等操作。 AbilityStage与Module一一对应&#xff0c;即一个Module拥有一个AbilityStage。 DevEco Studio默…

高并发如何保证接口的幂等性?

前言 接口幂等性问题&#xff0c;对于开发人员来说&#xff0c;是一个跟语言无关的公共问题。本文分享了一些解决这类问题非常实用的办法&#xff0c;绝大部分内容我在项目中实践过的&#xff0c;给有需要的小伙伴一个参考。 不知道你有没有遇到过这些场景&#xff1a; 有时我…

Open3D聚类算法

按照官网的例子使用聚类&#xff0c;发现结果是全黑的。 经过多次测试发现 eps3.3, min_points1这里是关键 min_points必须等于1否则无效果 import time import open3d as o3d; import numpy as np; import matplotlib.pyplot as plt#坐标 mesh_coord_frame o3d.geometry.Tria…

css sourcemap 源代码映射

vue.config.js css: {// Enable CSS source maps.sourceMap: process.env.NODE_ENV ! production, }重新运行&#xff1a;yarn serve 效果&#xff1a;

计算机基础知识——数据的表示概述

目录 1 进制转换 1.1 二进制、十进制和十六进制等常用数制及其相互转换 1.2 十进制和二进制之间转换 1.3 二进制数与八进制数、十六进制数之间的转换 2 码值&#xff1a;原码、反码、补码 2.1 原码 2.2 反码 2.3 补码 3 浮点数表示 3.1 浮点数的运算 1 进制转换 1…

MR实战:网址去重

文章目录 一、实战概述二、提出任务三、完成任务&#xff08;一&#xff09;准备数据1、在虚拟机上创建文本文件2、上传文件到HDFS指定目录 &#xff08;二&#xff09;实现步骤1、创建Maven项目2、添加相关依赖3、创建日志属性文件4、创建网址去重映射器类5、创建网址去重归并…

【Java】LockSupport原理与使用

LockSupport&#xff1a; 关键字段&#xff1a; private static final sun.misc.Unsafe UNSAFE;private static final long parkBlockerOffset; Unsafe&#xff1a;"魔法类"&#xff0c;较为底层&#xff0c;在LockSupport类中用于线程调度(线程阻塞、线程恢复等)。…

Windows安装部署nginx

1、官网下载安装包&#xff1a; 官网地址&#xff1a;https://nginx.org/en/download.html 下载好后&#xff0c;解压即可&#xff1a; 在nginx的配置文件是conf目录下的nginx.conf&#xff0c;默认配置的nginx监听的端口为80&#xff0c;如果本地80端口已经被使用则修改成其…

STM32F103C8T6制作简易示波器

1设计需求 通过stm32f103c8t6实现一个简易示波器功能&#xff0c;该示波器可以检测0-3.6khz频率范围内的波形。 也可以输出波形&#xff0c;输出方波、三角波、正弦波。 2技术方案 通过stm32的ADC功能&#xff0c;采集输入信号&#xff0c;最后由oled屏进行显示。 采样频率…

图像分割实战-系列教程10:U2NET显著性检测实战2

&#x1f341;&#x1f341;&#x1f341;图像分割实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 U2NET显著性检测实战1 U2NET显著性检测实战2 U2NET显著性检测实战3 5、残差Unet模块 class RSU7(n…

MySQL基础篇(四)事务

一、事务简介 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一期向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 注意&#xff1a; 默认 MySQL 的事务是 自动提交 的&#…

重磅发布|博睿数据2023年度精选案例集—— IT运维之光

当前&#xff0c;数字经济已经成为全球经济增长的重要引擎。随着新技术的飞速发展&#xff0c;企业数字化转型机遇不断涌现&#xff0c;而稳定、安全、可靠的IT运维环境是实现数字化转型的关键。 在此背景下&#xff0c;AIOps 智能运维正成为企业高效管控种类繁多数量庞大的物…

CommonJS 和 ES6 Module:一场模块规范的对决(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【Python学习】2024PyCharm插件推荐

目录 【Python学习】2024PyCharm插件推荐 1. Key Promoter X2.Rainbow CSV3.Markdown4.Rainbow Brackets5.Indent Rainbow6.Regex Tester7.Regex Tester8.Background Image Plus9.Material Theme UI10. Chinese 汉化插件参考 文章所属专区 Python学习 1. Key Promoter X 方便…

frp配置内网穿透访问家里的nas

frp配置内网穿透访问家里的nas 需求 家里局域网内有台nas&#xff0c;在去公司的路上想访问它 其内网地址为&#xff1a; http://192.168.50.8:6002 工具 1.frp版本v0.53.2 下载地址&#xff1a; https://github.com/fatedier/frp/releases/download/v0.53.2/frp_0.53.2_li…

十、基本对话框大集合(Qt5 GUI系列)

目录 一、设计需求 二、实现代码 三、代码解析 四、总结 一、设计需求 Qt提供了很多标准的对话框。例如标准文件对话框(QFileDialog)、标准颜色对话框(QColorDialog)、标准字体对话框 (QFontDialog)、标准输入对话框 (QInputDialog) 及消息对话框 (QMessageBox)。本文展示各…

1.4补码范围,溢出,补码加减法、加法器、竞争与冒险、杂项

正数三码合一 负数的原码有1的符号位&#xff0c;反码为除了符号位以外全部取反&#xff0c;补码在反码的基础上再加1 移码的符号位中0表示负数&#xff0c;1表示正数&#xff0c;简单来说&#xff0c;原码的补码数值位不变&#xff0c;符号位取反就是移码。 对于8位寄存器: …

【docker】一文讲完docker搭建私有仓库

一、docker搭建私有仓库方法总结 搭建Docker私有仓库主要有以下几种方式&#xff1a; 使用Docker官方提供的Registry镜像&#xff1a;Docker官方提供了一个用于构建私有镜像仓库的Registry镜像&#xff0c;只需将镜像下载并运行容器&#xff0c;然后暴露5000端口即可使用。可以…