JVM学习-虚拟机层面看String

String基本特性
  • String字符串,使用一对“”引起来表示
    • String s1 = “lotus”; //字面量定义方式
    • String s2 = new String(“hello”)
  • String声明为final,不可被继承
  • String实现了Serializable接口,表示字符串支持序列化,实现Comparable接口,表示String可以比较大小
  • String在JDK8以前内部定义了final char[] value用于存储字符串数据,jdk9改为byte[]
  • String代表不可变字符序列,称不可变性
    • 当对字符串重新赋值时,重写指定内存区域赋值,不能使用原有的value进行赋值
    • 当对现有的字符串进行连接操作时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
    • 当调用String的replace()方法修改指定字符或字符串时,也需要重新指定内存区域赋值,不能使用原有的value进行赋值
  • 通过字面量的方式(区别于new)给一个字符串赋值,此时的字符串值声明在字符串常量池中
  • 字符串常量池不会存储相同内容的字符串
    • String的String Pool是一个固定大小的Hashtable,默认值大小长度1009.如果放进String Pool的String非常多,就会造成Hash冲突严重,从而导致链表很长,而链表长了后直接会影响当前调用String.intern的性能
    • 使用-XX:StringTableSize可以设置StringTable的长度
    • 在jdk6中StringTable是固定的,就是1009,所以常量池中的字符串过多会导致效率下降很快,StringTableSize设置数值没有要求
    • 在jdk7中StringTable的长度默认60013,StringTableSize设置数值没有要求
    • 在jdk8开始,设置StringTable的长度,1009是可设置的最小值
String内存分配
  • Java语言中有8种基本数据类型和一种比较特殊的类型String,这些类型为了使它们运行过程中速度更快,更节省内存,都提供了一种常量池的概念
  • 常量池类似于一个Java系统级别提供的缓存。8种基本数据类型的常量池都是系统协调的,String的常量池比较特殊,主要使用方法有两种
    • 直接使用双引号声明出的String对象会直接存储在常量池中
    • 不使用双引号声明的String对象,可以使用String提供的intern()方法
  • Java6及以前,字符串常量池存放在永久代
  • Java7中对字符串池的逻辑做了改变,将字符串常量池位置调整到Java堆内
    • 所有的字符串都保存在堆中,和其他普通对象一样,这样可以让你在进行调优应用时仅需调整堆大小就可以了
    • 字符串常量池概念原本使用得比较多,但是这个改动使我们有足够的理由重新考虑使用String.intern()
  • Java8元空间,字符串常量池在堆中
字符串拼接操作
  • 常量与常量拼接结果在常量池,原理是编译期优化
 @Testpublic void test1() {String s1 = "a" + "b" + "c";          //等同于"abc",//编译期优化String s2 = "abc";//最终.java编译成.class,再执行.class// String s1 = "abc";       String s2 = "abc"System.out.println(s1 == s2);System.out.println(s1.equals(s2));}//执行结果truetrue
  • 常量池中不会存在相同内容的常量
  • 只要其中有一个是变量,结果就在堆中,变量拼接原理是StringBuilder
  @Testpublic void test2() {String s1 = "javaEE";String s2 = "hadoop";String s3 = "javaEEhadoop";String s4 = "javaEE" + "hadoop";    //编译期优化//拼接符号的前后出现了变量,则相当于在堆空间中new String(),具体内容为拼接结果String s5 = s1 + "hadoop";String s6 = "javaEE" + s2;String s7 = s1 + s2;System.out.println(s3 == s4);  //trueSystem.out.println(s3 == s5);  //falseSystem.out.println(s3 == s6);  //falseSystem.out.println(s3 == s7);  //falseSystem.out.println(s5 == s6);  //falseSystem.out.println(s5 == s7);  //falseSystem.out.println(s6 == s7);  //false//判断s6的值是否在常量池中,如果查到则直接返回常量池中的地址,如字符串常量池中不存在,则在常量池中加载一份,并返回此对象地址String s8 = s6.intern();System.out.println(s3 == s8);   //true}@Testpublic void test3() {String s1 = "a";String s2 = "b";String s3 = "ab";/*** s1 + s2执行细节* StringBuilder sb = new StringBuilder();* sb.append("a")* sb.append("b")* sb.toString()----------* @Override---StringBuilder*     public String toString() {*         // Create a copy, don't share the array*         return new String(value, 0, count);*     }*     *  在jdk5.0以后使用StringBuilder,5.0之前使用StringBuilder*/String s4 = s1 + s2;System.out.println(s3 == s4);   //false}
@Testpublic void test4() {/*** 1.字符串拼接不一定使用StringBuilder* 如果拼接符号左右两边都是字符串常量或常量引用,仍使用编译期优化,即非StringBuilder* 2.针对于final修饰类、方法、基本数据类型、引用数据类型的量的结构时,能使用上final时使用上*/final String s1 = "a";final String s2 = "b";String s3 = "ab";String s4 = s1 + s2;System.out.println(s3 == s4);     //true}/*** 通过StringBuilder的append方式添加字符串效率要远高于使用String的字符串拼接方式* 详情:①使用StringBuilder的append方式,自始至终只创建一个StringBuilder对象*      使用String拼接的方式,每次循环会创建一个StringBuilder和一个String*      ②使用String拼接方式,内存由于创建了较多的StringBuilder对象,占用内存大,GC耗时* 改进空间:在实际开发中,如果能基本确定要前前后后添加的字符串长度不高于某个限定值highLevel情况下,建议使用构造器创建StringBuilder* StringBuilder sb = new StringBuilder(highLevel)*/@Testpublic void test5() {long start = System.currentTimeMillis();//method1(100000);     //花费时间:3641method2(100000);   //花费时间:0long end = System.currentTimeMillis();System.out.println("花费时间:" + (end - start));}private void method1(int highLevel) {String src = "";for (int i = 0; i < highLevel; i++) {src += "a";        //每次循环创建一个StringBuilder,String}}private void method2(int highLevel) {StringBuilder sb = new StringBuilder();for (int i = 0; i < highLevel; i++) {sb.append("a");}}
  • 如果拼接的结果调用intern()方法,则该去将常量池中还没有的字符串对象放入池中,并返回些对象地址
intern的使用
  • JDK6中,将这个字符串对象尝试放入串池
    • 如果串池中有,则并不会放入,返回已有的串池中的对象地址
    • 如果没有,会把此对象复制一份,放入串池,并返回串池中的对象地址
  • JDK7起,将这个字符串对象尝试放入串池
    • 如果串池中有,则并不会放入,返回已有的串池中的对象地址
    • 如果没有,会把此对象的引用地址复制一份,放入串池,并返回串池中的对象地址
/*** 如何保证变量s指向的是字符串常量池中的数据?* 方式①:String s = "lotus";//字面量定义的方式* 方式②:调用intern()方法*        String s = new String("lotus").intern()*        String s = new StringBuilder("lotus").toString().intern()*/
public class StringIntern {public static void main(String[] args) {/*** new String("1"),会创建两个对象* 对象1:new String("1")* 对象2:常量池中“1”*/String s = new String("1");   //s指向堆空间创建的字符串对象地址s.intern();String s2 = "1";                      //s2指向常量池中对象地址System.out.println(s == s2);   //jdk6:false,jdk7/8:false/*** new String("1") + new String("1")会创建两个对象* 对象1:new StringBuilder* 对象2:new String("1")* 对象3:常量池中“1”* 对象4:new String("2")* 对象5:常量池中“2”*   深入剖析:StringBuilder的toString()*   对象6:new String("12")*   强调,toString()调用,在常量也中没有生顾"12"*/String s3 = new String("1") + new String("2");   //s3变量地址为new String("12")/*** 在字符串常量池中生成"12"* jdk6:创建一个新的对象"12",也就有新的地址* jdk7/8:此时常量池中并没有创建"12",而是创建一个指向堆空间的指针*/s3.intern();        String s4 = "12";   //使用上一行代码执行时,在常量池中生成的"12"的地址System.out.println(s3 == s4);    jdk6:false,jdk7/8:true}
}
G1的String去重操作
  • 许多Java应用做的测试得出以下结果
    • 堆存活数据集合里面String对象占了25%
    • 堆存活数据集合里面重复的String对象有13.5%
    • String对象的平均长度是45
  • 许多大规模的Java应用的瓶颈在于内存,测试表明,在这些类型的应用里,Java堆中存活的数据集合差不多25%是String对象,更进一步,这里面差不多一半String对象是重复的,重复的意思是说string1.equals(string2)=true,堆上存在重复的string对象必然是一种内存资源浪费,这个项目将在G1垃圾收集器中实现自动持续对重复的String对象进行去重,这样就避免浪费内存
  • 实现
    • 当垃圾收集器工作的时候,会访问堆上存活的对象,对每一个访问的对象都会检查是否是候选的要去重的String对象
    • 如果是,把这个对象的一个引用插入到队列中等待后续的处理,一个去重的线程在后台运行,处理这个队列,处理队列的一个元素意味着从队列删除这个元素,然后尝试去重它引用的String对象
    • 使用一个hashtable来记录所有的被String对象使用的不重复的char数组,当去重的时候,会查看这个hashtable,不看堆上是否已经存在一个一模一样的char数组
    • 如果存在,String对象会被调整引用那个数组,释放对原来的数组的引用,最终被垃圾收集器回收
    • 如果查看失败,char数组会被插入到hashtable,这样以后的时候就可以共享这个数组
  • 命令行选项
    • UseStringDeduplication (bool):开启String去重,默认是不开启的,需要手动开启
    • PrintStringDeduplicationStatistics(bool):打印详细的去重统计信息
    • StringDeduplicationAgeThreshold(uintx):达到这个年龄的String对象被认为是去重的候选对象

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

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

相关文章

供应链金融模式学习资料

目录 产生背景 供应链金融的诞生 供应链金额的六大特征

结构安全预警?事前发现?人工观测VS自动化监测,谁更胜一筹?

人工检测是依靠目测检查或借助于便携式仪器测量得到的信息&#xff0c;但是随着整个行业的发展&#xff0c;传统的人工检测方法已经不能满足检测需求&#xff0c;从人工检测到自动化监测已是必然趋势。 a. 从检测方式看 人工检测需要耗费大量的精力&#xff0c;从摆放检测工具到…

Golang | Leetcode Golang题解之第110题平衡二叉树

题目&#xff1a; 题解&#xff1a; func isBalanced(root *TreeNode) bool {return height(root) > 0 }func height(root *TreeNode) int {if root nil {return 0}leftHeight : height(root.Left)rightHeight : height(root.Right)if leftHeight -1 || rightHeight -1 …

最热门好用骨传导耳机推荐!!分享六大实用选购技巧助你挑选!

耳机基本是每人人手一台&#xff0c;不管是在地铁上还是在公交上&#xff0c;都可以看到很多人戴着耳机度过空余的时光&#xff0c;甚至现在人们在耳机的选择方面更加偏向于骨传导耳机&#xff0c;开放耳道的奇特设计在户外佩戴的时候可以更好的感知到周围的环境音&#xff0c;…

java标准库介绍

Java 提供了一个丰富且功能强大的标准库,也称为 Java API(Application Programming Interface)。这些库涵盖了广泛的功能,从基础数据结构和集合,到并发编程、网络通信、图形界面和XML处理等。以下是一些最基本和常用的 Java 库和包: 1. java.lang 包 java.lang 包是 Ja…

java单元测试:JUnit测试运行器

JUnit测试运行器&#xff08;Test Runner&#xff09;决定了JUnit如何执行测试。JUnit有多个测试运行器&#xff0c;每个运行器都有特定的功能和用途。 1. 默认运行器 当没有显式指定运行器时&#xff0c;JUnit会使用默认运行器&#xff0c;这在JUnit 4和JUnit 5之间有所不同…

基于多模态MRI中深层语义和边缘信息融合的脑肿瘤分割 | 文献速递-深度学习肿瘤自动分割

Title 题目 Brain tumor segmentation based on the fusion of deep semantics and edge information in multimodal MRI 基于多模态MRI中深层语义和边缘信息融合的脑肿瘤分割 01 文献速递介绍 医学图像分割是医学图像处理领域的重要课题。其中&#xff0c;脑肿瘤分割旨在…

基础5 探索JAVA图形编程桌面:字符操作组件详解

在繁华都市的一个角落&#xff0c;卧龙和凤雏相聚在他们常去的台球厅。灯光洒在绿色的台球桌上&#xff0c;彩色的台球整齐地排列着&#xff0c;仿佛在等待着一场激烈的角逐。 卧龙轻轻地拿起球杆&#xff0c;微微瞄准&#xff0c;然后用力一击&#xff0c;白球带着一股强大的力…

C#_库的引用

类库的引用 还可以自己引用类库&#xff1a;解决方案-添加-新建项目 主程序 using System; using System.Windows.Forms; using Tools;namespace ConsoleApp2 {class Program{static void Main(string[] args){//Console.WriteLine("helloword");// Form form ne…

[力扣]——70.爬楼梯

题目描述&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 本题较为简单&#xff0c;主要用到递归思想 int fun(int n,int memo[]) {if(memo[n]!-1) //如果备忘录中已经有记录了…

MVCC相关

文章目录 前情要点基于什么引擎并发事务产生的问题不可重复读和幻读区别Next-Key Lock的示例解决并发事务采用的隔离级别当前读(Current Read)快照读(Snapshot Read)参考 MVCC定义表里面的隐藏字段由db_roll_ptr串成的版本链ReadView可见性算法mvcc的可见性算法为什么要以提交的…

Java封装

什么是封装&#xff1f; 封装是指在将对象的状态信息隐藏在对象内部&#xff0c;不允许外部程序直接访问对象内部的信息&#xff0c;而是通过该类所提供的方法来实现对内部信息的操作和访问 封装的作用 隐藏类的实现细节 让使用者只能通过事先预定的方法来访问数据&#xff0c…

WMI技术介绍以及使用WMI技术获取系统信息

WMI简介 Windows Management Instrumentation&#xff08;WMI&#xff09;是Microsoft Windows操作系统中一个强大的管理框架&#xff0c;它允许管理员以及开发者以标准化的方式访问和控制系统的各种硬件、操作系统组件、应用程序以及网络资源。WMI是基于Web-Based Enterprise…

React Suspense与Concurrent Mode:异步渲染的未来

React的Suspense和Concurrent Mode是React 16.8及更高版本引入的概念&#xff0c;旨在提升用户体验和性能&#xff0c;特别是在处理异步数据加载和动画时。它们是React的下一代渲染策略的一部分&#xff0c;目的是实现更流畅的交互和更高效的资源调度。 Suspense Suspense是一…

Linux之单机项目部署

1、虚拟机&#xff08;VMware&#xff09;创建Linux系统 1.1、创建虚拟机 1.2、配置虚拟机IOS映射文件 1.3、虚拟机内部相关配置 等待加载即可&#xff0c;加载完后会弹出图形化界面&#xff0c;如图&#xff1a; 注意&#xff1a;一般我们做为管理员使用ROOT账号来操作&#x…

[AI Google] Android的防盗功能可保护您的设备和数据安全

Android的新功能可在盗窃发生前、期间和之后帮助保护您的数据的三种方式。 智能手机帮助我们处理日常任务&#xff0c;如在线银行业务、存储敏感信息、为朋友和家人拍照以及快速支付购物。尽管手机让我们的生活变得更加便捷&#xff0c;但它们也包含了大量宝贵信息&#xff0c…

微服务架构(如SpringCloud )中业务中台概念讲解

概念讲解 在微服务架构特别是Spring Cloud技术领域中&#xff0c;业务中台&#xff08;Business Middle Platform, BMP&#xff09;是一个关键概念&#xff0c;旨在通过整合和复用企业的核心业务能力&#xff0c;提高业务响应速度和敏捷性。以下是业务中台的详细讲解&#xff…

数据结构和算法基础(二)

树和二叉树——树的基本概念 树和二叉树——树转二叉树 树和二叉树——查找二叉树&#xff08;二叉排序树&#xff09; 树和二叉树——构造霍夫曼树&#xff08;最优&#xff09; 树和二叉树——线索二叉树 树和二叉树——平衡二叉树 图——基本概念 1、有向图 2、无向图 3、完…

BGP路由优化

一&#xff0c;拓扑 二&#xff0c;要求 用preva1策略确保R4通过R2到达192.168.10.0/24 &#xff0c;用AS Path策略&#xff0c;确保R4通过R3到达192.168.11.0/24 置MED策略&#xff0c;确保R4通过R3到达192.168.12.0/24 .用Local Preference策略&#xff0c;确保R1通过R2到达1…