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

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.与之前的内存差异明显。

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

直接内存

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

定义

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

基本使用

NIO的 directByteBuffer

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

 

传统IO操作的过程。 

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

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

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

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

directByteBuffer的过程

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

内存溢出

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

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

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

 分配和释放原理

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

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

 运行之后可以看见进程占用了一个G的内存。

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

原理说明 

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

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

分配之后 

 释放之后

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

 源码分析

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

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

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

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

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

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

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

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

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

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

然后运行之后System.gc()会失效,直接内存无法被回收,ByteBuffer只能等到真正的垃圾回收才会被清理。造成直接内存长时间占用较大。 

 解决方案

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

手动管理

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

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

相关文章

vue 使用C-Lodop打印小票

先从官网下载js文件 https://www.lodop.net/LodopDemo.html 打开安装程序&#xff0c;一直下一步既可&#xff0c;我这边已经安装过就不演示了。 // 引入 import { getLodop } from /utils/CLodopfuncs.js;// 使用 let LODOP getLodop()let Count LODOP.GET_PRINTER_COUNT…

13. Docker实战之安装MySQL

目录 1、前言 2、部署MySQL 2.1、Docker仓库查看镜像 2.2、拉取MySQL镜像 2.3、创建持久化目录 2.4、启动MySQL容器 2.5、查看宿主机上的MySQL目录 2.6、本地MySQL测试 2.7、新建MySQL用户&#xff0c;配置远程访问 2.8、本地Navicat连接测试 3、为什么数据库不适合D…

学校宿舍门禁:如何做好考勤管理?

随着科技的不断发展&#xff0c;传统的考勤方式逐渐显现出其局限性&#xff0c;而人脸识别技术作为一种快速、准确、无需直接接触的身份验证手段&#xff0c;为宿舍管理带来了全新的可能性。 宿舍管理能够让管理员随时通过系统监控宿舍内的人员流动情况&#xff0c;当系统检测到…

WebGL 变量uniform、gl.getUniformLocation、gl.uniform4f及其同族函数相关

目录 uniform变量命名规范 获取 uniform 变量的存储地址 gl.getUniformLocation 向uniform变量赋值 gl.uniform4f ​编辑 gl.uniform4f()的同族函数 demo&#xff1a;点击webgl坐标系的四个象限绘制各自不同颜色的点 uniform变量命名规范 var FSHADER_SOURCE uniform vec4…

word文件怎么免费转换为pdf格式?

大家在编辑word文件的时候&#xff0c;可能需要进行格式的转换&#xff0c;比如将word转换为pdf格式这时候需要使用工具软件。接下来小编就给大家介绍word文件怎么免费转换为pdf格式&#xff0c;免费word转pdf格式的方法。 免费word转pdf格式的方法 我们需要在电脑中安装并打开…

Failed to load property source from location ‘classpath:/application.yml‘

项目场景&#xff1a; 今天到公司启动项目&#xff0c;突然发现项目起不起来了 问题描述 出现 Failed to load property source from location ‘classpath:/application.yml’ 错误 原因分析&#xff1a; 刚开始以为是 application.yml 中格式错误&#xff0c;但是发现同…

python matlab 画坐标图

画一个坐标系&#xff0c;同时显示两条直线&#xff0c;效果图如下&#xff1a; 功能点&#xff1a; 同时显示两个纵坐标数据 显示图片名称 图片最大化保存 到本地 在图片某个位置显示字符信息 不同的线名称提示 代码如下&#xff1a; import matplotlib.pyplot as pltde…

uniapp微信小程序点击右上角菜单分享功能权限配置

个人项目地址&#xff1a; SubTopH前端开发个人站 &#xff08;自己开发的前端功能和UI组件&#xff0c;一些有趣的小功能&#xff0c;感兴趣的伙伴可以访问&#xff0c;欢迎提出更好的想法&#xff0c;私信沟通&#xff0c;网站属于静态页面&#xff09; SubTopH前端开发个人站…

ubuntu18.04安装keil5并迁移stm32项目(踩坑)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、安装winewine的总结 二、安装Keil5总结 前言 切记看完再享用&#xff0c;别直接上手&#xff0c;不然安装的时候会和我一样踩坑的&#xff08;走了很多弯路…

七夕特辑(一)浪漫表白方式 用神经网络生成一首情诗

目录 一、准备工作二、用神经网络生成一首诗&#xff0c;代码说明 牛郎织女相会&#xff0c;七夕祝福要送来。祝福天下有情人&#xff0c;终成眷属永相伴。 七夕是中国传统的情人节&#xff0c;也是恋人们表达爱意的好时机。在这个特别的日子里&#xff0c;送上温馨的祝福&…

社科院与美国杜兰大学金融管理硕士项目——畅游于金融世界

随着社会经济的不断发展&#xff0c;职场竞争愈发激烈&#xff0c;很多同学都打算通过报考研究生来实现深造&#xff0c;提升自己的综合能力和竞争优势&#xff0c;获得优质的证书。而对于金融专业的学生和在职人员来说&#xff0c;社科院与美国杜兰大学金融管理硕士项目是一个…

12.串,串的存储结构与模式匹配算法

目录 一. 一些术语 二. 串的类型定义 &#xff08;1&#xff09;串的顺序存储结构 &#xff08;2&#xff09;串的链式存储结构 三. 串的模式匹配算法 &#xff08;1&#xff09;BF算法 &#xff08;2&#xff09;KMP算法 四. 案例实现 串(String)---零个或多个任意字符…

函数式编程-Stream流学习第一节

1 为什么学习 1.现在很多公司在编程中大量使用函数式编程-Stream流格式代码&#xff0c;所以为了能够看懂公司的代码 2.大量数据下处理集合效率高--因为有并行流 3.代码可读性高 4.消灭嵌套地狱 2 函数式编程思想 2.1 概念 面向对象编程是关注于用对象完成什么事情。而函数式…

pm4py使用指南(非机翻)

目录 1. 日志数据读取及预处理&#xff08;1&#xff09;查看case和event数量&#xff08;2&#xff09;查看起始事件和结束事件&#xff08;3&#xff09;时间戳格式的问题 2. 日志数据过滤3. 流程发现4. 模型转化5. 模型可视化 1. 日志数据读取及预处理 通过 pandas库 读取c…

数据结构-Java逆天操作

本文章会对Java线性表的相关知识进行讲解&#xff0c;也会以Java代码示例来进行解释 对线性表的讲解分析 定义 线性表是一种数据结构&#xff0c;它是由一系列具有相同类型的元素组成的有序集合。线性表中的元素按照线性的顺序 排列&#xff0c;每个元素只有一个前驱元素和一…

对于pycharm 运行的时候不在cmd中运行,而是在python控制台运行的情况,如何处理?

对于pycharm 运行的时候不在cmd中运行&#xff0c;而是在python控制台运行的情况&#xff0c;如何处理&#xff1f; 比如&#xff0c;你在运行你的代码的时候 它总在python控制台运行&#xff0c;十分难受 解决方法 在pycharm中设置下即可&#xff0c;很简单 选择运行点击…

python爬虫10:selenium库

python爬虫10&#xff1a;selenium库 前言 ​ python实现网络爬虫非常简单&#xff0c;只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点&#xff0c;方便以后复习。 申明 ​ 本系列所涉及的代码仅用于个人研究与讨论&#xff0c;并不会对网站产…

LeetCode--HOT100题(34)

目录 题目描述&#xff1a;94. 二叉树的中序遍历&#xff08;简单&#xff09;题目接口解题思路1代码解题思路2代码 PS: 题目描述&#xff1a;94. 二叉树的中序遍历&#xff08;简单&#xff09; 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 LeetCode做…

爆火「视频版ControlNet」开源了!靠提示词精准换画风,全华人团队出品

“视频版ControlNet”来了&#xff01; 让蓝衣战神秒变迪士尼公举&#xff1a; 视频处理前后&#xff0c;除了画风以外&#xff0c;其他都不更改。 女孩说话的口型都保持一致。 正在插剑的姜文&#xff0c;也能“下一秒”变猩球崛起了。 这就是由全华人团队打造的最新视频处理…

性能调优篇 二、Jvm监控及诊断工具-命令行篇

目录 一、概述1、简单命令行工具 二、jps&#xff1a;查看正在运行的Java程序&#xff08;掌握&#xff09;1、是什么&#xff1f;2、测试3、基本语法 三、jstat&#xff1a;查看jvm统计信息&#xff08;掌握&#xff09;1、是什么&#xff1f;2、基本语法3、补充 四、jinfo&am…