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.mzph.cn/news/44063.shtml

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

相关文章

计算机组成与设计 Patterson Hennessy 笔记(二)MIPS 指令集

计算机的语言&#xff1a;汇编指令集 也就是指令集。本书主要介绍 MIPS 指令集。 汇编指令 算数运算&#xff1a; add a,b,c # abc sub a,b,c # ab-cMIPS 汇编的注释是 # 号。 由于MIPS中寄存器大小32位&#xff0c;是基本访问单位&#xff0c;因此也被称为一个字 word。M…

输入输出+暴力模拟入门:魔法之树、染色の树、矩阵、字母加密、玫瑰鸭

秋招实习刷题网站推荐&#xff1a;codefun2000.com&#xff0c;还有题解博客&#xff1a;blog.codefun2000.com/。以下内容都是来自塔子哥的~ 输入输出 2023.04.15-春招-第三题-魔法之树 //#include<bits/stdc.h> #include<vector> #include<iostream>usin…

Datawhale Django后端开发入门Task01 Vscode配置环境

首先呢放一张运行成功的截图纪念一下&#xff0c;感谢众多小伙伴的帮助呀&#xff0c;之前没有配置这方面的经验 &#xff0c;但还是一步一步配置成功了&#xff0c;所以在此以一个纯小白的经验分享如何配置成功。 1.选择要建立项目的文件夹&#xff0c;打开文件找到目标文件夹…

csapp archlab PartC满分解答

任务 修改ncopy.ys和pipe-full.hcl以尽可能的提高ncopy.ys的运行速度 思路 pipe-full.hcl&#xff1a; 实现iaddq指令&#xff08;家庭作业4.54&#xff09;实现加载转发&#xff08;家庭作业4.57&#xff09; ncopy.ys&#xff1a; 使用循环展开&#xff08;第5.8节&…

openai多模态大模型:clip详解及使用

引言 CLIP全称Constrastive Language-Image Pre-training&#xff0c;是OpenAI推出的采用对比学习的文本-图像预训练模型。CLIP惊艳之处在于架构非常简洁且效果好到难以置信&#xff0c;在zero-shot文本-图像检索&#xff0c;zero-shot图像分类&#xff0c;文本→图像生成任务…

windows服务器下java程序健康检测及假死崩溃后自动重启应用、开机自动启动

前两天由于项目需要&#xff0c;一个windows上的批处理任务&#xff08;kitchen.bat&#xff09;&#xff0c;需要接到mq的消息通知后执行&#xff0c;为了快速实现这里我们通过springboot写了一个jar程序&#xff0c;用于接收mq的消息&#xff0c;并调用bat文件。 本程序需要实…

缺少或找不到vcruntime140_1.dll的解决方法

某天&#xff0c;当我准备打开电脑上的一个应用程序时&#xff0c;突然收到一个错误提示&#xff0c;显示缺少了vcruntime140_1.dll文件。这个文件是一个重要的系统组件&#xff0c;它的丢失导致了我无法正常运行该应用程序。于是&#xff0c;我开始了一场寻找和修复旅程。然而…

这是我的纪念日

我的1460天 写这篇文章&#xff0c;是为了纪念自己这一千多个日日夜夜&#xff0c;我的热爱总算有了回报。 每次看到有小伙伴点赞&#xff0c;评论的时候&#xff0c;我都很开心&#xff0c;我知道自己的选择是正确的&#xff0c;我喜欢分享自己的所见所学&#xff0c;我也很…

python3.7 安装pywin32报错,完美解决方法

本机环境 python&#xff1a;3.7 遇到2种报错 第一种 ImportError: DLL load failed: The specified module could not be found.第二种&#xff1a; import win32gui ModuleNotFoundError: No module named ‘win32gui‘解决方法 我安装pywin32时候&#xff0c;是直接pi…

stm32红绿灯源代码示例(附带Proteus电路图)

本代码不能直接用于红路灯&#xff0c;只是提供一个思路 #include "main.h" #include "gpio.h" void SystemClock_Config(void); void MX_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStruct {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOB_CLK_ENAB…

Jenkins-CICD-python/Java包升级与回退

Jenkins- CICD流水线 python/Java代码升级与回退 1、执行思路 1.1、代码升级 jenkins上点击 upgrade和 代码版本号 --${tag} jenkins 推送 代码 和 执行脚本 到目标服务器/opt目录下 执行命令 sh run.sh 代码名称 版本号 upgrade 版本号 来自jenkins的 构建参数中的 标签…

Gitlab-第四天-CD到k8s集群的坑

一、.gitlab-ci.yml #CD到k8s集群的 stages: - deploy-test build-image-deploy-test: stage: deploy-test image: bitnami/kubectl:latest # 使用一个包含 kubectl 工具的镜像 tags: - k8s script: - ls -al - kubectl apply -f deployment.yaml # 根据实际情况替换…

LlamaGPT -基于Llama 2的自托管类chatgpt聊天机器人

LlamaGPT一个自托管、离线、类似 ChatGPT 的聊天机器人&#xff0c;由 Llama 2 提供支持。100% 私密&#xff0c;不会有任何数据离开你的设备。 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 1、如何安装LlamaGPT LlamaGPT可以安装在任何x86或arm64系统上。 首先确保…

PHP8的字符串操作3-PHP8知识详解

今天继续分享字符串的操作&#xff0c;前面说到了字符串的去除空格和特殊字符&#xff0c;获取字符串的长度&#xff0c;截取字符串、检索字符串。 今天继续分享字符串的其他操作。如&#xff1a;替换字符串、分割和合成字符串。 5、替换字符串 替换字符串就是对指定字符串中…

vue浏览器插件安装-各种问题

方法1&#xff1a;vue.js devtolls插件下载 https://blog.csdn.net/qq_55640378/article/details/131553642 下载地址&#xff1a; Tags vuejs/devtools GitHub npm install 或是 cnpm install 遇到的报错 设置淘宝镜像源&#xff08;推荐使用nrm&#xff0c;这一步是为…

使用IText导出复杂pdf

1、问题描述 需要将发票导出成pdf&#xff0c;要求每页都必须包含发票信息和表头行。 2、解决方法 使用IText工具实现PDF导出 IText8文档&#xff1a;Examples (itextpdf.com) 3、我的代码 引入Itext依赖&#xff0c;我这里用的是8.0.1版本 <dependency><groupId>…

uniapp 上传比较大的视频文件就超时

uni.uploadFile&#xff0c;上传超过10兆左右的文件就报错err&#xff1a;uploadFile:fail timeout&#xff0c;超时 解决&#xff1a; 在manifest.json文件中做超时配置 uni.uploadFile({url: this.action,method: "POST",header: {Authorization: uni.getStorage…

Python编程——列表解析与常用操作

作者&#xff1a;Insist-- 个人主页&#xff1a;insist--个人主页 本文专栏&#xff1a;Python专栏 专栏介绍&#xff1a;本专栏为免费专栏&#xff0c;并且会持续更新python基础知识&#xff0c;欢迎各位订阅关注。 目录 一、列表是什么&#xff1f; 二、列表的特点 1、元素…

pyltp 0.2.1安装

1. LTP及pyltp pyltp是 LTP的 Python封装&#xff0c;它里面提供了包括分词&#xff0c;词性标注&#xff0c;命名实体识别&#xff0c;句法分析等等能力。 比较坑的是我们可能无法直接通过pip install pyltp0.2.1方式来安装&#xff0c;所以本文就简单记录下如何通过源码安装…

商城-学习整理-高级-性能压测缓存问题(十一)

目录 一、基本介绍1、性能指标2、JMeter1、JMeter 安装2、JMeter 压测示例1、添加线程组2、添加 HTTP 请求3、添加监听器4、启动压测&查看分析结果 3、JMeter Address Already in use 错误解决 二、性能监控1、jvm 内存模型2、堆3、jconsole 与 jvisualvm1、jvisualvm 能干…