- 1. 常量池与串池的关系
- 2. 字符串【变量】拼接的原理 StringBuilder
- 3. 编译期优化(字符串【常量】拼接的原理)
- 4. 字符串延迟加载
- 5. 可使用intern方法,主动将串池中还没有的字符串对象放入串池
- 5.1 jdk1.8、1.7以后的intern,将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,并且会把串池中的对象返回
- 5.2. jdk1.6的intern,将这个字符串对象尝试放入串池,如果有则并不会放入,【【如果没有则会把此对象复制一份再 放入串池】】,并且会把串池中的对象返回
- 6. StringTable位置
- 7. StringTable垃圾回收
- 8. StringTable性能调优(其实主要是调整hashTable桶的个数)
- 8.1. 方式一:通过调整参数-XX:StringTableSize=桶个数
- 8.2. 考虑将字符串对象是否入池(采用intern()方法,可以去除重复的地址,相同的地址intern()后再串池中只会寸尺一份,减少内存的占用)
- 1:先把这段代码的二进制字节码进行反编译
- 2:反编译过后我们看类方法定义部分、虚拟机指令、常量池
类方法定义
虚拟机指令
常量池
在代码运行的过程中,常量池中的信息,都会被加载到运行时常量池中,我们可以看到这时a b ab 都是常量池中的符号,还没有变成Java字符串对象,只有在执行到具体引用它的那行代码上才会变成对象(懒惰模式),比如:String s1 = “a”;其实它代表的就是虚拟机指令中的:0: ldc #2 // String a
对于上面的补充:在开始创建出一个字符串对象的时候,还需要做一件事情—准备一块空间:StringTable[](串池) 刚开始它时空的没有内容,先去常量池中找到a这个符号,然后把它作为一个key去StringTable[]中找,看有没有取值相同的key,没有的话就会创建这个字符串"a"并且将它放入串池。
StringTable[]在结构上其实是一个hash表,且长度固定,不能扩容
- tip
2. 字符串【变量】拼接的原理 StringBuilder这个 LocalVariableTable其实就是当前当前正在运行的这个main方法的栈帧运行时的局部变量表,其里面存储的就是局部变量
下图中的虚拟机指令的作用就是把生成的字符串对象存入这个局部变量表中去,且在表中每个变量都有编号。
下图中的s4,首先给出结论,通过将二进制字节码反编译我们可以得出图中圈出部分结论
![\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PpabUICY-1651717989303)(https://note.youdao.com/yws/res/b/WEBRESOURCE87d705773f97fa577281e2ae1227d31b)\]](http://www.kaotop.com/file/tupian/20220508/f8089247a96c4af8a8acdd1023bafc4a.png)
aload指令和astore指令相反,aload是从局部变量表中取,astore是存
- 通过下图得出为什么会有上述结论
- 同上,我们直接开看反编译结果
4. 字符串延迟加载关键点:这里的虚拟机指令ldc #4其实就是源码运行到String s5 = “a” + "b"这里,先在常量池中找到"ab"字符串,然后把他们作为key去串池stringTable[]中寻找,发现有相同的,因此就直接使用串池中的了,所以s3 == s5 的值是true
5. 可使用intern方法,主动将串池中还没有的字符串对象放入串池 5.1 jdk1.8、1.7以后的intern,将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池,并且会把串池中的对象返回 5.2. jdk1.6的intern,将这个字符串对象尝试放入串池,如果有则并不会放入,【【如果没有则会把此对象复制一份再 放入串池】】,并且会把串池中的对象返回 6. StringTable位置首先给出结论:不是一下子把这些字符串放入串池,而是执行一行代码,遇到一个串池中没有的字符串对象就将其放入到串池中去。
jdk1.6 串池用的是【永久代】。
jdk1.8 串池用的是【堆空间】。
- ------jdk1.6下
通过以下代码来测试位置
通过下面的这个参数来设置永久代的最大内存大小(便于测试)
运行后的结果:显示为永久代内存溢出
- ------jdk1.8下
通过以下代码来测试位置
通过下面这个参数来设置堆的最大内存(便于测试)
运行的结果不是我们想象的那样报堆内存溢出的错误,这里的错误:OutOfMemory: GC overhead limit exceeded
通过官方文档的一个参数(如下图)可知出现那个错误的原因:如果98%的时间花在了垃圾回收上,但是只有2%的堆空间被回收了,会认为当前jvm已经无可救药了,这个时候就不会尝试垃圾回收了,就会报这个错误,而不是报堆空间不足的错误,为了掩饰这个现象我们需要加入这个参数来把 这个关掉。
7. StringTable垃圾回收接上述:我们添加 这个参数且运行后,报的是堆空间溢出的错误。
- 垃圾回收只有在内存不足的时候才会触发
先看这几个参数
-Xmx10m 设置堆空间的大小为10m
-XX:+PrintStringTableStatistics 打印字符串表的统计信息,通过它我们可以看到串池中字符串的个数包括大小信息
-XX:+PrintGCDetails -verbose:gc 打印垃圾回收的信息、堆内存占用的信息,如果发生垃圾回收的话它会把垃圾回收的次数、时间打印出来。
为这个测试demo加上这几个参数
先看这段代码,这是段没有实际 *** 作的代码,我们看他的输出结果
打印出的符号表(属于常量池的一部分,类的字节码中的类名、方法名、变量名等,他们也是需要读入内存中将来以被查表的方式去转换的)
打印出的StringTable的信息,其底层的实现是hashtable,数组加链表,其中:数组的个数称为“桶”,桶的个数就是下图中的Number Of buckets。
下图可见:桶的个数默认是60013,存储的对象的个数(键值对的个数)为:1754,字符串的常量的个数(串池中的字符的个数literals)为:1754
------改动一下上面的代码,我们创建字符串对象并且让其进入串池
------其运行结果是:可以明显的看到新的字符串的个数被加入StringTable中的,但是仅有的100个字符串不至于引发垃圾回收(通过这次运行打印没有看到上面过的那个参数打印出垃圾回收的信息)
-----如果我们改动一下代码,将10000个字符串放入串池中去,我们再看运行结果:可以看到串池中的字符串个数为7000+,说明发生了垃圾回收,也确实我们看到打印出了垃圾回收的信息(如下二图)
—
8.1. 方式一:通过调整参数-XX:StringTableSize=桶个数StringTable的底层是hash表,其性能是根据其大小相关的,如果hash表桶的个数(数组的个数)比较多,相对来说比较分散,发生hash碰撞的几率就会减小,查找的速率也会更快,反之,桶的个数较少,发生hash碰撞的概率较大,链表更长,查找的速率也会更慢。
- 先给出8.1的总结:如果你的这个系统中常量的个数比较多的话,可以适当的把StringTable的桶的个数调的比较大,可以有一个更好的hash分布,减少hash冲突,让StringTable的串池的效率有个明显的提升
------通过以下测试demo:读入一个文档,其中有40万+行单词
------此时我们通过用虚拟机参数对其调整如图一所示,运行结果为:
花费时间为0.4秒,为什么会这么快呢,我们再通过图三看一下StringTable的统计信息,可以发现桶的个数调整为了20万,也就是这40万+的单词分在了20万个桶里面,因此这效率是非常快的
------如果我们去掉对桶的个数的修改,让其个数为默认值,则运行结果为:可以看到时间为0.6秒,默认的桶的个数为:60013
8.2. 考虑将字符串对象是否入池(采用intern()方法,可以去除重复的地址,相同的地址intern()后再串池中只会寸尺一份,减少内存的占用)------如果我们把桶的值调小(规定范围为:1009~~2305843009213693951),可以看到运行结果很慢,变成了12秒左右
------我们通过下面这个demo演示,我们这个demo和8.1的区别就是我们这把这个文档循环读取了10词,就是把8.1中创建的对象的个数这里再×10并且存放在list中,且一直是存放在堆中,没有被垃圾回收掉,注意我们这里的29是没有使用intern的,所以确实是和8.1相比乘了10的对象个数
运行后我们看内存占用:加起来共占了80%多的堆内存
------和上面不同的是我们这里用了intern() 方法,这说明虽然我们这里创建的字符串对象的个数和上面一样,但是我们除了第一次循环创建,另外9此循环创建的字符串对象都因为在串池中已经有对应的字符串常量存在而没有存放在堆中,因此后面创建的这些都被垃圾回收掉了。
运行后我们看内存占用:加起来共占了30%多的堆内存,明显占用的内存量大大下降
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)