关于java字节码文件加载过程中,各种变量和常量的存储位置

java变量存储位置

前提

下面的东西都是在jdk1.8基础上做出的探究

首先我们先解释一下在各种网站上出现的一些名词到底是什么?

1.字面量:声明为final的int、long、double、char等基本类型的常量值(如final int a = 1 这是一个常量值,但是int a =1 这是一个变量值),特殊的字符串文本"abc",abc也是字面量,字面量一出生,其内容、大小全部固定,不会发生改变,编译时期存储在class文件的常量池中,可以在类加载阶段后存放到方法区的运行时常量池中。

class文件的常量池:一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述符信息外,还包含一项信息就是常量池表(Constant Pool Table),包括各种字面量和对类型、域和方法的符号引用。

2.符号引用:符号引用主要包含下面三类常量 

(1)类和接口的全限定名(Fully Qualified Name)

(2)字段的名称和描述符(Descriptor)

(3)方法的名称和描述符

3.对象和引用:

对象是存储在堆内存中的实体,它包含了数据和方法,当创建一个对象时候,实际上在内存中分配了一块空间来存储该对象的数据。

引用是一个变量,它存储了一个对象的内存地址(即对象在堆内存中的位置)。这样你可以通过引用来操作和访问对象。Person p = new Person()person 是一个引用,然后指向的是堆内存中的对象实体。

4.虚拟机栈是jvm内存模型的一部分,方法区也是,但是jdk8中方法区实际实现是元空间,元空间在本地内存当中。元空间的大小仅受本地内存限制

5.元空间

  元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制

探究方法区

我们就从我们classloader将class字节码文件记载到jvm内存中来进行探究,各种变量分别存储在了什么位置。

首先我们看一下方法区(逻辑概念)包含什么

方法区包含:
        运行时常量池
        自动和方法数据
        构造函数和普通方法的字节码内容
        一些特殊方法
这里虽然没有说明“字符串常量池”,但是它也是方法区的一部分。

取消永久代后,使用元空间来实现方法区。
        在JDK1.8中,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。注意这里的剩余内容:说明原来移除从永久代移出的字符串常量池,静态常量,在更换了方法区实现后,并没有顺势进入到元空间,那么它们到哪里去了呢?

字符串常量池和运行时常量池究竟去了哪里?

        在JDK1.8中,使用元空间代替永久代来实现方法区,但是方法区并没有改变,所谓"Your father will always be your father",变动的只是方法区中内容的物理存放位置。正如上面所说,类型信息(元数据信息)等其他信息被移动到了元空间中;但是运行时常量池和字符串常量池被移动到了堆中。但是不论它们物理上如何存放,逻辑上还是属于方法区的。

        JDK1.8中字符串常量池和运行时常量池逻辑上属于方法区,但是实际存放在堆内存中,因此既可以说两者存放在堆中,也可以说两则存在于方法区中,这就是造成误解的地方。

结论

 好了按照上面的说法,我们在加载class字节码文件的时候,直接将字节码文件当中的常量池中的字面量和符号引用保存在了我们的方法区,保存在什么位置中了呢,运行时常量池。那么我们就有疑问了,那么符号引用所对应的对象存储在哪里了呢,存储在了堆中。

下面探究在加载class字节码时候不同位置的变量存储在什么地方

(1)对于局部变量肯定就是定义在方法中的变量,如果是基本类型,会把值直接存储在栈帧当中的局部变量表中,而栈帧所在位置就是虚拟机栈;如果是引用类型,会把这个对象的引用存储在栈帧当中,而对象实例会存储在堆中。栈帧退出去之后,堆当中的对象就没有其他的引用了,这时候就会被回收。

(2)而类的成员变量,不管是基本类型还是引用类型都存储在这个对象当中,这个对象存储在堆当中,所以就是存储在堆当中。

(3)对于静态变量,是在堆当中进行存储 

·        ②JDK8.0开始,static修饰的成员变量位于堆空间中
 说明 : 当类加载器将含有static修饰的成员变量的类加载到方法区时,会根据反射机制生成一 个字节码文件对象,即Class对象。Class对象在堆空间中,而static变量保存在Class实例的尾部。如下图所示 : (即所有对象访问的某个类变量,其实就是那一份

静态变量根本不能在方法中进行定义。

public class Test {

// 成员变量,存放在堆中

int a = 10;

// 被 static 修饰,也存放在堆中,但属于类,不属于对象 // JDK1.7 静态变量从永久代移动了 Java 堆中

static int b = 20;

public void method() {

// 局部变量,存放在栈中

int c = 30;

static int d = 40; // 编译错误,不能在方法中使用 static 修饰局部变量

}

}

下面探究各种常量池

字符串常量池

字符串常量池是什么?

在 HotSpot VM 里实现的 string pool 功能的是一个 StringTable 类,它是一个 Hash 表,默认值大小长度是1009;里面存的是驻留字符串的引用(而不是驻留字符串实例自身)。也就是说某些普通的字符串实例被这个 StringTable 引用之后就等同被赋予了“驻留字符串”的身份。这个 StringTable 在每个 HotSpot VM 的实例里只有一份,被所有的类共享。

StringTable 本质上就是个 HashSet<String>。这是个纯运行时的结构,而且是惰性(lazy)维护的。注意它只存储对java.lang.String 实例的引用,而不存储 String 对象的内容。 注意,它只存了引用,根据这个引用可以得到具体的 String 对象。

关于字符串常量池 更加详细的信息,请看我的另一篇博客 java中的字符串池(创建字符串时的执行流程)-CSDN博客

class 文件常量池(class constant pool)

我们都知道,class 文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。 字面量比较接近 Java 语言层面常量的概念,如文本字符串、被声明为 final 的常量值等。 符号引用则属于编译原理方面的概念,包括了如下三种类型的常量:

  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

运行时常量池

运行时常量池是方法区的一部分。

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

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

封装类常量池


以下几个封装类Byte,Short,Integer,Long,Character,Boolean在Java中实现了常量池(Double和Float没有)

Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False

不同封装类常量池的范围有限:

Byte,Short,Integer,Long : [-128~127]
Character : [0~127]
Boolean : [True, False]

因此会产生以下差异

Integer i1 = 127; 
Integer i2 = 127; 
System.out.println(i1 == i2);

Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);

结果是 True Flase
 

工作机制

关于 JVM 执行的时候,还涉及到了字符串常量池

在类加载阶段, JVM 会在堆中创建对应这些 class 文件常量池中的字符串对象实例,并在字符串常量池中驻留其引用。具体在 resolve 阶段执行。这些常量全局共享。

这里说的比较笼统,没错,是 resolve 阶段(将运行时常量池中的符号引用转为直接引用 可能是堆当中地址),但是并不是大家想的那样,立即就创建对象并且在字符串常量池中驻留了引用。 JVM 规范里明确指定 resolve 阶段可以是 lazy 的。

VM 规范里 Class 文件常量池项的类型,有两种东西:CONSTANT_Utf8 和CONSTANT_String。前者是 UTF-8 编码的字符串类型,后者是 String 常量的类型,但它并不直接持有 String 常量的内容,而是只持有一个 index,这个 index 所指定的另一个常量池项必须是一个 CONSTANT_Utf8 类型的常量,这里才真正持有字符串的内容。

在HotSpot VM中,运行时常量池里,

CONSTANT_Utf8 -> Symbol*(一个指针,指向一个Symbol类型的C++对象,内容是跟Class文件同样格式的UTF-8编码的字符串)
CONSTANT_String -> java.lang.String(一个实际的Java对象的引用,C++类型是oop)

CONSTANT_Utf8 会在类加载的过程中就全部创建出来,而 CONSTANT_String 则是 lazy resolve 的,例如说在第一次引用该项的 ldc 指令被第一次执行到的时候才会 resolve。那么在尚未 resolve 的时候,HotSpot VM 把它的类型叫做JVM_CONSTANT_UnresolvedString,内容跟 Class 文件里一样只是一个 index;等到 resolve 过后这个项的常量类型就会变成最终的 JVM_CONSTANT_String,而内容则变成实际的那个 oop。

看到这里想必也就明白了, 就 HotSpot VM 的实现来说,加载类的时候,那些字符串字面量会进入到当前类的运行时常量池,不会进入全局的字符串常量池(即在 StringTable 中并没有相应的引用,在堆中也没有对应的对象产生)。所以上面提到的,经过 resolve 时,会去查询全局字符串池,最后把符号引用替换为直接引用。(即字面量和符号引用虽然在类加载的时候就存入到运行时常量池,但是对于 lazy resolve 的字面量,具体操作还是会在 resolve 之后进行的。)

通俗点讲就是 一开始加载字节码时候,在链接阶段实际上不会直接进行resolve,只有用到这个引用的时候才进行resolve,经过 resolve 时,会去查询全局字符串池,最后把符号引用替换为直接引用。具体resolve是如何查询的如下:

 在遇到 String 类型常量时,resolve 的过程如果发现 StringTable 已经有了内容匹配的 java.lang.String 的引用,则直接返回这个引用;反之,如果 StringTable 里尚未有内容匹配的 String 实例的引用,则会在 Java 堆里创建一个对应内容的 String 对象,然后在 StringTable 记录下这个引用,并返回这个引用。

用图解的方式展示:

String s1 = "abc";resolve 过程在字符串常量池中发现没有”abc“的引用,便在堆中新建一个”abc“的对象,并将该对象的引用存入到字符串常量池中,然后把这个引用返回给 s1。

String s2 = "abc"; resolve 过程会发现 StringTable 中已经有了”abc“对象的引用,则直接返回该引用给 s2,并不会创建任何对象。

String s3 = "xxx"; 同第一行代码一样,在堆中创建对象,并将该对象的引用存入到 StringTable,最后返回引用给 s3。

总结

1、全局字符串常量池在每个 VM 中只有一份,存放的是字符串常量的引用值。

2、class 常量池是在编译的时候每个 class 都有的,在编译阶段,存放各种字面量和符号引用。

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

4、class 文件常量池中的字符串字面量在类加载时进入到运行时常量池,在真正在 resolve 阶段(即执行 ldc 指令时)时将该字符串的引用存入到字符串常量池中,另外运行时常量池相对于 class 文件常量池具备动态性,有些常量不一定在编译期产生,也就是并非预置入 class 文件常量池的内容才能进入到方法区运行时常量池,运行期间通过 intern 方法,将字符串常量存入到字符串常量池中和运行时常量池

5.总结起来就是我们classsloader将class字节码文件加载到我们jvm方法区中,然后class当中的class文件常量池中的符号引用放在运行时常量池中,然后符号引用对应的对象存储在堆中,但是何时在堆中创建对象,何时将对象引用放在常量池中,有一个触发时机,是lazy的。

最后附上一张图

这是我个人的学习见解,如果有错误希望大佬指正。

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

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

相关文章

宝塔面板系列——两种方式安装青龙面板

因为最近旧windows服务器到期了&#xff0c;在搬服务器&#xff0c;新服务器尝试用Linux系统。过程中有很多不懂的地方&#xff0c;只能边搬迁边学边弄&#xff0c;顺带记录下来&#xff0c;哪天又要搬迁了&#xff0c;翻翻自己的文章也就一应俱全了。 非科班出身&#xff0c;选…

yarn安装包时报错error Error: certificate has expired

安装教程&#xff1a; 配置镜像地址&#xff1a; npm config set registry https://registry.npmmirror.com//镜像&#xff1a;https://developer.aliyun.com/mirror/NPM 安装yarn&#xff1a; npm install --global yarn查看版本&#xff1a; yarn --version卸载&#xff…

力扣题库27题移除元素(c语言)

解法&#xff1a; int removeElement(int* nums, int numsSize, int val) {int src0,dst0;while(src<numsSize){if(nums[src]val){src;}else{nums[dst]nums[src];src;dst;}}return dst; }

Vue el-table 合并单元格

一般常见的就是下图这种的单列&#xff0c;上下重复进行合并。 有时候可能也会需要多行多列的合并。 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content&qu…

搭建GItlab实现自动化部署Springboot项目(超详细)

提示&#xff1a;本例程中使用Docker搭建GItlab&#xff0c;Gitlab runner 通过编写CICD文件实现Springboot项目自动部署。 1、拉取GitLab镜像 命令&#xff1a; docker pull gitlab/gitlab-ce2、部署Gitlab&#xff1a; 我们通过docker搭建的gitlab部署项目的时候会出现一个…

Springboot+Vue前后端分离的在线商城系统

项目介绍 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本ONLY在线商城系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

PHP页面如何实现设置独立访问密码

PHP网页如果需要查看信息必须输入密码&#xff0c;验证后才可显示出内容的代码如何实现&#xff1f; 对某些php页面设置单独的访问密码,如果密码不正确则无法查看内容,相当于对页面进行了一个加密。 如何实现这个效果&#xff0c;详细教程可以参考&#xff1a;PHP页面如何实现…

从零开始学习在VUE3中使用canvas(二):fillStyle(填充样式)

一、fillStyle概念 在canvas中我们可以用fillStyle定义接下来的图像的样式&#xff0c;默认为黑色#000。 我们可以使用纯色、渐变、和纹理&#xff08;例如图片&#xff09;进行填充&#xff0c;来达到自己想要的效果。 二、代码 <template><div class"canva…

ideaSSM 工厂效能管理系统bootstrap开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 idea 开发 SSM 工厂效能管理系统是一套完善的信息管理系统&#xff0c;结合SSM框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff…

测试开发工程师(QA)职业到底需要干些什么?part2:服务端QA

服务端QA测试开发工作主要涉及测试和确保服务端应用程序的质量、稳定性和性能。以下是服务端QA测试开发人员在工作中可能涉及的任务和职责 编写测试计划和测试用例&#xff1a;QA测试开发人员负责编写详细的测试计划和测试用例&#xff0c;以覆盖服务端应用程序的各个功能和场景…

MYSQL索引、事务、存储引擎(一)

目录 一、索引 1、索引的概念 2、索引的作用 3、索引的副作用 4、创建索引的原则依据 二、索引的分类 1、普通索引 1.直接创建索引 2.修改方式创建索引 3.创建表的时候指定索引 2、唯一索引 1.直接创建唯一索引 2.修改表方式创建索引 3.创建表的时候指定索引 3、…

C# Onnx Yolov9 Detect 物体检测

目录 介绍 效果 项目 模型信息 代码 下载 C# Onnx Yolov9 Detect 物体检测 介绍 yolov9 github地址&#xff1a;https://github.com/WongKinYiu/yolov9 Implementation of paper - YOLOv9: Learning What You Want to Learn Using Programmable Gradient Information …

【LeetCode-74.搜索二维矩阵】

题目详情&#xff1a; 给你一个满足下述两条属性的 m x n 整数矩阵&#xff1a; 每行中的整数从左到右按非严格递增顺序排列。每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target &#xff0c;如果 target 在矩阵中&#xff0c;返回 true &#xff1b;否则&am…

HTML小游戏27 - Chuck Chicken 魔法蛋网页游戏(附完整源码)

&#x1f482; 网站推荐:【神级源码资源网】【摸鱼小游戏】 【工具大全】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;【轻量化工具创作平台】&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【学习交流群】 本节教程我会带大家使用 HTML 、…

技术分享|揭秘第三代指标平台如何解决复杂指标的定义与计算问题

本文根据 Aloudata 直播专栏 “NoETL 公开课&#xff5c;第三代指标平台如何解决复杂指标的定义与计算问题&#xff1f;”的演讲内容整理发布。 讲师简介&#xff1a;张乐&#xff0c;Aloudata CAN 指标平台技术负责人。8 年互联网技术架构和数据平台产品相关经验&#xff0c;…

预约陪诊服务app开发的运营模式分析详解

一、引言 随着社会的发展和人们生活水平的提高&#xff0c;人们对健康的需求越来越高&#xff0c;预约陪诊服务应运而生。预约陪诊服务app作为一种便捷的就医工具&#xff0c;为患者提供了更加人性化的就医体验。本文将对预约陪诊服务app开发的运营模式进行分析&#xff0c;以期…

鸿蒙Harmony应用开发—ArkTS(@BuilderParam装饰器:引用@Builder函数)

当开发者创建了自定义组件&#xff0c;并想对该组件添加特定功能时&#xff0c;例如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法&#xff0c;将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题&#xff0c;ArkUI引入了BuilderParam装饰器&…

多ip多进程代理的实现方法

目录 写在前面 一、背景 二、实现方法 1. 使用多线程处理代理请求 2. 使用多进程处理代理请求 3. 实现多IP代理 三、总结 写在前面 实现多IP多进程代理需要使用Python的多线程和多进程模块。本文将介绍如何使用这些模块来实现多IP多进程代理&#xff0c;并提供相关的代…

基于SSM+Jsp+Mysql的高校二手交易平台

基于SSMJspMysql的高校二手交易平台 基于SSMJspMysql的高校二手交易平台的设计与实现 开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff0…

API 接口渗透测试

1 API 接口介绍 1.1 RPC&#xff08;远程过程调用&#xff09; 远程过程调用&#xff08;英语&#xff1a;Remote Procedure Call&#xff0c;缩写为 RPC&#xff09;是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序&#xff0c;而程序员无…