Java虚拟机面试题:内存管理(中)

news2025/4/6 17:59:50

🧑 博主简介:CSDN博客专家历代文学网(PC端可以访问:https://literature.sinhy.com/#/?__c=1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程高并发设计Springboot和微服务,熟悉LinuxESXI虚拟化以及云原生Docker和K8s,热衷于探索科技的边界,并将理论知识转化为实际应用。保持对新技术的好奇心,乐于分享所学,希望通过我的实践经历和见解,启发他人的创新思维。在这里,我希望能与志同道合的朋友交流探讨,共同进步,一起在技术的世界里不断学习成长。
技术合作请加本人wx(注明来自csdn):foreast_sea

在这里插入图片描述


在这里插入图片描述

Java虚拟机面试题:内存管理(中)

1. 什么是指针碰撞?什么是空闲列表?

在堆内存分配对象时,主要使用两种策略:指针碰撞和空闲列表。

在这里插入图片描述

①、指针碰撞(Bump the Pointer)

假设堆内存是一个连续的空间,分为两个部分,一部分是已经被使用的内存,另一部分是未被使用的内存。

在分配内存时,Java 虚拟机维护一个指针,指向下一个可用的内存地址,每次分配内存时,只需要将指针向后移动(碰撞)一段距离,然后将这段内存分配给对象实例即可。

②、空闲列表(Free List)

JVM 维护一个列表,记录堆中所有未占用的内存块,每个空间块都记录了大小和地址信息。

当有新的对象请求内存时,JVM 会遍历空闲列表,寻找足够大的空间来存放新对象。

分配后,如果选中的空闲块未被完全利用,剩余的部分会作为一个新的空闲块加入到空闲列表中。

指针碰撞适用于管理简单、碎片化较少的内存区域(如年轻代),而空闲列表适用于内存碎片化较严重或对象大小差异较大的场景(如老年代)。

2. JVM 里 new 对象时,堆会发生抢占吗?JVM 是怎么设计来保证线程安全的?

会,假设 JVM 虚拟机上,每一次 new 对象时,指针就会向右移动一个对象 size 的距离,一个线程正在给 A 对象分配内存,指针还没有来的及修改,另一个为 B 对象分配内存的线程,又引用了这个指针来分配内存,这就发生了抢占。

有两种可选方案来解决这个问题:

在这里插入图片描述

  • 采用 CAS 分配重试的方式来保证更新操作的原子性

  • 每个线程在 Java 堆中预先分配一小块内存,也就是本地线程分配缓冲(Thread Local Allocation

    Buffer,TLAB),要分配内存的线程,先在本地缓冲区中分配,只有本地缓冲区用完了,分配新的缓存区时才需要同步锁定。

3. 能说一下对象的内存布局吗?

在 Java 中,对象的内存布局是由 Java 虚拟机规范定义的,但具体的实现细节可能因不同的 JVM 实现(如 HotSpot、OpenJ9 等)而异。

在 HotSpot 中,对象在堆内存中的存储布局可以划分为三个部分:对象头(Object Header)、实例数据(Instance Data)和对齐填充(Padding)。

在这里插入图片描述

①、对象头是每个对象都有的,包含三部分主要信息:

  • 标记字Mark Word):包含了对象自身的运行时数据,如哈希码(HashCode)、垃圾回收分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 等信息。在 64 位操作系统下占 8 个字节,32 位操作系统下占 4 个字节。
  • 类型指针Class Pointer):指向对象所属类的元数据的指针,JVM 通过这个指针来确定对象的类。在开启了压缩指针的情况下,这个指针可以被压缩。在开启指针压缩的情况下占 4 个字节,否则占 8 个字节。
  • 数组长度Array Length):如果对象是数组类型,还会有一个额外的数组长度字段。占 4 个字节。

注意,启用压缩指针(-XX:+UseCompressedOops)可以减少对象头中类型指针的大小,从而减少对象总体大小,提高内存利用率。

可以通过 java -XX:+PrintFlagsFinal -version | grep UseCompressedOops 命令来查看当前 JVM 是否开启了压缩指针。

二哥的 Java 进阶之路:查看 JVM 是否开启压缩指针

如果压缩指针开启,会看到类似以下的输出,其中 bool UseCompressedOops 的值为 true。

在 JDK 8 中,压缩指针默认是开启的,以减少 64 位应用中对象引用的内存占用。

②、实例数据存储了对象的具体信息,即在类中定义的各种字段数据(不包括由父类继承的字段)。这部分的大小取决于对象的属性和它们的类型(如 int、long、引用类型等)。JVM 会对这些数据进行对齐,以确保高效的访问速度。

③、对齐填充,为了使对象的总大小是 8 字节的倍数(这在大多数现代计算机体系结构中是最优访问边界),JVM 可能会在对象末尾添加一些填充。这部分是为了满足内存对齐的需求,并不包含任何具体的数据。

为什么非要进行 8 字节对齐呢?

这是因为 CPU 进行内存访问时,一次寻址的指针大小是 8 字节,正好是 L1 缓存行的大小。如果不进行内存对齐,则可能出现跨缓存行访问,导致额外的缓存行加载,降低了 CPU 的访问效率。

rickiyang:缓存行污染

比如说上图中 obj1 占 6 个字节,由于没有对齐,导致这一行缓存中多了 2 个字节 obj2 的数据,当 CPU 访问 obj2 的时候,就会导致缓存行的刷新,这就是缓存行污染。

也就说,8 字节对齐,是为了效率的提高,以空间换时间的一种方案。固然你还能够 16 字节对齐,可是 8 字节是最优选择。

rickiyang:000 结尾

Object a = new object()的大小

一般来说,对象的大小是由对象头、实例数据和对齐填充三个部分组成的。

  • 对象头的大小在 32 位 JVM 上是 8 字节,在 64 位 JVM 上是 16 字节(如果开启了压缩指针,就是 12 字节)。
  • 实例数据的大小取决于对象的属性和它们的类型。对于new Object()来说,Object 类本身没有实例字段,因此这部分可能非常小或者为零。
  • 对齐填充的大小取决于对象头和实例数据的大小,以确保对象的总大小是 8 字节的倍数。

rickiyang:Java 对象模型

一般来说,目前的操作系统都是 64 位的,并且 JDK 8 中的压缩指针是默认开启的,因此在 64 位 JVM 上,new Object()的大小是 16 字节(12 字节的对象头 + 4 字节的对齐填充)。

为了确认我们的推理,我们可以使用 JOL 工具来查看对象的内存布局:

JOL 全称为 Java Object Layout,是分析 JVM 中对象布局的工具,该工具大量使用了 Unsafe、JVMTI 来解码布局情况。

第一步,在 pom.xml 中引入 JOL 依赖:

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

第二步,使用 JOL 编写代码示例:

public class JOLSample {
    public static void main(String[] args) {
        // 打印JVM详细信息(可选)
        System.out.println(VM.current().details());

        // 创建Object实例
        Object obj = new Object();

        // 打印Object实例的内存布局
        String layout = ClassLayout.parseInstance(obj).toPrintable();
        System.out.println(layout);
    }
}

第三步,运行代码,查看输出结果:

二哥的 Java 进阶之路:JOL 运行结果

可以看到有 OFFSET、SIZE、TYPE DESCRIPTION、VALUE 这几个名词头,它们的含义分别是

  • OFFSET:偏移地址,单位字节;
  • SIZE:占用的内存大小,单位字节;
  • TYPE DESCRIPTION:类型描述,其中 object header 为对象头;
  • VALUE:对应内存中当前存储的值,二进制 32 位;

从上面的结果能看到对象头是 12 个字节,还有 4 个字节的 padding,一共 16 个字节。我们的推理是正确的。

对象引用占多少大小?

在 64 位 JVM 上,未开启压缩指针时,对象引用占用 8 字节;开启压缩指针时,对象引用可被压缩到 4 字节。

而 HotSpot JVM 默认开启了压缩指针,因此在 64 位 JVM 上,对象引用占用 4 字节。

dijia478:对象头

我们可以通过下面这个例子来验证一下:

class ReferenceSizeExample {
    private static class ReferenceHolder {
        Object reference;
    }

    public static void main(String[] args) {
        System.out.println(VM.current().details());
        System.out.println(ClassLayout.parseClass(ReferenceHolder.class).toPrintable());
    }
}

运行代码,查看输出结果:

二哥的 Java 进阶之路:对象的引用有多大?

ReferenceHolder.reference 字段位于偏移量 12,大小为 4 字节。这表明在当前的 JVM 配置下(64 位 JVM 且压缩指针开启),对象引用占用的内存大小为 4 字节。

4. 对象怎么访问定位?

Java 程序会通过栈上的 reference 数据来操作堆上的具体对象。由于 reference 类型在《Java 虚拟机规范》里面只规定了它是一个指向对象的引用,并没有定义这个引用应该通过什么方式去定位、访问到堆中对象的具体位置,所以对象访问方式也是由虚拟机实现而定的,主流的访问方式主要有使用句柄和直接指针两种:

  • 如果使用句柄访问的话,Java 堆中将可能会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息,其结构如图所示:

在这里插入图片描述

  • 如果使用直接指针访问的话,Java 堆中对象的内存布局就必须考虑如何放置访问类型数据的相关信息,reference 中存储的直接就是对象地址,如果只是访问对象本身的话,就不需要多一次间接访问的开销,如图所示:

在这里插入图片描述

这两种对象访问方式各有优势,使用句柄来访问的最大好处就是 reference 中存储的是稳定句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference 本身不需要被修改。

使用直接指针来访问最大的好处就是速度更快,它节省了一次指针定位的时间开销,由于对象访问在 Java 中非常频繁,因此这类开销积少成多也是一项极为可观的执行成本。

HotSpot 虚拟机主要使用直接指针来进行对象访问。

5. 说一下对象有哪几种引用?

四种,分别是强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)。

在这里插入图片描述

强引用是 Java 中最常见的引用类型。使用 new 关键字赋值的引用就是强引用,只要强引用关联着对象,垃圾收集器就不会回收这部分对象。

String str = new String("沉默王二");

软引用是一种相对较弱的引用类型,可以通过 SoftReference 类实现。软引用对象在内存不足时才会被回收。

SoftReference<String> softRef = new SoftReference<>(new String("沉默王二"));

弱引用可以通过 WeakReference 类实现。弱引用对象在下一次垃圾回收时会被回收,不论内存是否充足。

WeakReference<String> weakRef = new WeakReference<>(new String("沉默王二"));

虚引用可以通过 PhantomReference 类实现。虚引用对象在任何时候都可能被回收。主要用于跟踪对象被垃圾回收的状态,可以用于管理直接内存。

PhantomReference<String> phantomRef = new PhantomReference<>(new String("沉默王二"), new ReferenceQueue<>());

6. Java 堆的内存分区了解吗?

Java 堆被划分为新生代老年代两个区域。

在这里插入图片描述

新生代又被划分为 Eden 空间和两个 Survivor 空间(From 和 To)。

  • Eden 空间:大多数新创建的对象会被分配到 Eden 空间中。当 Eden 区填满时,会触发一次轻量级的垃圾回收(Minor GC),清除不再使用的对象。
  • Survivor 空间:每次 Minor GC 后,仍然存活的对象会从 Eden 区或 From 区复制到 To 区。From 和 To 区可以交替使用。

对象在新生代中经历多次 GC 后,如果仍然存活,会被移动到老年代。

7. 说一下新生代的区域划分?

新生代的垃圾收集主要采用标记-复制算法,因为新生代的存活对象比较少,每次复制少量的存活对象效率比较高。

基于这种算法,虚拟机将内存分为一块较大的 Eden 空间和两块较小的 Survivor 空间,每次分配内存只使用 Eden 和其中一块 Survivor。发生垃圾收集时,将 Eden 和 Survivor 中仍然存活的对象一次性复制到另外一块 Survivor 空间上,然后直接清理掉 Eden 和已用过的那块 Survivor 空间。默认 Eden 和 Survivor 的大小比例是 8∶1。

新生代内存划分

8. 对象什么时候会进入老年代?

对象通常会先在年轻代中分配,然后随着时间的推移和垃圾收集的处理,某些满足条件的对象会进入到老年代中。

二哥的 Java 进阶之路:对象进入老年代

①、长期存活的对象将进入老年代

对象在年轻代中存活足够长的时间(即经过足够多的垃圾回收周期)后,会晋升到老年代。

每次 GC 未被回收的对象,其年龄会增加。当对象的年龄超过一个特定阈值(默认通常是 15),它就会被移动到老年代。这个年龄阈值可以通过 JVM 参数-XX:MaxTenuringThreshold来设置。

②、大对象直接进入老年代

为了避免在年轻代中频繁复制大对象,JVM 提供了一种策略,允许大对象直接在老年代中分配。

这些是所谓的“大对象”,其大小超过了预设的阈值(由 JVM 参数-XX:PretenureSizeThreshold控制)。直接在老年代分配可以减少在年轻代和老年代之间的数据复制。

③、动态对象年龄判定

除了固定的年龄阈值,还会根据各个年龄段对象的存活大小和内存空间等因素动态调整对象的晋升策略。

比如说,在 Survivor 空间中相同年龄的所有对象大小总和大于 Survivor 空间的一半,那么年龄大于或等于该年龄的对象就可以直接进入老年代。

9. 什么是 Stop The World ? 什么是 OopMap ?什么是安全点?

进行垃圾回收的过程中,会涉及对象的移动。为了保证对象引用更新的正确性,必须暂停所有的用户线程,像这样的停顿,虚拟机设计者形象描述为Stop The World。也简称为 STW。

在 HotSpot 中,有个数据结构(映射表)称为OopMap。一旦类加载动作完成的时候,HotSpot 就会把对象内什么偏移量上是什么类型的数据计算出来,记录到 OopMap。在即时编译过程中,也会在特定的位置生成 OopMap,记录下栈上和寄存器里哪些位置是引用。

这些特定的位置主要在:

  • 1.循环的末尾(非 counted 循环)

  • 2.方法临返回前 / 调用方法的 call 指令后

  • 3.可能抛异常的位置

这些位置就叫作安全点(safepoint)。 用户程序执行时并非在代码指令流的任意位置都能够在停顿下来开始垃圾收集,而是必须是执行到安全点才能够暂停。

用通俗的比喻,假如老王去拉车,车上东西很重,老王累的汗流浃背,但是老王不能在上坡或者下坡休息,只能在平地上停下来擦擦汗,喝口水。

在这里插入图片描述

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

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

相关文章

游戏无法启动?XINPUT1_3.dll 丢失的终极解决方案

当你兴奋地启动一款新游戏时&#xff0c;突然弹出一个错误提示——‘程序无法启动&#xff0c;因为计算机中丢失 XINPUT1_3.dll’。这种问题在 PC 玩家中非常常见&#xff0c;尤其是运行一些较老的游戏时。XINPUT1_3.dll 是 DirectX 运行库的关键组件&#xff0c;缺失会导致游戏…

嵌入式硬件如何在PADS中将原理图转换为PCB详解

本文旨在讲述如何在PADS中将原理图转换为PCB。 本文以C51原理图作为例子。 1.首先在桌面上打开PADS Logic 2.找到菜单栏的文件选项,然后点击新建。 点击新建之后出现如下界面。

软件工程-UML

例图&#xff0c;类图&#xff0c;状态图&#xff0c;顺序图&#xff0c;活动图 目录 例图 类图 状态图 顺序图 活动图 例图 例图由四个元素组成&#xff0c;参与者、用例、系统边界、参与者和用例之间的关系 参与者用一个小人表示&#xff0c;用例用椭圆表示&#xff…

【Linux学习笔记】初识进程概念和进程PCB

【Linux学习笔记】初识冯诺依曼体系和进程PCB &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;Linux学习笔记 文章目录 【Linux学习笔记】初识冯诺依曼体系和进程PCB前言一. 冯诺依曼体系结构1.1 关于冯诺依曼体系的要点&#xff1a; 二. 操…

深入探索 Linux Top 命令:15 个实用示例

在 Linux 系统管理中&#xff0c;top 命令是系统性能监控不可或缺的工具。它能够实时显示系统的 CPU、内存、进程等资源的使用情况&#xff0c;帮助您快速识别性能瓶颈和异常进程。本文将详细介绍 15 个实用的 top 命令使用示例&#xff0c;旨在帮助您更高效地进行系统管理与优…

风电行业预测性维护解决方案:AIoT驱动下的风机健康管理革命

在风电行业向平价化与智慧化转型的关键阶段&#xff0c;如何通过预测性维护技术将风机可用率提升至99%以上&#xff1f;本文基于中讯烛龙系统的实战经验&#xff0c;解析如何构建基于LSTM、数字孪生与边缘计算的智能运维体系&#xff0c;实现从“故障维修”到“健康预判”的技术…

通过Postman和OAuth 2.0连接Dynamics 365 Online的详细步骤

&#x1f31f; 引言 在企业应用开发中&#xff0c;Dynamics 365 Online作为微软的核心CRM平台&#xff0c;提供了强大的Web API接口。本文将教你如何通过Postman和OAuth 2.0认证实现与Dynamics 365的安全连接&#xff0c;轻松调用数据接口。 &#x1f4dd; 准备工作 工具安装…

Ubuntu-安装redis

apt list | grep redis apt 类似于应用商店的感觉 ‘|’的作用是作为管道&#xff0c;把前者到的数据列表再通过grep筛选出包含redis字眼的一行数据 需要联网 apt install redis -y 修改配置文件 vi /etc/redis/redis.conf redis是客户端服务器程序 需要先把服务器给后台启…

制造装备物联及生产管理ERP系统设计与实现(代码+数据库+LW)

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装制造装备物联及生产管理ERP系统软件来发挥其高效地信息处理…

[ctfshow web入门] web4

前置知识 robots.txt是机器人协议&#xff0c;在使用爬虫爬取网站内容时应该遵循的协议。协议并不能阻止爬虫爬取&#xff0c;更像是一种道德规范。 假设robots.txt中写道 Disallow: /admind.php&#xff0c;那我就暴露了自己的后台&#xff0c;这属于信息泄漏&#xff0c;攻击…

【JavaWeb-Spring boot】学习笔记

目录 <<回到导览Spring boot1. http协议1.1.请求协议1.2.响应协议 2.Tomcat2.1.请求2.1.1.apifox2.1.2.简单参数2.1.3.实体参数2.1.4.数组集合参数2.1.5.日期参数2.1.6.(重点)JSON参数2.1.7.路径参数 2.2.响应2.3.综合练习 3.三层架构3.1.三层拆分3.2.分层解耦3.3.补充 &…

SQLmap工具使用

1. sqlmap介绍 sqlmap是一款自动化的SQL注入工具&#xff0c;用于检测和利用web应用程序中的SQL注入漏洞。不需要我们进行手注&#xff0c;当我们输入url地址后&#xff0c;会自动进行注入指令并将payload返回显示。 在kali中自带。在本机中需要下载&#xff0c;在相应的路径…

OpenCV 实现对形似宝马标的黄黑四象限标定位

文章目录 功能背景代码效果 功能 实现对形似宝马标的黄黑四象限光学识别标定位 背景 大学同学遇到了这个场景&#xff0c;琢磨了下&#xff0c;以备不时之需。 代码 所用opencv版本&#xff1a;4.12 numpy2.2.4 scikit_learn1.6.1import time import cv2 import numpy as…

2025 年 4 月补丁星期二预测:微软将推出更多 AI 安全功能

微软正在继续构建其 AI 网络安全战略&#xff0c;并于本月宣布在 Microsoft Security Copilot 中引入新代理。 他们引入了用于网络钓鱼分类的代理、用于数据丢失预防和内部风险管理的警报分类、条件访问优化、漏洞修复和威胁情报简报。 这些代理的目标是不断从这些不同学科中…

从吉卜力漫画到艺术创造:GPT-4o多种风格绘图Prompt大全

在3月底&#xff0c;GPT-4o掀起了一阵吉卜力绘图浪潮&#xff0c;大家纷纷输入一张图片&#xff0c;让4o模型进行风格化迁移&#xff0c;其中吉卜力风格的漫画在社交媒体上最为火热。在大家争议4o的训练数据是否侵权和4o背后的技术原理的时候&#xff0c;我们先来玩一玩&#x…

16.1Linux自带的LED灯驱动实验(知识)_csdn

前面我们都是自己编写 LED 灯驱动&#xff0c;其实像 LED 灯这样非常基础的设备驱动&#xff0c; Linux 内核已经集成了。 Linux 内核的 LED 灯驱动采用 platform 框架&#xff0c;因此我们只需要按照要求在设备树文件中添加相应的 LED 节点即可&#xff0c;本章我们就来学习如…

【vLLM】使用 vLLM 对自定义实现模型进行高速推理

推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 介绍什么是 vLLM?处理 vLLM 中的多模态模型实现独特的视频生成模型转换为 vLLM 模型的策略准备输入标记序列如何添加多个多模式输入如…

SQL Server 数据库实验报告

​​​​​​​ 1.1 实验题目&#xff1a;索引和数据完整性的使用 1.2 实验目的&#xff1a; &#xff08;1&#xff09;掌握SQL Server的资源管理器界面应用&#xff1b; &#xff08;2&#xff09;掌握索引的使用&#xff1b; &#xff08;3&#xff09;掌握数据完整性的…

在响应式网页的开发中使用固定布局、流式布局、弹性布局哪种更好

一、首先看下固定布局与流体布局的区别 &#xff08;一&#xff09;固定布局 固定布局的网页有一个固定宽度的容器&#xff0c;内部组件宽度可以是固定像素值或百分比。其容器元素不会移动&#xff0c;无论访客屏幕分辨率如何&#xff0c;看到的网页宽度都相同。现代网页设计…

代码随想录算法训练营第三十八天 | 322.零钱兑换 279.完全平方数 139.单词拆分

322. 零钱兑换 题目链接&#xff1a;322. 零钱兑换 - 力扣&#xff08;LeetCode&#xff09; 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;动态规划之完全背包&#xff0c;装满背包最少的物品件数是多少&#xff1f;| LeetCode&#xff1a;322.零钱兑换_哔哩哔哩_b…