JVM——StringTable面试案例+垃圾回收+性能调优

news/2024/11/20 23:26:49/

JVM——引言+JVM内存结构_北岭山脚鼠鼠的博客-CSDN博客

书接上回内存结构——方法区。

这里常量池是运行时常量池。

方法区

面试题

intern()方法 

intern() 方法用于在运行时将字符串添加到内部的字符串池stringtable中,并返回字符串池stringtable中的引用。

返回值

当调用 intern() 方法时,如果字符串池中已经存在相同内容的字符串,则返回字符串池中的引用;否则,将该字符串添加到字符串池中,并返回对字符串池中的新引用。

StringTable_常量池与串池的关系

从字节码和常量池角度看。

准备如下一个类

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

在命令行先用javac test.java编译出字节码文件,然后用javap -v test.class显示反编译内容

如果没有生成LocalVariableTable,可以通过这条命令 javac -g:vars xxxxxx.java

看见main方法部分的指令中第一条是ldc #2,到常量池中2号位置加载一个信息,该信息有可能是常量,也有可能是对象的引用。这里加载了一个字符串对象a。

 astroe_1将a存入局部变量表中的1号位置,astore将b存入2,astore将ab存入3。

局部变量表是栈帧的东西。

常量池最初存在于字节码文件,运行时会被加载到运行时常量池,Constant Pool中的符号会被加载到运行时常量池。

//常量池中的信息,都会被加载到运行时常量池中,这时a,b,ab都是常量池中的符号,还没有变为java 字符串对象,也还没有加入到stringtable中。

ldc #2会把a符号变为“a”字符串对象,然后会拿着“a”去StringTable串池里面找有没有取值相同的key,没有的话会把“a”放入stringtable并返回引用,有的话会使用串池中的a对象的引用。

每个字符串对象都不是事先放在串池里面,而是用到的时候才开始创建,懒加载。

这里容易混淆,stringtable=字符串常量池=串池,是在堆中的一块区域,常量池是另一个东西,是内存元空间中的一块区域.(1.8)

 StringTable_字符串变量拼接

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

加上这个字符串拼接的代码之后再次反编译后。

s4那里创建一个StringBuilder对象并调用其init方法,()V表示调用无参构造。

aload_1表示准备好了s1这个参数,astore_1是存入了局部变量表。

然后调用了append方法,最后那里调用了一个toString方法。

s1+s2    =    new StringBuild().append("a").append("b").toString();

astore 4是将上面toString得到的新的String对象("ab")存入s4.

 然后在这里

输出一个false,s3是在串池中的,s4是引用一个新的字符串对象,值一样,s3在串池,s4在堆里面,位置不同,是两个对象,输出false.

  StringTable_编译期优化

public class test {public static void main(String args[])   {String s1="a";String s2="b";String s3="ab";String s4=s1+s2;  //new StringBuild().append("a").append("b").toString()String s5="a"+"b"; //这就是javac找编译期的优化,结果已经在编译期确定为abSystem.out.println(s3==s5);}
}

编译之后如下

s5="a"+"b"对应的是ldc #4,直接在字符串常量池找到已经拼接好的“ab”,并存入了局部变量表5号中

String s3="ab"也是到常量池中找“ab”.

区别就是s3没有找到ab对象并加了进去,s5找到并获取了s3存入的ab对象,因此两个获得的是同一个对象。

因此s3==s5结果为true.

简单说,就是**new出来的放在堆里**,**直接赋值的放在串池里**,**虽然串池也在堆里**,**但是区域不同(地址不同)**,而==比较的是地址,因此false(s3==s4时)。

 

 StringTable特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池

intern_1.8

public class test {//["a","b"]public static void main(String args[])   {String s=new String("a")+new String("b");//new String("ab")=new StringBuild().append(a).append(b).toString()//堆中 new String("a")  new String("b") new String("ab")String s2=s.intern();  //将字符串对象尝试放入串池,如果有则不会放入,并将串池中的对象返回给s2。System.out.println(s2=="ab");  //trueSystem.out.println(s=="ab");     //true}
}

上面这段代码中s=new ....已经在堆里放了a,b,ab,在串池放了a,b.

s.intern()则是把s在堆中的地址也放入了串池,因此s2在串池取到的“ab”和堆中的“ab”地址是相等的。

public class test {//["a","b"]public static void main(String args[])   {String x="ab";String s=new String("a")+new String("b");//new String("ab")=new StringBuild().append(a).append(b).toString()//堆中 new String("a")  new String("b") new String("ab")String s2=s.intern();  //将字符串对象尝试放入串池,如果有则不会放入,并将串池中的对象返回给s2。System.out.println(s2==x);  //trueSystem.out.println(s==x);   //false}
}

 这一段代码里面,ab早已在串池,s.intern()没有放进去,于是s2取到了串池中的“ab”,而不是堆中的“ab”.

intern_1.6

  • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回
  • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份创建个新的,放入串池,并会把串池中的对象返回

 下面的代码在1.8是true,true,在1.6是true,false

public class test {//["a","b"]public static void main(String args[])   {String s=new String("a")+new String("b");//new String("ab")=new StringBuild().append(a).append(b).toString()//堆中 new String("a")  new String("b") new String("ab")String s2=s.intern();  //将字符串对象尝试放入串池,如果有则不会放入,并将串池中的对象返回给s2。String x="ab";System.out.println(s2==x);  //System.out.println(s==x);   //}
}

面试题

public class test {public static void main(String args[])   {String s1="a";String s2="b";String s3="a"+"b";//串池中abString s4=s1+s2;//堆里new String("ab")String s5="ab"; //应用串池abString s6=s4.intern();  //串池已有,获得串池ab
//问System.out.println(s3==s4);  //falseSystem.out.println(s3==s5);  //trueSystem.out.println(s3==s6);  //trueString x2=new String("c")+new String("d"); //堆中cdString x1="cd";     //串池中cdx2.intern();        //尝试入池失败
//问 ,如果调换了x1,x2位置呢?如果是jdk1.6呢?System.out.println(x1==x2);  //  false}
}

String x1=“cd”和x2.intern调换之后就是true,x1直接用串池中的ab.

调换之后换成jdk1.6的话,也是false,串池中是x2的副本,不是x2故x1从串池取到的不是x2.

StringTable_位置

1.6串池在常量池中,1.8串池去到了堆中。

原因:

永久代内存回收效率很低,永久代需要fullGC时才会触发永久代的垃圾回收,而fullGC只有在老年代空间不足时才会触发,触发时间晚,间接导致串池回收效率不高。

1.8在堆中只要Minor GC就会触发StringTable串池的垃圾回收。

案例

1.6下报错

1.8下报错

并不是堆内存不足报错

 原因:

-XX:+UseGCOverheadLimit是打开

-XX:-UseGCOverheadLimit是关闭

 1.8中,如果98%的时间花在垃圾回收上但只回收了2%的堆空间,则jvm已经无药可救,便不会再尝试垃圾回收,会报错OverHead Limit。

 关闭后1.8报错

堆空间不足

 证明了1.8串池用的堆空间,1.6串池用的永久代。

StringTable_垃圾回收

两个参数,一个是打印字符串表的统计信息,可以看见串池中字符串实例个数以及占用大小

一个是打印垃圾回收详细信息,次数和回收时间。

 串池统计信息,底层采用哈希表实现,数组加链表结构。

数组个数成为桶buckets,下图默认是60013个,entries键值对个数是1754个,literals字符串常量也是1754个。总占用Total footprint.

 修改代码如下

在try中加个循环不断往串池加入新对象。

运行后可以看见多了100个字符串对象进StringTable 

 

 加多循环次数

 此时只有7000多个。

 由于内存空间分配失败触发了一次垃圾回收。

minorGc新生代垃圾回收。

 这里证明了StringTable也是会发生垃圾回收的。

StringTable性能调优

  • 调整-xx:StringTableSize=桶个数

串池底层是哈希表,性能跟大小相关,桶的个数多,碰撞几率低,查找速度快,桶的个数较少的话,碰撞几率更高,导致链表较长,查找速度变慢。

这里调优就是调整桶的个数,下图中使用参数设定了桶的个数为20万个。

 

上面是用BufferReader读取了单词表48万个单词,然后全部放入串池中。上面输出了入池所花费的时间。

花费时间0.4s.

 统计信息可以看见桶个数有20万个。平均每个桶两个单词多。因此效率较高。

 在去除20w的参数后花费时间变多,默认桶个数是6万

 桶的个数最小是1009,设置为1009后再次测试变得更慢了。

 系统里字符串常量个数较多就可以调整桶的个数变大,使其有更好的分布,减少冲突,使串池效率提升。

StringTable_性能调优案例

  • 调整-xx:StringTableSize=桶个数
  • 考虑将字符串对象是否入池

网络段子:

推特要存储用户地址在内存中,但是堆内存不够大并且很多内存重复,解决方法就是采用了字符串的intern()方法,可以去除重复地址。

在上面的代码基础上改动。

 循环读取10次48w个单词,使其出现10次重复的情况。

使用一个生命周期更长的List<String>中防止被垃圾回收。这里先不入池。并且在开始前和结束后都有一个输入。

使用Java VisualVM查看信息

读取文件前

char和String有个11mb左右

读取后char和String有300mb,占了80%多。

 修改代码

在添加到list集合之前先入池,然后返回值是串池中的字符串对象。串池外的会被垃圾回收。

 这一次的内存占用只剩30%多,一共也就30多mb.与之前的内存差异明显。

  案例证明如果引用里面有大量的字符串且字符串可能存在重复问题可以让字符串入池减少字符串对象个数,减少堆内存的使用。


http://www.ppmy.cn/news/1044134.html

相关文章

一文读懂结构型模式----组合模式!

一、组合模式 1.组合模式的概述 1.1 组合模式是什么 组合模式是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次。 三要素&#xff1a; 根节点(root)&#xff1a;在一棵非空的树中只有一个根节点&#xff0…

【rust/egui】(三)看看template的app.rs:序列化、持久化存储

说在前面 rust新手&#xff0c;egui没啥找到啥教程&#xff0c;这里自己记录下学习过程环境&#xff1a;windows11 22H2rust版本&#xff1a;rustc 1.71.1egui版本&#xff1a;0.22.0eframe版本&#xff1a;0.22.0上一篇&#xff1a;这里 serde app.rs中首先定义了我们的Templ…

36.SpringMVC视图

SpringMVC视图 SpringMVC中的视图是View接口&#xff0c;视图的作用渲染数据&#xff0c;将模型Model中的数据展示给用户 SpringMVC视图的种类很多&#xff0c;默认有转发视图(InternalResourceView)和重定向视图(RedirectView) 配置视图&#xff1a; 当工程引入jstl的依赖&a…

Java 计算两个字符的相似度

在Java中&#xff0c;要计算两个字符的相似度&#xff0c;可以借助一些字符串相似度算法。以下是几种常见的字符串相似度算法&#xff1a; Levenshtein距离&#xff1a;也称为编辑距离&#xff0c;用于计算两个字符串之间的最小编辑操作次数&#xff08;插入、删除、替换&…

Unity进阶–通过PhotonServer实现人物移动和攻击–PhotonServer(五)

文章目录 Unity进阶–通过PhotonServer实现人物移动和攻击–PhotonServer(五)DLc&#xff1a; 消息类和通信类服务器客户端 Unity进阶–通过PhotonServer实现人物移动和攻击–PhotonServer(五) DLc&#xff1a; 消息类和通信类 Message namespace Net {public class Message{p…

力扣75——单调栈

总结leetcode75中的单调栈算法题解题思路。 上一篇&#xff1a;力扣75——区间集合 力扣75——单调栈 1 每日温度2 股票价格跨度1 - 2 解题总结 1 每日温度 题目&#xff1a; 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &…

「UG/NX」Block UI 面收集器FaceCollector

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#