JVM面试整理--对象的创建和堆

文章目录

    • 对象的创建过程是怎样的?
    • 对象在内存中的结构是怎样的(专业的叫法:对象的内存布局)
    • 对象在内存分配时使用的哪种方式(有的地方也称为:分配算法)
    • 知道什么是“指针碰撞”吗?
    • 知道什么是“空闲列表”吗?
    • 内存分配算法是否存在并发问题?是如何解决并发问题的?
    • 请说一说“TLAB”
    • 对象创建好后,如何对其进行定位访问
    • jvm中堆的结构是怎样的?新生代、老年代
    • JVM中老年代和新生代的比例是多少?
    • 说说对象的分配规则。
    • 什么是空间分配担保?

 


对象的创建过程是怎样的?

答:
对象的创建过程大致可分为如下四个步骤:

  • 类加载检查。JVM遇到 new 指令,会去检查能否在常量池中定位到类的符号引用,并且检查该符号引用代表的类是否已经被加载、解析和初始化过。如果没有,则进行类加载过程。
  • 为对象分配内存。内存分配方式可以选择“指针碰撞”或是“空闲列表”。具体选择哪一种,是由堆内存是否规整决定的。
  • 将内存空间初始化为零值(除对象头)。这一步保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用。
  • 为对象进行必要的信息设置。这些信息主要存放在对象头(Object Header)之中。对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。还有关于对象是否加锁的信息。
    对象头的内容如下:

对象头

助记方法:
1.类是对象的基础,只有加载到jvm的类,才可以创建其对象 -> 类加载检查。
2.对象是需要存储信息的 -> 就需要为其分配内存。
3.对象的信息称为实例变量,实例变量需要赋值才可使用 -> 初始化零值
4.对象头保存了对象的元数据->这些信息是在对象创建时设置的

 


对象在内存中的结构是怎样的(专业的叫法:对象的内存布局)

答:
再次引用前面的图:
对象头
 
对象在堆内存中存储,其内存布局可分为3部分:
对象头、实例数据和对齐填充。

  • 对象头(Object Header)。
    又可再分为两部分:第一部分,官方称为“MarkWord”,存储的是对象自身的运行时数据。如:哈希码(HashCode)、GC分代年龄、锁状态标志、偏向线程ID等;第二部分是“类型指针”(Klass Pointer)。即对象指向它的类元数据的指针,虚拟机可通过其确定该对象是哪个类的实例。如果是数组对象,其对象头还会有一块用于记录数组长度的数据。
     
  • 实例数据(Instance Data)。
    真正存储有效信息的部分,这里主要指对象的实例字段。包含从父类继承的字段信息,相同宽度的字段总是分配在一起。
     
  • 对齐填充(Padding)
    因为对象的大小必须是8字节的整数倍。对象头是8字节的整数倍,但是实例数据则不一定,当其大小不足8字节时,需要对其进行填充,以达到8字节。

下图给出了对象头的空间占用情况:

对象头的空间占用

 


对象在内存分配时使用的哪种方式(有的地方也称为:分配算法)

答:对象在内存分配时,会使用到两种内存分配方式:

  • 指针碰撞
  • 空闲列表

具体使用哪一种内存分配方式,取决于堆内存是否规整。而堆内存是否规整,则决定于垃圾收集器是否带有整理功能(即是否采用了“标记-整理”垃圾收集算法)。

因此,带压缩功能的Serial New等收集器,采用 “指针碰撞” 分配算法。
而像CMS这种基于 Mark-Sweep 算法的收集器,则采用 “空闲列表” 分配算法。

 


知道什么是“指针碰撞”吗?

答:

指针碰撞

一般情况下,JVM的对象都放在堆内存中(发生逃逸分析除外)。
Java虚拟机为新生对象分配内存时。如果Java堆中的内存是绝对规整的,所有被使用过的的内存都被放到一边,空闲的内存放在另外一边,中间以一个指针作为分界点的指示器(如图所示)。那么分配内存所要做的仅仅是把指针向空闲内存方向挪动,挪动的距离正好等于对象的大小,这种内存分配方式就称为“指针碰撞”。

助记点:
1.堆内存需绝对规整(结合图加深理解)。关键字:已使用内存空闲的内存指针分界点的指示器
2.分配内存,即指针向空闲内存方向移动。关键词:向空闲内存的方向移动
3.内存分配方式,称为“指针碰撞”。关键词:内存分配方式

 


知道什么是“空闲列表”吗?

答:
堆内存不规整 (即已使用内存和空闲内存是交错在一起的)时,是不适合使用“指针碰撞”的方式为对象分配内存的。

而对象的内存是需要一整块连续区域的。为了能快速找到合适大小的内存,虚拟机就需要维护一个列表,用于记录哪些内存可用,其大小是多少

当分配内存时,借助于该列表可以很快找到合适的内存。列表中已分配内存的记录需要进行更新(删除还是打标已使用?)。
这种内存分配方式,称为“空闲列表”。

 


内存分配算法是否存在并发问题?是如何解决并发问题的?

答:
内存分配时,不管是使用 “指针碰撞” 还是 “空闲列表” 分配算法,都有可能产生并发问题。

因为可能有多个线程发起了对象创建,从而出现多个线程对同一个内存位置的争抢情况,结果导致某些线程中对象创建失败。

为了解决并发问题,JVM给出了两种方案:

  • CAS + 失败重试
  • TLAB(本地线程分配缓冲)

 


请说一说“TLAB”

答:TLAB是本地线程分配缓冲(Thread Local Allocation Buffer)的缩写。有的地方也翻译为本地线程分配缓存。

它的工作原理:为每一个线程在堆中预先分配一小块内存,称其为TLAB。为线程分配堆内存时,优先从TLAB上分配,当TLAB用完后,再使用同步锁定的方式分配新的TLAB。

虚拟机通过参数 -XX:+/-UseTLAB 决定是否启用TLAB功能。

 


对象创建好后,如何对其进行定位访问

答:对象创建好后,可以通过如下方式对其进行定位访问:

  • 使用句柄
  • 直接指针

使用句柄访问, 将会从堆中划分出一块内存来作为句柄池,reference(栈中的本地变量表) 中存储的是对象的句柄地址,而句柄中包含了对象实例数据指针与对象类型数据指针(二元组)。

使用句柄访问:
在这里插入图片描述

使用句柄访问的好处:

reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而reference本身不需要修改。

助记关键词:
句柄池句柄地址-存在本地变量表中句柄是一个二元组<实例数据指针, 类型数据指针>

 
使用直接指针访问,reference 中存储的直接就是对象地址。而对象的头信息中包含对象类型数据的信息(称为对象类型指针) 。

使用直接指针访问:
使用直接指针访问

直接指针访问的好处:

速度更快,节省了一次指针定位的时间开销。

助记关键词:
reference直接指向对象地址对象头中包含类型信息

 


jvm中堆的结构是怎样的?新生代、老年代

答:
Java堆可分为新生代和老年代,新生代和老年代的比例为1:2。这个比例可通过-XX:NewRatio参数进行调整。

新生代又可细分为一块较大的Eden区和两块较小的Survivor区。默认Eden和Survivor大小的比例是8:1。这个比例可以通过 -XX:SurvivorRatio 参数进行调整。

JVM每次只会使用Eden和其中一块Survivor区域来为对象服务,所以总是有一块Survivor区域是空闲的。
所以新生代的实际可用内存空间为9/10的新生代内存空间。

新生代存储的都是新创建的对象、比较小的对象;而老年代存的都是活得久的,或是比较大的对象。所以,老年代占JVM堆内存的比例较大。

下图是新生代和老年代的比例:

新生代和老年代

 


JVM中老年代和新生代的比例是多少?

答:新生代和老年代的默认比例是1:2。这个比例可通过-XX:NewRatio参数进行调整。

注:和堆的内存结构重复,单独拎出来是为了强调该知识点的重要性。

 


说说对象的分配规则。

答:
目前主流的虚拟机都是使用的分代收集算法。对应的内存的分配也是分代分配的。

规则如下:

  • 对象优先分配在Eden区。如果启用了TLAB,则优先在TLAB上分配。当Eden区没有足够空间时,会触发一次Minor GC(YGC)。
  • 大对象(指需要大量连续内存空间的对象,如长字符串和长数组)直接进入老年代。
  • 长期存活的对象进入老年代。对象头的MarkWord中有一个GC分代年龄,默认分代年龄大于15的对象进入老年代。对象首次进入Survivor区后,其对象年龄设为1,然后在Survivor中每熬过一次Minor GC,其对象年龄就加1,直至对象年龄达到阈值,对象直接进入老年代(也称为晋升为老年代)。可通过设置参数-XX:MaxTenuringThreshold修改对象进入老年代的年龄阈值。该规则针对的是Survivor中的对象
  • 动态对象年龄判断。其概念为:如果Survivor中相同年龄的对象的总大小大于Survivor空间的一半,年龄大于等于该年龄的对象,直接进入老年代。注意:这条规则也是针对Survivor中的对象的

    例如:Survivor空间=2M,当中有4个对象:
    对象1:大小-0.5M,年龄-3
    对象2:大小-0.6M,年龄-3
    对象3:大小-0.1M,年龄-2
    对象4:大小-0.2M,年龄-4
     
    根据动态对象年龄判断的规则:对象1+对象2的总大小 > 1M;
    所以,Survivor中年龄大于等于3的对象,都将进入老年代。
    故:对象1、对象2和对象4,都将进入老年代。

  • 空间分配担保。

助记关键词:
Eden区-优先分配大对象Survivor-长期存活的对象Survivor-基于同龄对象占比空间分配担保

参考:
《深入理解Java虚拟机(第2版)》p95、3.6节

 


什么是空间分配担保?

答:
每次在进行Minor GC之前,JVM会检查老年代最大可用连续空间是否大于新生代所有对象的总空间

如果大于,说明Minor GC是安全的,进行Minor GC。

帮助理解 =>(因为Minor GC是对新生代空间的回收,如果其中的对象还需要使用,则需要将其移动到老年代,所以就需要老年代有足够的空间存放这些对象。这里考虑的是最极端的情况,即所有的新生代对象都将进入老年代

如果老年代的最大可用连续空间,放不下所有的新生代对象,那么是否大于历次晋升到老年代的对象的平均大小。如果满足,则进行Minor GC,否则进行Full GC。

担保是由老年代作出的,并且主要发生在第二次判断中。老年代根据历史的经验值,对Minor GC的结果作出担保,保证GC后老年代的最大连续空间可以容纳GC后晋升到老年代的对象大小。

担保依然有失败的可能,因为Minor GC后存活的对象可能会很多,以至于大于老年代的可用连续空间。所以就有了第三个判断。

如果担保失败,则在Minor GC后,会再次触发一次Full GC(如图所示)。

下图演示了“空间分配担保”的流程:

空间分配担保

JDK6 Update24之前的空间分配担保流程如下:

空间分配担保-02

 
 

声明:大部分图片来源自互联网和书籍。

 
 
 


参考:

  • [1] 《深入理解Java虚拟机(第2版)》

  • [2] https://blog.csdn.net/qyj19920704/article/details/123965383

  • [3] https://blog.csdn.net/guorui_java/article/details/137178686

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

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

相关文章

功能测试_订购单检查_判定表

画判定表的步骤&#xff1a; 列出条件 列出动作

ssm048电子竞技管理平台的设计与实现+jsp

电子竞技管理平台设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本电子竞技管理平台就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短…

ThingsBoard通过服务端获取客户端属性或者共享属性

MQTT基础 客户端 MQTT连接 通过服务端获取属性值 案例 1、首先需要创建整个设备的信息&#xff0c;并复制访问令牌 ​2、通过工具MQTTX连接上对应的Topic 3、测试链接是否成功 4、通过服务端获取属性值 5、在客户端查看对应的客户端属性或者共享属性的key 6、查看整个…

Cesium.js--》探秘Cesium背后的3D模型魔力—加载纽约模型

今天简单实现一个Cesium.js的小Demo&#xff0c;加强自己对Cesium知识的掌握与学习&#xff0c;先简单对这个开源库进行一个简单的介绍吧&#xff01; Cesium 是一个开源的地理空间可视化引擎&#xff0c;用于创建基于 Web 的三维地球应用程序。它允许开发人员在网页上呈现高度…

C++设计模式:代理模式(十三)

1、代理模式 定义&#xff1a;为其他对象提供一种代理以控制&#xff08;隔离使用接口&#xff09;对这个对象的访问等。 动机 在面向对象系统中&#xff0c;有些对象由于某种原因&#xff08;比如对象需要进程外的访问等&#xff0c;例如在分布式的系统中&#xff09;&#x…

5G Frequency Bands 频率分布

连接&#xff1a;https://www.5g-networks.net/5g-technology/5g-frequency-bands/

基于springboot+vue实现的药品信息管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

意得辑真不错

意得辑真不错&#xff0c;85喆优惠码延长到25.12.31了我用editage意得辑润色SCI已经第4年了&#xff0c;今天他家的学术支持老师让我写几句感受&#xff0c;那我真的感受太多了。因为下单太多一度被导师怀疑是在他家套经费。22年刚读博同时润色了三篇&#xff0c;被导师叫到办公…

环信 IM 客户端将适配鸿蒙 HarmonyOS

自华为推出了自主研发操作系统鸿蒙 HarmonyOS 后&#xff0c;国内许多应用软件开始陆续全面兼容和接入鸿蒙操作系统。环信 IM 客户端计划将全面适配统鸿蒙 HarmonyOS &#xff0c;助力开发者快速实现社交娱乐、语聊房、在线教育、智能硬件、社交电商、在线金融、线上医疗等广泛…

C语言函数指针应用——计算器(转移表)的使用

对与上一节&#xff0c;我们对指针函数已经有了深刻意识了&#xff1b;练一练吧 如果还没有学习到&#xff0c;也是没有关系的&#xff0c;可以看一看这一篇 C语言详解指针-CSDN博客https://blog.csdn.net/Asuku_/article/details/137690083希望能提高你对指针的理解 计算器的实…

KVM + GFS 分布式存储

目录 一、案例分析 1.1、案例概述 1.2、案例前置知识点 1&#xff09;Glusterfs 简介 2&#xff09;Glusterfs 特点 1.3、案例环境 1&#xff09;案例环境 2&#xff09;案例需求 3&#xff09;案例实现思路 二、案例实施 2.1、安装部署 KVM 虚拟化平台 1&…

04异常Lambda算法正则

异常 异常是什么&#xff1f; 异常是代码在编译或者执行的过程中可能出现的错误。避免异常的出现&#xff0c;同时处理可能出现的异常&#xff0c;让代码更稳健。 异常分为几类&#xff1f; 编译时异常、运行时异常。编译时异常&#xff1a;没有继承RuntimeExcpetion的异常…