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

这篇具有很好参考价值的文章主要介绍了JVM——StringTable面试案例+垃圾回收+性能调优+直接内存。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

方法区

面试题

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm astroe_1将a存入局部变量表中的1号位置,astore将b存入2,astore将ab存入3。

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

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

//常量池中的信息,都会被加载到运行时常量池中,这时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.

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 然后在这里

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

  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找编译期的优化,结果已经在编译期确定为ab

        System.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时)。

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 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");  //true
        System.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);  //true
        System.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";//串池中ab
        String s4=s1+s2;//堆里new String("ab")
        String s5="ab"; //应用串池ab
        String s6=s4.intern();  //串池已有,获得串池ab
//问
        System.out.println(s3==s4);  //false
        System.out.println(s3==s5);  //true
        System.out.println(s3==s6);  //true

        String x2=new String("c")+new String("d"); //堆中cd
        String x1="cd";     //串池中cd
        x2.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_位置

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

原因:

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

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

案例

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

1.6下报错

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

1.8下报错

并不是堆内存不足报错

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 原因:

-XX:+UseGCOverheadLimit是打开

-XX:-UseGCOverheadLimit是关闭

 1.8中,如果98%的时间花在垃圾回收上但只回收了2%的堆空间,则jvm已经无药可救,便不会再尝试垃圾回收,会报错OverHead Limit。JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 关闭后1.8报错

堆空间不足

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

StringTable_垃圾回收

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

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 修改代码如下

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

 JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 加多循环次数JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 此时只有7000多个。JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

minorGc新生代垃圾回收。JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

StringTable性能调优

  • 调整-xx:StringTableSize=桶个数

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

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

花费时间0.4s.

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 统计信息可以看见桶个数有20万个。平均每个桶两个单词多。因此效率较高。JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

StringTable_性能调优案例

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

网络段子:

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

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

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

使用Java VisualVM查看信息

读取文件前

char和String有个11mb左右

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm 修改代码

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

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 这一次的内存占用只剩30%多,一共也就30多mb.与之前的内存差异明显。JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

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

直接内存

直接内存不属于java虚拟机内存管理,而是属于系统内存,是操作系统的内存。

定义

  • 常见于 NIO 操作时,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受JM 内存回收管理

基本使用

NIO的 directByteBuffer

使用directBuffer比用传统阻塞IO 传输效率快很多。

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

传统IO操作的过程。 

 java本身没有磁盘读写能力,需要调用操作系统提供的函数。

切换到内核态之后就可以有cpu函数读取磁盘文件内容,读取进来后会在系统内存中划出一块系统缓冲区,系统缓冲区java代码不能运行,因此java代码会在堆内存分配一块java缓冲区,对应代码里new的byte[]数组,要读取数据还要将其从系统缓冲区读入到java缓冲区。

然后到了下一个用户态再去调用输入流的写操作,反复进行读写。

这里数据经过了两次复制,造成效率较低。

directByteBuffer的过程

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 这里直接在操作系统划出了一块缓冲区,这里java代码可以直接访问。两边的代码共享的一个区域。复制次数少了一次,效率提高。

内存溢出

这个直接内存的特点还有不受JVM内存回收管理。

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 上面这个代码每次分配100mb内存。

然后就把报错了直接内存溢出,循环了36次,3.6G.

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 分配和释放原理

  • 使用了Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
  • BvteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 BvteBuffer 对象,一旦 BteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存

通过一个Unsafe对象进行管理的,而不是垃圾回收

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 运行之后可以看见进程占用了一个G的内存。JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 然后置为null之后,1个G的内存被回收

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

原理说明 

在java较为底层的一个Unsafe类可以做到分配直接内存和释放直接内存。一般都是jdk内部自己使用的

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

分配内存那个方法返回了一个long类型的地址,下面就可以用这个地址来进行内存的释放。

分配之后 

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm 释放之后

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 上面说明了直接内存的分配和释放是通过一个Unsafe对象进行管理的,而不是垃圾回收。

 源码分析

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 在源码里面可以看见调用了分配内存的方法。

在cleaner的create方法参数中的一个回调任务对象Deallocator。

在该方法中可以看见Unsafe释放内存的方法。

直接内存的释放借助了虚引用的机制。

意思就是JAVA对象被回收,触发直接内存回收

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

禁用显式回收对直接内存的影响

  • 使用了Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
  • BvteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 BvteBuffer 对象,一旦 BteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存

在做jvm调优时经常会用的一个参数

-XX:+DisableExplicitGC  禁用显式垃圾回收 

systemgc()触发的是fullGC,对新生代和老年代都会回收,造成程序暂停时间较长

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

然后运行之后System.gc()会失效,直接内存无法被回收,ByteBuffer只能等到真正的垃圾回收才会被清理。造成直接内存长时间占用较大。 JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

 解决方案

直接使用unsafe对象进行直接内存的释放。

手动管理文章来源地址https://www.toymoban.com/news/detail-658882.html

JVM——StringTable面试案例+垃圾回收+性能调优+直接内存,JVM,jvm

到了这里,关于JVM——StringTable面试案例+垃圾回收+性能调优+直接内存的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • 小研究 - JVM 垃圾回收方式性能研究(三)

    本文从几种JVM垃圾回收方式及原理出发,研究了在 SPEC jbb2015基准测试中不同垃圾回收方式对于JVM 性能的影响,并通过最终测试数据对比,给出了不同应用场景下如何选择垃圾回收策略的方法。 目录 4 垃圾回收器性能比较 4.1 测试结果 5 结语 4 垃圾回收器性能比较 最大性能指

    2024年02月14日
    浏览(8)
  • 小研究 - JVM 垃圾回收方式性能研究(一)

    小研究 - JVM 垃圾回收方式性能研究(一)

    本文从几种JVM垃圾回收方式及原理出发,研究了在 SPEC jbb2015基准测试中不同垃圾回收方式对于JVM 性能的影响,并通过最终测试数据对比,给出了不同应用场景下如何选择垃圾回收策略的方法。 目录 1 引言 2 垃圾回收算法 2.1 标记清除法 2.2 复制算法 2.3 标记压缩法 2.4 分区算

    2024年02月14日
    浏览(11)
  • java八股文面试[JVM]——垃圾回收

    java八股文面试[JVM]——垃圾回收

    参考:JVM学习笔记(一)_卷心菜不卷Iris的博客-CSDN博客 GC垃圾回收 面试题: JVM内存模型 以及分区,需要详细到每个区放什么 堆里面的分区:Eden,survival from to,老年代,各自的特点。 GC的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方 针

    2024年02月11日
    浏览(18)
  • java八股文面试[JVM]——垃圾回收器

    java八股文面试[JVM]——垃圾回收器

    jvm结构总结   常见的垃圾回收器有哪些?     CMS(Concurrent Mark Sweep) 整堆收集器 : G1 由于整个过程中 耗时最长 的 并发标记 和 并发清除 过程中,收集器线程都可以与用户线程一起工作,所以 总体上来说 ,CMS收集器的内存回收过程是与用户线程一起并发地执行。老年代收

    2024年02月11日
    浏览(14)
  • 面试官:如何进行 JVM 调优(附真实案例)

    面试官:如何进行 JVM 调优(附真实案例)

    面试官:在工作中做过 JVM 调优吗?讲讲做过哪些 JVM 调优? 我一个QPS不到10的项目,上次问我缓存穿透缓存雪崩,这次问我 JVM 调优,我是真滴难。 不过大家别慌,热心的我给大家找来了几个满分回答,大家选择合适的使用。 回答1:听好了,下面将是我第一次 JVM 调优。 回

    2024年01月19日
    浏览(11)
  • JVM调优篇:探索Java性能优化的必备种子面试题

    JVM调优篇:探索Java性能优化的必备种子面试题

    首先面试官会询问你在进行JVM调优之前,是否了解JVM内存模型的基础知识。这是一个重要的入门问题。JVM内存模型主要包括程序计数器、堆、本地方法栈、Java栈和方法区(1.7之后更改为元空间,并直接使用系统内存)。 正常堆内存又分为年轻代和老年代。在Java虚拟机中,年

    2024年02月15日
    浏览(11)
  • JVM——垃圾回收(垃圾回收算法+分代垃圾回收+垃圾回收器)

    JVM——垃圾回收(垃圾回收算法+分代垃圾回收+垃圾回收器)

    只要一个对象被其他对象所引用,就要让该对象的技术加1,某个对象不再引用其,则让它计数减1。当计数变为0时就可以作为垃圾被回收。 有一个弊端叫做循环引用,两个的引用计数都是1,导致不能作为垃圾回收,会造成内存泄露。 java虚拟机没有采用该算法。 该算法需要

    2024年02月12日
    浏览(10)
  • JVM——JVM 垃圾回收

    JVM——JVM 垃圾回收

    本节常见面试题 问题答案在文中都有提到 如何判断对象是否死亡(两种方法)。 简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。 如何判断一个常量是废弃常量 如何判断一个类是无用的类 垃圾收集有哪些算

    2024年02月12日
    浏览(9)
  • 【JVM】JVM 垃圾回收算法

    【JVM】JVM 垃圾回收算法

    目前JVM中有三种常见的垃圾回收算法,分别是:标记清除、标记整理和复制,这三种垃圾回收算法各有优缺点,下面逐一介绍。 在读本篇文章中,如果对JVM中哪个是垃圾,哪个不是垃圾,JVM到底是怎么知道的,请先读下面这篇文章 【JVM】JVM 判断对象存活算法(引用计数算法

    2024年02月09日
    浏览(12)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包