JVM基础篇-StringTable

StringTable

特性

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

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

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

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

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

    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
场景1
package com.vmware.jvm;public class Demo1 {public static void main(String[] args) {String a="a";String b="b";String c="ab";}
}
  • 在JVM中会预先准备好一块内存空间 StringTable [] ,其内容为空 数据结构采用HashTable
  • 此时符号a、b、ab都还是常量池中的符号,还没有变为java字符串对象
  • ldc #2会将a符号变为a字符串对象,然后判断StringTable中是否存在字符串a,如果不存在则添加到StringTable中,否则直接使用StringTable中的串
  • ldc #3会将b符号变为b字符串对象 然后判断StringTable中是否存在字符串b,如果不存在则添加到StringTable中,否则直接使用StringTable中的串
  • ldc #4会将ab符号变为ab字符串对象 然后判断StringTable中是否存在字符串ab,如果不存在则添加到StringTable中,否则直接使用StringTable中的串
  • 执行结束后: StringTable [“a”,“b”,“ab”]

❗️ 注意:字符串不会预先加载到StringTable中,而是当使用该字符串时才会被加载,其行为为懒惰型

反编译代码

      stack=1, locals=4, args_size=10: ldc           #2                  // String a  2: astore_1                                       3: ldc           #3                  // String b  5: astore_2                                       6: ldc           #4                  // String ab 8: astore_3                                       9: return
场景2
public class Demo1 {public static void main(String[] args) {String a = "a";String b = "b";String c = "ab";String d = a + b;}
}

反编译如下

9: new           #5                  // class java/lang/StringBuilder //new StringBuilder()
12: dup
13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V 调用无参构造方法
16: aload_1                           //从LocalVariableTable中加载1号槽的变量"a"
17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; //调用StringBuild().append("a")
20: aload_2                           //从LocalVariableTable中加载2号槽的变量"b"
21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;//调用StringBuild().append("b")
24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
//调用toString()方法  -> new String("ab");
27: astore        4                   //存储到LocalVariableTable4号槽

StringBuild toString方法源码

 	@Overridepublic String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);}

相当于

new StringBuild().append("a").append("b").toString();

调用new String(“ab”)后相当于在堆中创建了一个新的字符串对象,所以

d==c  //false d在堆中 c在StringTable中
编译期优化

javac会认为e的结果是确定的,所以进行了编译期优化

String e = "a" + "b";

反编译

 29: ldc           #4                  // String ab
  • 相当于直接从常量池中加载符号ab
c==e //true  c第一次被加载到StringTable中,e与c相同不会再次进入StringTable,e指向的地址与c的地址相同
字符串延迟加载证明

在这里插入图片描述

  • 可以使用IDEA开启debug模式后,使用内存检测功能,观察字符串的数量
package com.vmware.jvm;public class Demo2 {public static void main(String[] args) {System.out.println();//2433System.out.println("a");//2434System.out.println("b");//2235System.out.println("c");//2436System.out.println("a");//2436System.out.println("b");//2436System.out.println("c");//2436}
}
  • 代码每执行一行,String的数量+1,说明字符串没有进行预加载
  • 第二次使用字符串时,String的数量没有增加
场景三
public class Demo3 {public static void main(String[] args) {String x="ab";String s = new String("a") + new String("b");//实际上利用StringBuild的append方法进行拼接,"a" "b"会被先加载到串池中 StringTable=["ab","a","b"]String s2 = s.intern();//入池失败   s2-> table."ab"  s-> heap."ab"  x-> table."ab"System.out.println(s == x);//falseSystem.out.println(s2 == x);//true}
}
  • intern方法在jdk1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,并返回串池中的对象
  • 在jdk1.6中将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
String s2 = s.intern();//s2 -> table  s-> heap
  • intern方法注释:调用 intern 方法时,如果池已包含与equals(Object)方法比较后相等的String字符串,则返回池中的字符串。否则,此String对象将添加到池中,并返回对此String对象的引用
问题
package com.vmware.jvm;public class Demo4 {public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "a" + "b";String s4 = s1 + s2;String s5 = "ab";String s6 = s4.intern();//问System.out.println(s3 == s4);//false  s3在编译期进行优化 s3="ab" 在StringTable中  s4=new StringBuild().append("a").append("b"),toString() 在堆中System.out.println(s3 == s5);//true s3进入StringTable s5发现StringTable中有"ab"则指向Table中的地址System.out.println(s3 == s6);//true  s4调用intern方法后入池失败,返回池中的"ab"所以 s6即为池中的"ab"String x2 = new String("c") + new String("d");String x1 = "cd";x2.intern();//将对象new String("cd")放入池中,入池失败//问 如果调换了后两行的位置呢,如果是jdk1.6呢System.out.println(x1 == x2);//jdk1.8 false x2指向堆,x2调用入池方法后入池失败 x2指向堆 x1指向StringTable//交换位置  x2入池成功,x2指向池中的cd  x1指向池中的c 所以为true//jdk1.6 首先在堆中new String("cd") 然后调用intern方法,会对堆中的对象拷贝一份入池,而x2仍然指向堆中的对象  x1发现池中有"cd"直接使用池中的对象,所以为false}
}
String Table的位置

在这里插入图片描述

  • jdk1.6:永久代
  • jdk1.7、1.8:堆内存

🔖 为什么要将StringTable从永久代移动到堆中?

因为永久代的内存只有当触发FullGC的时候才会回收,而堆内存在触发MinorGC的时候就会触发回收,由于StringTable使用比较频繁,所以JVM工程师对位置进行了移动

垃圾回收
package com.vmware.jvm;/*** @apiNote StringTable垃圾回收延时* -Xmx10m:设置堆大小为10m* -XX:+PrintStringTableStatistics 打印StringTable信息* -XX:+PrintGCDetails -verbose:gc 打印垃圾回收信息** 初始信息* StringTable statistics:* Number of buckets       :     60013 =    480104 bytes, avg   8.000* Number of entries       :      1711 =     41064 bytes, avg  24.000  //StirngTable中的key-value对 1711* Number of literals      :      1711 =    154040 bytes, avg  90.029  //字符串数量1711* * 添加10000个字符串到StringTable后* StringTable statistics:* Number of buckets       :     60013 =    480104 bytes, avg   8.000* Number of entries       :      5845 =    140280 bytes, avg  24.000* Number of literals      :      5845 =    353352 bytes, avg  60.454* 数量小于11711,说明StringTable内进行了垃圾回收*/
public class Demo6 {public static void main(String[] args) {int i = 0;try {for (int j = 0; j < 10000; j++) {String.valueOf(j).intern();}} catch (Throwable e) {e.printStackTrace();} finally {System.out.println(i);}}
}
性能调优

StringTable采用的数据结构为哈希表,所以可以通过调整主数组的大小来减小哈希碰撞的概率,进而提升效率

  • 调整StringTable的桶大小
-XX:StringTableSize=桶个数
  • 考虑将字符串对象是否入池

如果程序运行期间会产生大量字符串,并且字符串重复数量较多,可以考虑将字符串进行入池操作,减少内存开销

入池前

在这里插入图片描述

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

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

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

相关文章

VSCode C/C++ 分目录编译配置

分目录编译配置记录 launch.json文件 注释处为修改内容 {// 使用 IntelliSense 了解相关属性。 // 悬停以查看现有属性的描述。// 欲了解更多信息&#xff0c;请访问: https://go.microsoft.com/fwlink/?linkid830387"version": "0.2.0","configur…

uniapp两个单页面之间进行传参

1.单页面传参&#xff1a;A --> B url: .....?code JSON.stringify(param), 2.单页面传参B–>Auni.$emit() uni.$on()

shell 入门练习小记

一、hello world #!/bin/bash echo "Hello World !"#! 为约定的标记&#xff0c;告诉系统这个脚本需要什么解释器执行&#xff0c;后接绝对路径 /bin/bash 表示期望 bash去解析并运行shell echo用于向窗口输出文本 chmod x ./test.sh #给脚本赋执行权限 ./test.sh …

Android性能优化—数据结构优化

优化数据结构是提高Android应用性能的重要一环。在Android开发中&#xff0c;ArrayList、LinkedList和HashMap等常用的数据结构的正确使用对APP性能的提升有着重大的影响。 一、ArrayList ArrayList内部使用的是数组&#xff0c;默认大小10&#xff0c;当数组长度不足时&…

【Linux命令详解 | cp命令】Linux系统中用于复制文件或目录的命令

文章标题 简介参数列表二&#xff0c;使用介绍1. 复制单个文件2. 复制多个文件3. 复制目录4. 保留文件属性5. 创建链接6. 强制覆盖7. 显示复制进度8. 创建备份9. 只有当源文件比目标文件新时才复制10. 复制链接文件 总结 简介 cp命令在Linux系统中用于复制文件或目录。其功能强…

通用人工智能操作系统

随着科技的飞速发展&#xff0c;人工智能已经成为了当今世界最热门的技术领域之一。从智能手机、自动驾驶汽车到智能家居系统&#xff0c;人工智能技术已经渗透到了我们生活的方方面面。然而&#xff0c;尽管人工智能在很多领域取得了显著的成果&#xff0c;但它仍然存在一些局…

电动汽车设计、制造、研发的学科、技术和前沿科技综述

引言&#xff1a;电动汽车作为替代传统燃油汽车的一种先进交通工具&#xff0c;不仅具有环保、低噪音等优势&#xff0c;而且对于能源消耗和气候变化等全球性问题也具有重要意义。本文将综述与电动汽车设计、制造、研发相关的学科、技术和前沿科技&#xff0c;以期对电动汽车领…

【Python】Web学习笔记_flask(3)——上传文件

用GET、POST请求上传图片并呈现出来 首先还是创建文件上传的模板 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>上传图片</title> </head> <body> <form action""…

使用 POI 在 Word 中重新开始编号、自定义标题格式

效果图 引入依赖 <!-- https://mvnrepository.com/artifact/org.apache.poi/poi --><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><!-- https…

【GO】 33.go-zero 示例

1. 获取go-zero库 go get -u github.com/zeromicro/go-zero 2. 安装goctl brew install goctlgoctl -v #goctl version 1.5.4 darwin/amd64 3. 创建.api文件&#xff0c; greet.api goctl api -o greet.api syntax "v1"info (title: // TODO: add titledesc: //…

OSPF综合实验

实验题目如下&#xff1a; 实验拓扑如下&#xff1a; 实验要求如下&#xff1a; 【1】R4为ISP&#xff0c;其上只能配置IP地址: R4与其他所有直连设备间使用公有 【2】R3---R5/6/7为MGRE环境&#xff0c;R3为中心站点 【3】整个OSPF环境IP地址为172.16.0.0/16 【4】所有设备…

Python——调用webdriver.Chrome() 报错

今天运行脚本&#xff0c;报错内容如下&#xff1a; collecting ... login_case.py:None (login_case.py) login_case.py:11: in <module> dr webdriver.Chrome() D:\Program Files (x86)\Python\Python39\Lib\site-packages\selenium\webdriver\chrome\webdriver.p…

外网通过ipv6访问家里设备

想从公司访问家里的设备&#xff0c;比较轻松方便的&#xff0c;用向日葵也可以远程。但是家里电脑比较old的了&#xff0c;向日葵开起来&#xff0c;占用内存挺大的&#xff0c;想尝试windows自带的“mstsc”&#xff0c;所以硬着头皮搞ipv6. &#xff08;重点提示&#xff1…

MySQL-NoSQL整体笔记---持续输出中

MySQL部分 一、搭建 MySQL 数据库服务器 1、下载并上传glibc版本的Mysql 2、新建用户以安全方式运行进程 [roottemplate ~]# groupadd -r -g 306 mysql [roottemplate ~]# useradd -g 306 -r -u 306 mysql3、安装并初始化mysql [roottemplate ~]# tar xf mysql-5.7.36-linu…

Django实现音乐网站 ⑷

使用Python Django框架制作一个音乐网站&#xff0c;在系列文章3的基础上继续开发&#xff0c; 本篇主要是后台歌曲类型表、歌单表模块功能开发。 目录 表结构设计 歌曲类型表结构 歌单表结构 创建表模型 创建表 后台注册表模型 引入表模型 后台自定义 总结 表结构设计…

在.net 6.0中 调用远程服务器web服务,Webservices(xxx.asmx) ,RESTful 风格,2种解决方案。

1.使用 Connected Services&#xff1a; 右键单击您的项目&#xff0c;选择 "Add"&#xff08;添加&#xff09;-> "Connected Services"&#xff08;已连接的服务&#xff09;。 在 "Connected Services" 对话框中&#xff0c;选择 "W…

Gitlab CI/CD笔记-第一天-GitOps和以前的和jenkins的集成的区别

一、GitOps-CI/CD的流程图与Jenkins的流程图 从上图可以看到&#xff1a; GitOps与基于Jennkins技术栈的CI/CD流程&#xff0c;无法从Jenkins集成其他第三方开源的项目来实现换成了Gitlab来进行集成。 好处在于&#xff1a;CI 一个工具Gitlab就行了&#xff0c;但CD部分依旧是…

SpringBoot + Docker 实现一次构建到处运行~

一、容器化部署的好处 图片 Docker 作为一种新兴的虚拟化方式&#xff0c;它可以更高效的利用系统资源&#xff0c;不需要进行硬件虚拟以及运行完整操作系统等额外开销。 传统的虚拟机技术启动应用服务往往需要数分钟&#xff0c;而 Docker 容器应用&#xff0c;由于直接运行…

关于Java的IO流开发

IO概述 回想之前写过的程序&#xff0c;数据都是在内存中&#xff0c;一旦程序运行结束&#xff0c;这些数据都没有了&#xff0c;等下次再想使用这些数据&#xff0c;可是已经没有了。那怎么办呢&#xff1f;能不能把运算完的数据都保存下来&#xff0c;下次程序启动的时候&a…

嵌入式该往哪个方向发展?

1. 你所在的城市嵌入式Linux岗位多吗&#xff1f;我觉得这是影响你做决定的另一个大问题。我们学嵌入式Linux这门技术&#xff0c;绝大部分人是为了从事相关的工作&#xff0c;而不是陶冶情操。但是根据火哥统计来看&#xff0c;嵌入式Linux的普遍薪资虽然高于单片机&#xff0…