Java中几种常量池面试总结

字符串常量池(string pool)

字符串常量池是JVM为了提升性能和减少内存消耗针对字符串(String类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

当需要使用字符串时,先去字符串池中查看该字符串是否已经存在,如果存在,则可以直接使用,如果不存在,初始化,并将该字符串放入字符串常量池中。

字符串常量池的位置也是随着JDK版本的不同而位置不同。在JDK1.6及之前,字符串常量池的位置在永久代中,此时字符串常量池中存储的是字符串对象;在JDK1.7时,字符串常量池的位置从永久代移动到了Java堆中,此时,字符串常量池存储的就是字符串对象的引用,具体的实例对象是在堆中开辟的一块空间存放的;在JDK1.8及之后,永久代被元空间取代了。

HotSpot 虚拟机中字符串常量池的实现是 src/hotspot/share/classfile/stringTable.cpp ,StringTable 本质上就是一个HashSet ,容量为 StringTableSize(可以通过 -XX:StringTableSize 参数来设置)。

StringTable 中保存的是字符串对象的引用,字符串对象的引用指向堆中的字符串对象。

注意:在JDK1.7时,静态变量和字符串常量池一起从永久代中移动到了Java堆中。

JDK 1.7 为什么要将字符串常量池移动到堆中?

主要是因为永久代的GC回收效率太低,只有在整堆收集(Full GC)的时候才会被执行GC,而Java程序中通常有大量的被创建的字符串等待回收,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。

class文件中的常量池(class constant pool)

class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。

在Java源文件被编译成class文件时,其中的所有常量都会被存储在常量池中,而且每个常量在常量池中都有一个唯一编号,可以通过该编号来引用常量池中的常量。

常量池中每一项常量都是一个表,这 14 种表有一个共同的特点:开始的第一位是一个 u1 类型的标志位 -tag 来标识常量的类型,代表当前这个常量属于哪种常量类型。

为什么单独设置class文件常量池

Class文件常量池是为了解决Java虚拟机中内存分配和效率问题而设置的。具体来说,Java虚拟机中的内存是通过JVM在内存中开辟一块特殊区域来管理的,而这个特殊区域叫做方法区(Method Area),其中包含了Java程序中所有的类、接口、字段、方法等信息。而Java程序中的常量通常是在编译时确定的,如果每次程序运行时都需要重新分配内存来存储这些常量,会严重浪费内存资源和降低程序运行效率。

因此,在编译Java源文件时,编译器会将其中的所有常量存储在一个单独的常量池中,然后在运行时将这个常量池加载到内存中,同时将其中的符号引用解析为实际的对象和值。 这种常量池的设计,不仅可以提高内存利用率,还可以加快类加载、解析和执行代码的速度。同时,在运行时,如果需要修改常量池中的常量,只需要修改常量池中的相应项就可以了,无需重新进行内存分配和释放,也就节约了时间和资源。

总之,Java中的常量池设计,既可以提高内存利用率,又可以加快程序的运行速度,在Java程序中有着重要的作用。

字面量

字面量比较接近于 Java 语言层面的的常量概念,如文本字符串、声明为 final 的常量值等。

在计算机编程领域,字面量(literal)是指程序中硬编码的常量值,以及该常量的类型。比如,数字123、字符串"hello world"、true/false布尔值等就是字面量。

字面量通常是编译时就准备好的,程序执行时直接使用,不需要再进行计算或解释,即它们的值可以直接使用或检查。在代码中使用字面量具有以下优点:

  1. 代码可读性好,易于理解:由于字面量代表常量,直接写在程序中,直接阅读代码就可以理解它们代表的值,不需要查找其他地方的定义。

  2. 方便查错:字面量用于定义固定的常量值,可以降低因常量值错误带来的bug和问题。

  3. 编译器优化:编译器可以在编译时就进行一些优化,比如将字面量操作转换成简单的指令,提高程序的运行效率。

总之,字面量在程序中应用广泛,是编程中十分重要的概念。

符号引用

符号引用是一组用来描述所引用目标的符号,属于编译原理方面的概念,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,主要包括下面几类常量:

  1. 被模块导出或者开放的包(Package)
  2. 类和接口的全限定名(Full Qualified Name)
  3. 字段的名称和描述符(Descriptor)
  4. 方法的名称和描述符
  5. 方法的句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
  6. 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)

很多资料只提到了2—4,大家可以重点记一下。

在Java虚拟机中,符号引用是一种编译时的符号名称,它是指用来描述类、接口、字段和方法的名称。

简单来说,符号引用就是一个符号名称,比如类名、字段名、方法名等,它并不指向实际的内存地址,在运行时需要通过解析符号引用得到对应的实际内存地址。 换句话说,符号引用就是一种用于在程序中表示对某个类、属性或方法的引用的标记。

符号引用在Java程序中的使用很广泛,因为Java源代码中引用的类、属性或方法可能在编译时并没有被定义,而只在运行时才被加载到内存中。因此,编译器在编译Java源代码时使用符号引用,而在Java虚拟机运行时才根据符号引用解析出对应的实际内存地址,并执行对应的代码。

在Java虚拟机中,由于符号引用不直接指向实际内存地址,因此需要一些机制来解析符号引用,例如类加载器会加载字节码文件到内存中,并将符号引用转化为直接引用,或者JIT编译器在运行时第一次解析符号引用时,将其转换为直接引用。

直接引用

在JVM 类加载过程中,解析阶段,Java虚拟机将常量池内的符号引用替换为直接引用。直接引用可以帮助程序直接定位到所需的对象。

直接引用一般为下面三类:

  1. 直接指向目标的指针
  2. 相对偏移量
  3. 一个能够直接定位到目标的句柄

句柄

句柄是一个是用来标识对象或者项目的标识符,可以用来描述窗体、文件等,值得注意的是句柄不能是常量

偏移量

计算机汇编语言,是指把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为“有效地址或偏移量”。

直接引用适合虚拟机的布局相关,同一个符号引用在不同的虚拟机上翻译出来的直接引用一般会不一致。如果有了直接引用,那么引用目标必定已经被加载到了内存当中。

运行时常量池(runtime constant pool)

当Java文件被编译成class文件之后,也就是会生成我上面所说的class常量池,那么运行时常量池又是什么时候产生的呢?

JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,JVM就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在上面我也说了,class文件的常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中所引用的是一致的。

运行时常量池相对于class文件的常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。

总结:运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与字符串常量池中的引用值保持一致。

在这里插入图片描述

为什么单独设置运行时常量池

运行时常量池是为了加速Java程序的运行和节省内存资源而设置的。在Java虚拟机中,一些常量是在Java程序运行时动态生成的,如字符串连接表达式、调用方法返回值等。如果每次在程序执行这些操作时都需要重新分配内存来存储常量,会显著降低Java程序的性能。

因此,在运行时常量池中,JVM会将class文件常量池中的符号引用解析为实际的对象和值,并存储在运行时常量池中。这样,当程序需要使用这些常量时,只需要在运行时常量池中查找对应的值即可,而不需要重新分配内存,这样可以加快程序的执行速度,提高Java程序的效率。

另外,由于运行时常量池是每个线程私有的,相比于其他共享区域,如方法区,它更不容易发生线程安全问题。在多线程环境中,如果每个线程都有自己的运行时常量池,能够有效地保障线程安全,避免线程之间的干扰。

总之,运行时常量池的设计提高了Java程序的执行效率,同时也能保障程序的线程安全。在Java程序中,常常用到的字符串、数字等常量值都存储在运行时常量池中,它是Java虚拟机中的重要组成部分之一。

三种常量池之间的关系

在Java虚拟机中,有三种不同类型的常量池:字符串常量池、class文件常量池和运行时常量池。这三种常量池之间存在着紧密的关系。

首先,编译Java源文件时,编译器会将其中的所有字符串字面量和其他编译期声明的常量存储在class文件常量池中。因此,class文件常量池相当于是所有编译时常量的根源。

其次,当Java程序在运行时,JVM会将class文件常量池复制到内存中形成运行时常量池,以供程序在运行时动态使用。运行时常量池是每个线程私有的,用于存储常量池中常量的实际值,同时也存储着类、方法等的相关信息。

最后,字符串常量池是一种特殊的、系统级别的常量池,它用于存储所有字符串字面量的实例,以及其他头文件声明的常量实例,它是在程序运行时被创建。

因此,这三个常量池之间的关系如下:

  • class文件常量池:是所有编译时常量的根源,编译器会将其中的所有字符串字面量和其他编译期声明的常量存储在其中。
  • 运行时常量池:是从class文件常量池中复制得到的,在程序运行时会动态使用其中的常量。作为每个线程私有的内存区域,它存储着常量池中常量的实际值,同时也包含类、方法等的相关信息。
  • 字符串常量池:用于存储所有字符串字面量的实例,以及其他头文件声明的常量实例,它是在程序运行时被创建。

总之,这三个常量池之间相互衔接,紧密联系。在Java程序的编译、加载、解析和执行过程中都发挥着不可替代的

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java spring 实现 下载hls(m3u8+ts)实时流并进行合并mp4和压缩

参考连接 链接: java下载m3u8视频,解密并合并ts(三) 链接: Java 下载 HLS (m3u8) 视频 首先需要了解什么是HLS 链接: HTTP Live Streaming (HLS) - 概念 链接: M3U8是什么 简单理解就是, m3u8文件存放着可供客户端播放TS 片段 简单一点…

新写了的 AOP 日志切面,方便以后直接使用。

前言 最近项目进入联调阶段,服务层的接口需要和协议层进行交互,协议层需要将入参[json字符串]组装成服务层所需的json字符串,组装的过程中很容易出错。入参出错导致接口调试失败问题在联调中出现很多次,因此就想写一个请求日志切…

二叉搜索树(BST)详解

文章目录 性质二叉搜索树的遍历遍历伪代码实现 二叉搜索树的查找伪代码实现 二叉搜索树最大元素伪代码实现 二叉搜索树最小元素伪代码实现 二叉搜索树的插入伪代码实现 二叉搜索树的删除删除叶子节点(对应上面第一种情况):删除度为1的节点&am…

多维时序 | MATLAB实现BP神经网络多变量时间序列预测(考虑历史特征的影响,多指标、多图输出)

多维时序 | MATLAB实现BP神经网络多变量时间序列预测(考虑历史特征的影响,多指标、多图输出) 目录 多维时序 | MATLAB实现BP神经网络多变量时间序列预测(考虑历史特征的影响,多指标、多图输出)预测效果基本介绍程序设计学习总结参考资料预测效果 基本介绍 MATLAB实现BP神经网…

【Fluent】接着上一次计算的结果继续计算,利用计算过程中得到的物理场(温度、速度、压力等)插值Interpolate文件初始化模型的方法

一、问题背景 因为fluent中支持的初始化无非三种类型。 1、Standard initialization 标准初始化 2、Hybridinitialization 混合初始化 3、FMG initialization FMG初始化 另外,还可以用UDF通过坐标判断的方式予以初始化。 但是这些初始化方法都没办法利用以前计算过…

电子数据取证之宝塔面板

一、宝塔面板介绍 1、官网bt.com,是提升运维效率的服务器管理软件,支持一键WAMP/LAMP/LNMP等100多项服务器管理功能;是跨平台的软件,同时支持Windows和Linux。开源永久免费。提高工作效率,对小白比较友好。 2、怎么看服…

操作系统2(多处理器编程)

一、并发 1.操作系统是最早的并发程序之一 2.并发的基本单位:线程 共享内存的多个执行流 执行流拥有独立的堆栈/寄存器共享全部的内存(指针可以互相引用) 3.实现原子性 lock(&lk)unlock(&lk) 实现临界区(critical section)之间…

Redis监控步骤get!Google精髓的四大法则直接掌握

Redis也是对外服务,所以Google四个黄金指标同样适用,还从延迟、流量、错误、饱和度分析Redis关键指标。 1 延迟 选择Redis是想得到更快响应速度和更高吞吐量,所以延迟数据对使用Redis的应用程序至关重要。 1.1 如何监控延迟 ① 客户端应用…

C++之深入解析如何实现一个线程池

一、基础概念 当进行并行的任务作业操作时,线程的建立与销毁的开销是,阻碍性能进步的关键,因此线程池,由此产生。使用多个线程,无限制循环等待队列,进行计算和操作,帮助快速降低和减少性能损耗…

Linux安装Mysql操作步骤详解

目录 1. 检测当前系统中是否安装了MySql数据库 2. 使用FinalShell自带的上传工具将jdk的二进制发布包上传到Linux 3. 解压并解包到/usr/local/mysql(便于区分) 第一步:将包先移动到该目录下 第二步:解压解包 第三步&#xff1a…

springboot的创建和使用

目录 1.springboot的优点 2.springboot项目创建 2.1使用idea创建 2. 2 ⽹⻚版创建 3.项⽬⽬录介绍和运⾏ 3.1运行项目 3.2输出hello world 4.注意事项 1.路径 2.约定大于配置 spring的诞生为了简化java程序,springboot的诞生为了简化spring程序开发 1.springboot的优点…

了解标量、向量和点积

数据科学基础数学:线性代数简介 了解标量、向量和点积 机器只能按着算法理解和处理数据结构存储的数字. 例如创建垃圾邮件检测器,则首先必须将文本数据转换为数字(通过单词嵌入)。 两个句子之间的余弦相似性 两个句子之间的余弦相似性可以通过它们的向量…

Python小姿势 - Python使用Jupyter Notebook

Python使用Jupyter Notebook Jupyter Notebook是一个开源的Web应用程序,可以用来创建和共享包含 live code,公式,可视化和解释性文本的文档。 安装Jupyter Notebook 首先,确保你安装了正确的Python版本和包管理器(pip&…

java中的\t说明

阅读前请看一下:我是一个热衷于记录的人,每次写博客会反复研读,尽量不断提升博客质量。文章设置为仅粉丝可见,是因为写博客确实花了不少精力。希望互相进步谢谢!! 文章目录 阅读前请看一下:我是…

加载自己的图像数据集

文章目录 1 加载图像数据集2 图像预处理3 再次加载数据集4 这里还有一个问题,我们没有验证集5 构建DataLoader6 检查是否正确导入数据集 原文链接:《加载自己的图像数据集》 ​ 数据集下载链接 1 加载图像数据集 目录结构: 针对这种非常典型…

while语句和until语句顺便带点小实验

while语句和until语句 一、while用法二、Until循环语句三、趣味小实验猜价格的游戏(价格是随机数)写一个计算器脚本闲来无事去购物 一、while用法 for循环语句非常适用于列表对象无规律,且列表来源以固定(如某个列表文件&#xf…

nginx配置sh脚本远程执行一键安装

背景 本地多机重复操作某些shell指令,分步执行,很耗费时间, 需要远程一键部署,傻瓜化运维,更为通用安装。 即参考docker通用安装 sudo curl https://get.docker.com | sh - # sudo python3 -m pip install docker-co…

Design_transformer

磁性元件设计 思路 滤波电感设计 磁芯不要饱和(开气隙) 考虑铜损大于铁损 谐振电感设计 磁芯不要饱和(开气隙) 考虑铁损大于铜损 变压器设计 磁芯不要饱和(开气隙) 励磁电流产生磁场 开气隙 增加了…

FreeRTOS系统学习-内核篇.01-数据结构---列表与列表项定义详解-链表节点插入实验

# 内核篇.01 列表与列表项 为什么要学列表?链表单向链表双向链表 FreeRTOS 中链表的实现节点节点初始化尾节点根节点链表根节点初始化将节点插入到链表的尾部将节点按照升序排列插入到链表将节点从链表删除节点带参宏小函数 链表节点插入实验实验现象 为什么要学列表…

内存优化-比glibc更快的tcmalloc

TCMalloc 是 Google 开发的内存分配器,在不少项目中都有使用,例如在 Golang 中就使用了类似的算法进行内存分配。它具有现代化内存分配器的基本特征:对抗内存碎片、在多核处理器能够 scale。据称,它的内存分配速度是 glibc2.3 中实…
最新文章