JVM学习8: 字符串

news2024/9/28 23:23:29

基本特性

  • 代表不可变字符序列
  • final不可被继承
  • 实现了Serializable、Comparable等接口
  • jdk8及以前使用final char[]存储,jdk9开始改为使用byte[]存储
  • 通过字面量方式给一个字符串变量赋值,此时字符串对象在字符串常量池里面

字符串常量池

字符串常量池不会存储内容相同的字符串。

  • 字符串常量池是一个固定大小的HashTable,默认长度是1009。
  • 如果字符串非常多,就会造成Hash冲突,导致链表过长,而链表过长会影响String.intern的性能。
  • 可以使用-XX:StringTableSize设置HashTable的长度。
  • 在jdk6中,长度是固定的,所以常量池的字符串过多就会导致性能下降,StringTableSize设置没有要求。
  • 在jdk7中,长度默认60013,StringTableSize设置没有要求。
  • 在jdk8中,长度默认60013,可以根据实际需求设置,1009是可以设置的最小值。

查看StringTableSize的值

jinfo -flag StringTableSize pid

验证字符串常量池不重复

System.out.println();
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("10"); // 假设执行到这里字符串个数是N

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("10"); // 执行到这里字符的数量还是N

内存分配

在Java语言中,有8种基本数据类型和String类型,这些类型为了在运行过程中速度更快、更加节省内存,都提供了常量池的概念。

常量池类似一个Java系统级别的缓存,8种基本数据类型的常量池都是系统协调,String常量池比较特别:

  • 使用字面量方式声明的String对象会直接存储在常量池中
  • 使用new方式创建的字符串对象,可以使用intern()方法放入到常量池中

存储在哪里

jdk6及以前,字符串常量池在永久代。

jdk7开始,将字符串常量池移到了Java堆。在调优时,仅需要调整堆大小。

jdk8开始,字符串常量池还是在堆中。

创建了几个对象

String s=“a”+“b”+“c”;创建了几个对象?

反编译出来的代码

String s = "abc";

原因分析

在编译期间,应用了编译器优化中一种被称为常量折叠(Constant Folding)的技术,会将编译期常量的加减乘除的运算过程在编译过程中折叠。编译器通过语法分析,会将常量表达式计算求值,并用求出的值来替换表达式,而不必等到运行期间再进行运算处理,从而在运行期间节省处理器资源。

编译期常量的特点就是它的值在编译期就可以确定,并且需要完整满足下面的要求,才可能是一个编译期常量:

  • 被声明为final
  • 基本类型或者字符串类型
  • 声明时就已经初始化
  • 使用常量表达式进行初始化

上面的前两条比较容易理解,需要注意的是第三和第四条,通过下面的例子进行说明:

final String s1 = "hello " + "Hydra";
final String s2 = UUID.randomUUID().toString() + "Hydra";

编译器能够在编译期就得到s1的值是hello Hydra,不需要等到程序的运行期间,因此s1属于编译期常量。而对s2来说,虽然也被声明为final类型,并且在声明时就已经初始化,但使用的不是常量表达式,因此不属于编译期常量,这一类型的常量被称为运行时常量。再看一下编译后的字节码文件中的常量池区域:

在这里插入图片描述

另外值得一提的是,编译期常量与运行时常量的另一个不同就是是否需要对类进行初始化,下面通过两个例子进行对比:

public class IntTest1 {
	public static void main(String[] args) {
		System.out.println(a1.a);
	}
}
class a1{
	static {
		System.out.println("init class");
	}
	public static int a=1;
}

// init class
// 1

public static final int a=1;

// 1

加深对final关键字的理解

public static void main(String[] args) {
	final String h1 = "hello";
	String h2 = "hello";
	String s1 = h1 + "Hydra";
	String s2 = h2 + "Hydra";
	System.out.println((s1 == "helloHydra")); // true
	System.out.println((s2 == "helloHydra")); // false
}

加深对final关键字的理解 - 2

public static void main(String[] args) {
	String s1 = "my ";
	String s2 = "name ";
	String s3 = "is ";
	String s4 = "Hydra";
	String s = s1 + s2 + s3 + s4;
}

在这里插入图片描述

字符串拼接

概述

  • 常量与常量的拼接结果在常量池,原理是编译期优化
  • 只要其中有一个变量,结果就是在堆中创建对象,使用的是StringBuilder拼接
  • 如果拼接的结果调用了intern()方法,则将常量池中还没有的字符串对象放入池中,并返回此对象地址

示例1

String s1 = "javaEE";
String s2 = "hadoop";

String s3 = "javaEEhadoop";
String s4 = "javaEE" + "hadoop"; // 编译期优化
String s5 = s1 + "hadoop"; // 出现变量拼接,在堆中创建String对象
String s6 = "javaEE" + s2;
String s7 = s1 + s2;

System.out.println(s3 == s4); // true
System.out.println(s3 == s5); // false
System.out.println(s3 == s6); // false
System.out.println(s3 == s7); // false
System.out.println(s5 == s6); // false
System.out.println(s5 == s7); // false
System.out.println(s6 == s7); // false

// 判断字符串常量池是否存在s6字符串,如果是,则返回常量池的字符串地址
// 如果不存在,则在常量池保存这个字符串,并返回对象地址
String s8 = s6.intern();
System.out.println(s3 == s8); // true

示例2

String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4); // false

示例3

final String s1 = "a";
final String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
System.out.println(s3 == s4); // true

StringBuilder和字符串拼接比较

通过StringBuilder的append方法追加字符串效率高于String的拼接方式。

因为StringBuilder只使用同一个StringBuilder对象。

但是每一次字符串拼接都会创建新的StringBuilder和字符串,效率低,且内存占用多。

如果在创建StringBuilder的时候就指定一个容量,性能会更好:

StringBuilder sb = new StringBuilder(highLevel);

intern方法

概述

Returns a canonical representation for the string object.

A pool of strings, initially empty, is maintained privately by the class String.

When the intern method is invoked, if the pool already contains a string equal to this String object as determined by the equals(Object) method, then the string from the pool is returned. Otherwise, this String object is added to the pool and a reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned.

如何使用

如果不是使用双引号声明的String对象,可以使用intern()方法将其放入常量池或从常量池获取并返回常量池地址。

也就是说,如果在任意字符串上调用该方法,那么其返回结果所指向的实例,必须和直接以常量形式出现的字符串实例完全相同。

示例1

String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); // false

String s3 = new String("1") + new String("2");
s3.intern();
String s4 = "12";
System.out.println(s3 == s4); // 6:false;7/8:true

示例2

new String(“ab”)创建几个对象?

查看字节码,是两个:

  • 堆里面的字符串对象,使用new关键字创建
  • 字符串常量池的ab字符串,字节码指令ldc

示例3

new String(“a”) + new String(“b”)创建几个对象?

6个:

  • new StringBuilder()
  • new String(“a”)
  • 常量池中的a
  • new String(“b”)
  • 常量池中的b
  • StringBuilder的toString()方法会new String(“ab”)创建字符串对象,但是这个ab不会在字符串常量池创建

intern方法使用

在jdk1.6中:

  • 如果常量池存在,则不放入,返回已经存在的池里面的对象的地址
  • 如果常量池不存在,会把对象复制一份,放入常量池,返回池里面的对象的地址

在jdk1.7/1.8中:

  • 如果常量池存在,则不放入,返回已经存在的池里面的对象的地址
  • 如果常量池不存在,会把对象引用地址复制一份,放入常量池,返回池里面的引用

示例1

String s3 = new String("1") + new String("1");
String s4 = "11";
String s5 = s3.intern();
System.out.println(s3 == s4); // false
System.out.println(s5 == s4); // true

示例2

String s = new String("a") + new String("b");
String s2 = s.intern();
System.out.println(s2 == "ab"); // 6:true;7/8:true
System.out.println(s == "ab"); // 6:false;7/8:true

String的垃圾回收

打印StringTable统计信息

-XX:+PrintStringTableStatistics

G1的String去重

许多Java应用:

  • 堆存活数据集合里面String占了25%
  • 堆存活数据集合里面重复String占了13.5%
  • String对象的平均长度是45

许多大规模Java应用的瓶颈在于内存,在这些类型的应用里面,Java堆中存活的数据集合差不多25%是String对象,差不多一半的字符串是重复的。

s1.equals(s2)==true这样的情况下,重复的String对象会造成资源浪费。这个将在G1垃圾收集器中实现自动持续对重复的String对象进行去重,这样就能避免资源浪费。

实现

  • 垃圾收集器工作时,会访问堆上的存活对象,对每一个对象检查是否是候选的需要去重的String对象
  • 如果是,把这个对象的一个引用插入到队列等待去重线程处理
  • 去重线程删除这个对象,然后尝试引用其他的String对象
  • 使用一个hashtable记录所有的被String对象使用的不重复的char数组,去重时,会检查这个hashtable判断堆上是否已经存在一个相同的char数组
  • 如果存在,String对象会被调整引用那个数组,释放原来数组的引用,最终旧数组会被垃圾收集器回收
  • 如果查找失败,char数组会插入到hashtable中,这样以后就可以共享这个数组

参数

  • UserStringDeduplication - 切换去重开关状态
  • PrintStringDeduplicationStatistics - 打印去重统计信息
  • StringDeduplicationAgeThreshold - 年龄上限

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

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

相关文章

《论文阅读》PAL: Persona-Augmented Emotional Support Conversation Generation

《论文阅读》PAL: Persona-Augmented Emotional Support Conversation Generation 前言简介思路出发点相关知识coefficient of determination任务定义模型框架实验结果前言 你是否也对于理解论文存在困惑? 你是否也像我之前搜索论文解读,得到只是中文翻译的解读后感到失望?…

企企通:企业供应商风险管理,如何用采购管理软件赋能?

企业采购过程中,最怕/最担心的事情无非是:供应链异常。供应链异常,也就是我们常说的供应链风险,可以简单分为需求风险、供应商风险、物流风险和财务风险四大类。其中,最为突出的风险便是供应商风险。从寻找合适的供应商…

用VSCode搭建Vue.js开发环境及Vue.js第一个应用

目录 一、VSCode安装 二、VSCode简单配置 三、Vue.js的下载和引入 四、Vue.js第一个应用 一、VSCode安装 Visual Studio Code是一个轻量级但功能强大的源代码编辑器,可在您的桌面上运行,可用于Windows,macOS和Linux。它内置了对JavaScrip…

阿里一P7员工为证明自己年入百万,晒出工资,却被网友...

阿里的工资在行业内确实是比较高的一类,之前网络上流传着阿里P7年入百万的消息也不是空穴来风,日前,有位阿里P7员工,为了证明自己的确年入百万,晒出了他的工资,网友们看完都沸腾了。什么情况?一…

BSN全球伙伴大会于本周五召开在即,重磅嘉宾演讲主题前瞻

“第三届区块链服务网络(BSN)全球合作伙伴大会”召开在即,将于2023年2月17日(本周五)在杭州市拱墅区举办。 BSN已邀请到来自国内外的行业专家学者与生态合作伙伴,与各界来宾就“建设数字中国”指导思想中的…

iOS 客户端 IM 消息卡片插件化

背景 目前探探 IM 聊天消息列表由于长年累月的代码堆积,对业务迭代产生了很多的困扰。所以趁着工作中的一些空隙,对聊天页消息卡片做了插件化,使得不同的消息类型,可以根据具体需求方便的增删迭代。下面分享一下自己重构过程中一…

项目经理,千万不要在这时候跳槽

早上好,我是老原。节后开工也一段时间了,有不少小友私信老原想要面试题库,大多都是想要跳槽涨薪的......当然除了在做准备的,也有不少朋友都在诉苦:其实,不少人回头去看自己过去经验感觉就像个打杂的&#…

PCB中的HDI板生产中的变化

关键词:HDI概述 HDI发展演变 HDI生产难点如果把一整个电子产业比作浩瀚的宇宙,那些智能电子设备就像宇宙中闪耀的星光,当你以“上帝”的视角手持放大镜去观察时,这些闪烁的星光点点其实都是一个个由精密的“自然规律”所“设计”好…

金三银四丨黑蛋老师带你剖析-CTF岗

作者丨黑蛋二进制是个庞大的方向,对应着许许多多方向的岗位,除了之前说过的逆向岗位,漏洞岗位,病毒岗位,还有专门打CTF的岗位,CTF是网络安全领域的一种比赛。普遍来讲,大学生学习网络安全都会参…

percona软件介绍 、 innobackupex备份与恢复

1. 常用的mysql备份工具 物理备份缺点: 跨平台差。备份时间长、冗余备份、浪费存储空间。 解释如下:如Linux操作系统和Windows操作系统之间,由于文件系统不一样,如Linux操作系统的文件系统是ext4、xfs,Windows操作系统…

K8s+SpringBoot+gRpc

本文使用K8s当做服务注册与发现、配置管理&#xff0c;使用gRpc用做服务间的远程通讯一、先准备K8s我在本地有个K8s单机二、准备service-providerpom<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.…

浅谈性能测试监控系统,做好关键指标的监控

随着业务的增长&#xff0c;服务器部署由单一架构向分布式集群架构转变&#xff0c;性能测试过程中指标监控也由单一服务器向集群服务器转变。 对于性能测试团队来说&#xff0c;需要建立起适用于测试的多机监控系统&#xff0c;以便后期顺利且高效地进行监控分析调优&#xf…

Java程序员拿下高薪offer需要具备哪些能力?这份Java面试专题汇总助你拿下心仪offer!!

背景今天这篇文章的灵感来自一个粉丝的亲身经历&#xff0c;想必也是求职浪潮中很多朋友的经历&#xff0c;内卷大环境找不到满意工作的人太多了&#xff0c;之前也有很多人问过我怎么才能找到不错的工作&#xff0c;甚至是进大厂&#xff0c;所以今天就借这位粉丝的经历来聊聊…

对JAVA 中“指针“理解

对于Java中的指针&#xff0c;以下典型案例会让你对指针的理解更加深刻。 首先对于&#xff1a; 系统自动分配对应空间储存数字 1&#xff0c;这个空间被变量名称b所指向即: b ——> 1 变量名称 空间 明…

linux下yum安装consul实现动态配置管理

一、yum安装consul #安装yum-utils yum install -y yum-utils#配置consul的下载仓库 yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo#必须上面步骤&#xff0c;不然会找不到仓库 yum -y install consul#查看版本 consul -v 二、启动…

基于深度学习的三维重建(二):pytorch的简单操作及DataLoader、Dataset类简介

目录 1.numpy举几个demo 2.pytorch基础 2.1 tensor介绍 3.简单版DataSet & DataLoader 4.模型构建 5.深度学习模型demo&#xff1a;手写文字识别 5.1 构建网络 5.2 前向传播过程 5.3 训练部分 5.4 测试部分 5.5 模型导出 5.6 模型测试 6.pytorch可视化工具ten…

MySQL数据库调优————索引数据结构

B-TREE B-TREE数据结构 B-TREE特性 根节点的子结点个数2 < X < m&#xff0c;m是树的阶 假设m 3&#xff0c;则根节点可有2-3个孩子 中间节点的子节点个数m/2 < y < m 假设m 3&#xff0c;中间节点至少有2个孩子&#xff0c;最多3个孩子 每个中间节点包含n个关…

《MySql学习》 行锁对业务的影响

一. 行锁介绍 行锁由各个存储引擎分别实现&#xff0c;MyISAM存储引擎是不支持行锁的&#xff0c;这也是MySQL使用InnoDB作为默认存储引擎的一个重要原因&#xff0c;锁更细的InnoDB能支持更多的并发业务。但需要注意的是&#xff0c;行锁在InnoDB的实现是给索引加的锁&#x…

智慧养殖无线通讯解决方案

一、方案概述农植畜禽/水产养殖智能监控系统可以在远端设备实现对如温度、湿度、气体浓度、光照度等传感设备的自动调节与控制功能。管理者可随时通过电脑了解养殖场各环节的运行状况&#xff0c;并根据养殖现场内外环境因子的变化情况将命令下发到现场执行设备。为动植物营造舒…

docker-compose安装SonarQube

前言SonarQube 是一个开源的代码分析平台, 用来持续分析和评测项目源代码的质量。 通过SonarQube我们可以检测出项目中重复代码&#xff0c; 潜在bug&#xff0c; 代码规范&#xff0c;安全性漏洞等问题&#xff0c; 并通过SonarQube web UI展示出来。一、docker-compose配置#v…