【String str = new String(“hollis“) 创建了几个对象?】

在这里插入图片描述

✅典型解析


创建的对象数应该是1个或者2个。


首先要清楚什么是对象?


Java是一种面向对象的语言,而Java对象在JVM中的存储也是有一定的结构的,在HotSpot虚机中,存储的形式就是oop-klass model,即ava对象模型。我们在Java代码中,使用new创建一个对象的时候,JVM会创建一instanceOopDesc对象,这个对象中包合了两部分信息,对象头以及元数据。对象头中有一些运行时数据,其中就包括和多线程相关的锁的信息,元数据其实维护的是指针,指向的是对象所属的类的instanceKlass。


这才叫对象。其他的,一概都不叫对象。


那么不管怎么样,一次new的过程,都会在堆上创建一个对象,那么就是起码有一个对象了。至于另外一个对象到底有没有要看具体情况了。


另外这一个对象就是常量池中的字符串常量,这个字符串其实是类编译阶段就进到Class常量池的,然后在运行期,字符串常量在第一次被调用(准确的说是ldc指令)的时候,进行解析并在字符串池中创建对应的String实例的。


在运行时常量池中,也并不是会立刻被解析成对象,而是会先以VM_CONSTANT_UnresolveString_info的形式驻留在常量池。在后面,该引用第一次被LDC指令执行到的时候,就尝试在堆上创建字符串对象,并将对象的引用驻留在字符串常量池中。


通过看上面的过程,你也能发现,这个过程的触发条件是我们没办法决定的,问题的题干中也没提到。有可能执行这段代码的时候是第一次LDC指令执行,也许在前面就执行过了。


所以,如果是第一次执行,那么就是会同时创建两个对象。一个字符串常量引用指向的对象,一个我们new出来的对象。


如果不是第一次执行,那么就只会创建我们自己new出来的对象。


至于有人说什么在字符串池内还有在栈上还有一个引用对象,你听听这说法,引用就是引用。别往对象上面扯。


✅什么是Class常量池,和运行时常量池关系是什么?


Class常量池可以理解为是Class文件中的咨源仓库。Cass文件中除了包合类的版本,字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Litera)和符号引用Symbolic References)。


Class是用来保存常量的一个媒个场所,并且是一个中间场所。Class文件中的常量池部分的内容,会在运行期被运行时常量池加载进去。


✅ 查看Class常量池


由于不同的Class文件中包合的常量的个数是不固定的,所以在Class文件的常量池入口处会设置两个字节的常量池容量计数器,记录了常量池中常量的个数。


在这里插入图片描述

当然,还有一种比较简单的产看Class文件中常量池的方法,那就是通过 javap 命令,对于以上的HelloWorld.class,可以通过

javap -v HelloWorld.class


查看常量池内容如下:


在这里插入图片描述

从上图可以看到,反编译后的class文件常量池中共有16个常量。而Class文件中常量计数器的数值是0011,将该16进制数字转化为10进制的结果是17。


原因是与Java的语言习惯不同,常量池计数器是从0开始而不是从1开始的,常量池的个数是10进制的17,这就代表了其中有16个常量,索引值的范围为1-16。


✅字符串常量是什么时候进入到字符串常量池的?


字符串常量池中的常量有两种来源,一种是字面量会在编译期先进入到Class常量池,然后再在运行期进去到字符串池,还有一种就是在运行期通过intern将字符串对象手动添加到字符串常量池中。


那么,Class常量池中的常量,是在什么时候被放进到字符串池的呢?


Java 的类加载过程要经历加载 (Loading) 、链接 (Linking) 、初始化nitializing) 等几个步,在链接这人步骤,又分为验证 (Verification) 、准备 (Preparation) 以及解析(Resolution) 等几个步骤。


在Java 虚拟机规范及Java语言规范中都提到过:


《The Java Virtual Machine Specification》 5.4 Linking ::




For example, a Java Virtual Machine implementation may choose to resolve each symbolic reference ina class or interface individually when it is used (azy" or “late” resolution), or to resolve them all atonce when the class is being verified (“eager” or “static” resolution)




《The Java Language Specification》 12.3 Linking of Classes and Interfaces




For example, an implementation may choose to resolve each symbolic reterence in a class or interfaceindividually, only when it is used (lazy or late resolution), or to resolve them all at once while the classis being verified (static resolution), This means that the resolution process may continue, in someimplementations, after a cass or interface has been initialized.


大致意思差不多,就是说,Java 虚拟机的实现可以选择只有在用到类或者接口中的符号引用时才去逐一解析他(延迟解析),或者在验证类的时候就解析每个引用(预先解析)。这意味着在一些虚拟机实现中,把常量放到常量池的步骤可能是延迟处理的。


对于 HotSpot 虚拟机来说,字符串字面量,和其他基本类型的常量不同,并不会在类加载中的解析阶段填充并驻留在字符串常量池中,而是以特殊的形式存储在运行时常量池中。只有当这个字符串字面量被调用时,才会对其进行解析,开始为他在字符串常量池中创建对应的 String 实例。


通过查看 HotSpotJDK 1.8 的 ldc 指令的源代码,也可以验证上面的说法。


ldc 指令表示int、float或String型常量从常量池推送至栈顶


IRT_ENTRY(void,InterpreterRuntime::ldc(JavaThread* thread, bool wide))
//access constant pool
ConstantPool* pool = method(thread)->constants():
int index = wide ? get_index_u2(thread, Bytecodes::_ldc_w) : get_index_u1(thread,Bytecodes :: _ldc);
constantTag tag = pool->tag_at(index);assert (tag.is_unresolved_klass() || tag.is_klass(),"wrong ldc call");
Klass* klass = pool->klass_at(index,CHECK);
oop java_class = klass->java mirror();	
thread->set_wm_result(java_class);
IRT_END

所以,字符串常量,是在第一次被调用(准确的说是ldc指令)的时候,进行解析并在字符审池中创建对应的String实例的。


✅字符串常量池是如何实现的?


字符串常量池(String Constant Pool) 是Java中一块特殊的内存区域,用于存储字符串常量。


当程序中出现字符串常量时,Java编译器会将其放入字符串常量池中。字符串常量是不可变的,因此可以共享。如果字符串常量池中已存在相同内容的字符串,编译器会直接引用已存在的字符串常量,而不会创建新的对象。


在HotSpot虚拟机中:


在JDK 1.6及之前的版本,字符串常量池通常被实现为方法区的一部分,即永久代(Permanent Generation)用于存储类信息、常量池、静态变量、即时编译器编译后的代码等数据。


从JDK 1.7开始,字符串常量池的实现方式发生了重大改变。字符串常量池不再位于永久代,而是直接存放在堆(Heap) 中,与其他对象共享堆内存。


之所以要挪到堆内存中,主要原因是因为永久代的 GC 回收效率太低,只有在FulGC的时候才会被执行回收。但是Java中往往会有很多字符串也是朝生夕死的,将字符串常量池放到堆中,能够更高效及时地回收字符串内存。

✅字符串常量从哪来的?


字符串常量池中的常量有以下几个来源:

1、字面量常量

在代码中直接使用双引号括起来的字符串字面值(如 String s "Hllis”)会被认为是常量,并且会在编译后进入class文件的常量池,并且在运行阶段,进入字符串常量池。这是最常见的字符串常量来源。

2、intern() 方法


String类提供了一intern()方法,用于将字符电对象手动添加到字符电常量池中。调用intern0方法时,如果字答串常量池中已经存在相同内容的字符串,将会返回常量池中的引用:如果不存在,则会在常量池中创建新的字符串。


✅扩展知识仓


✅字面量和运行时常量池


JVM为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化。为了减少在JM中创建的字符串的数量,字符串类维护了一个字符串常量池。


在JVM运行时区域的方法区中,有一块区域是运行时常量池,主要用来存储编译期生成的各种字面量和符号引用。


了解Class文件结构或者做过Java代码的反编译的朋友可能都知道,在iava代码被 javac 编译之后,文件结构中是包含 部分 Constant pool 的。比以下代码:


public static void main(string[] args) {String s ="Hollis";
}

经过编译后,常量池内容如下:


在这里插入图片描述

上面的Class文件中的常量池中,比较重要的几个内容:


在这里插入图片描述

上面几个常量中, s 就是前面提到的符号引用,而 Hollis 就是前面提到的字面量。而Class文件中的常量池部分的内容,会在运行期被运行时常量池加载进去。关于字面量,详情参考Java SE Specifications: Java SE Specifications


✅intern

编译期生成的各种字面量符号引用是运行时常量池中比较重要的一部分来源,但是并不是全部。那么还有一种情况,可以在运行期向运行时常量池中增加常量。那就是 string 的 intern 方法。


当一个 string 实例调用 intern() 方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用:。


intern()有两个作用,第一个是将字符串字面量放入常量池 (如果池没有的话),第二个是返回这个常量的引用。


✅intern的正确用法


不知道,你有没有发现,在 String s3 = new String(“Hollis”).intern(); 中,其实 intern 是多余的?


因为就算不用 intern ,Hollis作为一个字面量也会被加载到Class文件的常量池,进而加入到运行时常量池中为啥还要多此一举呢? 到底什么场景下才需要使用 intern 呢?


在解释这个之前,我们先来看下以下代码:


String s1 ="Hollis";
String s2 = "Chuang";
String s3 = s1 + s2;
String s4 ="Hollis" + "Chuang";

在经过反编译后,得到代码如下:

String s1 = "Hollis";
String s2 = "Chuang";
String s3 = (new StringBuilder()).append(s1).append(s2).toString();
String s4 ="HollisChuang";

可以发现,同样是字符串拼接,s3和s4在经过编译器编译后的实现方式并不一样。s3被转化成 stringBuilder及 append ,而s4被直接拼接成新的字符串。


如果你感兴趣,你还能发现, String s3 = s + s2;经过编译之后,常量池中是有两个字符串常量的分别是Chuang (其实 Hollis 和 Chuang 是 String s1 =“Hollis”,和 String s2 = “ChuanHollis.g”,定义出来的),拼接结果 HollisChuang 并不在常量池中。


究其原因,是因为常量池要保存的是已确定的字面量值。也就是说,对于字符串的拼接,纯字面量和字面量的拼接,会把拼接结果作为常量保存到字符串池。


如果在字符串拼接中,有一个参数是非字面量,而是一个变量的话,整个拼接操作会被编译成 StringBuilder.append这种情况编译器是无法知道其确定值的。只有在运行期才能确定。


那么,有了这个特性了, intern 就有用武之地了。那就是很多时候,我们在程序中得到的字符串是只有在运行期才能确定的,在编译期是无法确定的,那么也就没办法在编译期被加入到常量池中。


这时候,对于那种可能经常使用的字符串,使用 intern 进行定义,每次JVM运行到这段代码的时候,就会直接把常量池中该字面值的引用返回,这样就可以减少大量字符串对象的创建了。


比如 —— 深入解析String#intern中举的一个例子:


static final int MAX = 1000 * 10000;
static final String[] arr = new String[MAX];public static void main(String[] args) throws Exception {Integer[] DB_DATA = new Integer[10];Random random = new Random(10 * 10000);for (int i = 0; i < DB_DATA.length; i++) {DB_DATA[i] = random.nextInt();}long t = System.currentTimeMillis();for (int i = 0; i < MAX; i++) {//arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length]));arr[i] = new String(String.valueOf(DB_DATA[i % DB_DATA.length])).intern();}System.out.println((System.currentTimeMillis() - t) + "ms");System.gc();
}

运行的参数是:-Xmx2g -Xms2g -Xmn1500M 上述代码是一个演示代码,其中有两条语句不一样,一条是使用 intern,一条是未使用 intern。结果如下图:


2160ms

在这里插入图片描述
826ms

在这里插入图片描述
通过上述结果,我们发现不使用 intern 的代码生成了1000w 个字符串,占用了大约640m 空间。 使用了 intern 的代码生成了1345个字符串,占用总空间 133k 左右。其实通过观察程序中只是用到了10个字符串,所以准确计算后应该是正好相差100w 倍。虽然例子有些极端,但确实能准确反应出 intern 使用后产生的巨大空间节省。

细心的同学会发现使用了 intern 方法后时间上有了一些增长。这是因为程序中每次都是用了 new String 后,然后又进行 intern 操作的耗时时间,这一点如果在内存空间充足的情况下确实是无法避免的,但我们平时使用时,内存空间肯定不是无限大的,不使用 intern 占用空间导致 jvm 垃圾回收的时间是要远远大于这点时间的。 毕竟这里使用了1000w次intern 才多出来1秒钟多的时间。

So,我们明确的知道,会有很多相同的字符串产生,但是这些字符串的值都是只有在运行期才能确定的。所以,只能我们通过 intern 显示的将其加入常量池,这样可以减少很多字符串的重复创建。

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

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

相关文章

FastGPT+ChatGLM3-6b搭建知识库

前言&#xff1a;我用fastgpt直接连接chatglm3&#xff0c;没有使用oneai&#xff0c;不是很复杂&#xff0c;只需要对chatglm3项目代码做少量修改就能支持使用embeddings&#xff0c;向量模型用的m3e&#xff0c;效果还可以 我的配置&#xff1a; 处理器&#xff1a;i5-13500 …

VideoPoet: Google的一种用于零样本视频生成的大型语言模型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

【C++进阶02】多态

一、多态的概念及定义 1.1 多态的概念 多态简单来说就是多种形态 同一个行为&#xff0c;不同对象去完成时 会产生出不同的状态 多态分为静态多态和动态多态 静态多态指的是编译时 在程序编译期间确定了程序的行为 比如&#xff1a;函数重载 动态多态指的是运行时 在程序运行…

【Java异常】聊聊异常可能带来的坑

一个活生生的案例 本周帮同事排查了一个问题&#xff0c;比较诡异的是他通过测试&#xff0c;并没有找到根本原因&#xff0c;只是发现有对应的错误日志。 但是其实并没有将堆栈信息打印出来。很难看出问题。添加了 e.printStackTrace(); get exception in exter: / by zero显…

HP笔记本电脑进入BIOS的方法主要有两种,它们使用场合不同

BIOS&#xff08;基本输入输出系统&#xff09;是一种实用程序&#xff0c;它在你按下电源按钮后启动并加载操作系统。无论是要更新HP笔记本电脑的BIOS系统&#xff0c;还是清除前一个系统中的错误&#xff0c;第一步都是进入BIOS实用程序。 在按键输入BIOS设置并对其进行修改…

循环神经⽹络中的梯度算法GRU

1. 什么是GRU 在循环神经⽹络中的梯度计算⽅法中&#xff0c;我们发现&#xff0c;当时间步数较⼤或者时间步较小时&#xff0c;**循环神经⽹络的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸&#xff0c;但⽆法解决梯度衰减的问题。**通常由于这个原因&#xff0…

Java开发框架和中间件面试题(3)

14.Spring事务中的隔离级别有哪几种&#xff1f; 在TransactionDefinition接口中定义了五个表示隔离级别的常量&#xff1a; 1⃣️ISOLATION DEFAULT&#xff1a;使用后端数据库默认的隔离级别&#xff0c;Mysql默认采用的可重复读隔离级别&#xff1b;Oracle默认采用的读已提…

在Linux下探索MinIO存储服务如何远程上传文件

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;网络奇遇记、Cpolar杂谈 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 创建Buckets和Access Keys二. Linux 安装Cpolar三. 创建连接MinIO服务公网地…

JavaWeb—html, css, javascript, dom,xml, tomcatservlet

文章目录 快捷键HTML**常用特殊字符替代:****标题****超链接标签****无序列表、有序列表****无序列表**:ul/li 基本语法**有序列表ol/li:****图像标签(img)**** 表格(table)标签****表格标签-跨行跨列表格****form(表单)标签介绍****表单form提交注意事项**div 标签p 标签sp…

Linux命令-查看内存、GC情况及jmap 用法

查看进程占用内存、CPU使用情况 1、查看进程 #jps 查看所有java进程 #top 查看cpu占用高进程 输入m &#xff1a;根据内存排序 topMem: 16333644k total, 9472968k used, 6860676k free, 165616k buffers Swap: 0k total, 0k used, 0k free, 6…

Kubernetes 的用法和解析(K8S 日志方案) -- 8

一、统一日志管理的整体方案 通过应用和系统日志可以了解Kubernetes集群内所发生的事情&#xff0c;对于调试问题和监视集群活动来说日志非常有用。对于大部分的应用来说&#xff0c;都会具有某种日志机制。因此&#xff0c;大多数容器引擎同样被设计成支持某种日志机制。 对…

MySQL——复合查询

目录 一.基本查询回顾 二. 多表查询 三.自连接 四.子查询 1.单行子查询 2.多行子查询 3.多列子查询 4.在from子句中使用子查询 5.合并查询 一.基本查询回顾 准备数据库&#xff1a; 查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为…

【IntelliJ IDEA】打开项目Git突然无法识别解决方案

这个问题也是我今天突然偶尔遇到的&#xff0c;当时没在意&#xff0c;项目打开之后又关闭&#xff0c;后来很久才又打开&#xff0c;发现项目明明有git版本控制的&#xff0c;咋突然开发工具右下角没有标识了&#xff0c;然后检查了一下git配置还报错了。 其实从图上我们可以看…

服务器经常死机怎么办?如何处理

关于服务器死机这一话题相信大家是不会陌生的&#xff0c;平时在使用服务器的过程中&#xff0c;或多或少都是会有遇到过。轻则耽误业务开展&#xff0c;重则造成数据丢失&#xff0c;相信每个人都不想碰到服务器死机的情况。下文我也简单的介绍下服务器死机的原因以及对应的预…

安装vcpkg管理opencv的安装+MFC缺失的解决

第一步&#xff0c;出现#include没有办法找到opencv头文件的问题&#xff0c;无法解决 在VC的提示下&#xff0c;安装了vcpkg&#xff0c;然后用vcpkg命令来帮助安装opencv&#xff0c;过程十分顺利。 1. cmd 到命令行窗口&#xff1b; 2. 建立src文件夹&#xff0c;并进入…

Python入门学习篇(五)——列表字典

1 列表 1.1 定义 ①有序可重复的元素集合 ②可以存放不同类型的数据 ③个人理解:类似于java中的数组1.2 相关方法 1.2.1 获取列表长度 a 语法 len(列表名)b 示例代码 list2 [1, 2, "hello", 4] print(len(list2))c 运行结果 1.2.2 获取列表值 a 语法 列表名…

001 图书增删改查 SSM MySQL

技术框架&#xff1a;Spring SpringMVC Mybatis JSP MySQL 001 图书增删改查 SSM MySQL package com.demo.controller;import com.demo.pojo.Book; import com.demo.service.BookService; import org.springframework.beans.factory.annotation.Autowired; import org.spri…

Log4net 教程

一、Log4net 教程 在CodeProject上找到一篇关于Log4net的教程&#xff1a;log4net Tutorial&#xff0c;这篇博客的作者是&#xff1a;Tim Corey &#xff0c;对应源代码地址为&#xff1a; https://github.com/TimCorey/Log4netTutorial&#xff0c;视频地址为&#xff1a;Ap…

C#上位机与欧姆龙PLC的通信04---- 欧姆龙plc的存储区

1、存储区概念 欧姆龙PLC将整个数据存储器分为10个区&#xff1a;输入继电器区、输出继电器区、内部辅助继电器区、特殊继电器区、保持继电器区、暂存继电器区、定时/计数器区、数据存储区、辅助存储继电器区、链接继电器区。 输入输出继电器区 CP1E系列PLC输入继电器区有16…

acwing linux 第七讲 环境变量、管道、常用命令、附录

文章目录 管道 概念 要点 举例 环境变量 查看 修改 常用环境变量 常用命令 系统状况 文件权限 文件检索 查看文件内容 用户相关 其他工具 安装软件 附录 Linux权限 本节课讲解的是管道&#xff0c;环境变量&#xff0c;以及常用命令 管道 概念 管道类似文…