java内存管理机制(二)-内存分配

news2025/1/12 12:26:41

在上一篇文章中,我们花了较大的篇幅去介绍了JVM的运行时数据区,并且重点介绍了栈区的结构及作用,在本文中,我们将主要介绍对象的创建过程及在堆中的分配方式。

对象的创建

在上文我们提过一些问题,你的对象是怎么new出来的?new出来又放在哪里?怎么引用的? 老规矩,我们还是通过字节码来了解一下。

public static void main (String[] args){
  People p = new People();
}

这样的代码大家一点也不会陌生,我们都知道使用new关键字可以创建一个对象,咦!一看字节码才知道,我们的一行new的代码,对应的字节码原来要做这么多操作!我们逐一来分析一下。

一、new指令

1、检查、加载相应类

JVM遇到new指令时,先检查指令参数(上面字节码中的#2)是否能在常量池中定位到一个类的符号引用(上面最终定位到常量池中的com/test/entity/People):

  1. 如果能定位到,检查这个符号引用代表的类是否已被加载、解析和初始化过;
  2. 如果不能定位到,或没有检查到,就先执行相应的类加载过程;

具体类的加载、解析、初始化的过程大家可以去查找JVM类加载机制相关资料,这里就不展开啦!我们需要知道的是这一步保证了在方法区中,存在要创建实例对象的类对象

 2、空间分配

咱们到了适婚年龄,也就该找个对象了吧!你看上了一个姑娘,长得楚楚动人,就跑去跟他妈说:“我要一个对象,把你女儿嫁给我吧!”。她妈妈倒是十分爽快:“好啊,我女儿总得有个地方住吧,小伙子你有房吗?”。这时候场面一度十分尴尬,心里嘀咕着“要是国家能分配房子就好了!”。这在当前社会显然不现实,毕竟咱们还没进入共产主义社会!然而在JVM王国里,对象住的“房子”却是“国家”统一分配的。国家集中圈了一大块“地”,谁家要娶“媳妇”,就给他家分配一块“地”,“媳妇”胖点呢,地就大一点,“媳妇”瘦一点呢,“地”就小一点。在这里,你一个人可以同时拥有多个对象,在这里,多个人可以拥有同一个对象。所以这里的老百姓安居乐业、这里一片祥和……当然,由于这块“地”大小有限,而你又同时拥有很多对象,还有其他人也要娶对象,所以那些不用了的对象的“地”国家就会进行统一征收(当然这里不会给补贴,毕竟是免费分配的~)以继续分给其他人用。

上面扯了这么多,相信你已经知道“你”就代表着一个线程,“国家”指的是JVM,“国家”圈的一块“地”就是堆空间,你娶的“对象”就是实例对象,“国家”分配地的动作就是内存分配,而国家征收的动作就是垃圾回收。

由于要找对象的人太多了,所以分配的操作也很频繁,那么摆在“国家”的问题就来了:怎么合理分配?怎么最大限度的提高空间利用率?怎么提高分配效率?不用了的空间怎么回收?怎么知道哪些空间不用了?上面很多问题都需要结合后面的垃圾回收相关的内容来讨论,这里只讨论分配内存的方式。

一个对象需要占用多大的内存?这个问题其实在类加载完成后就已经确定啦!JVM可以通过普通java对象的类元信息确定对象大小。为对象分配内存相当与把一块确定大小的内存从java堆中划分出来。那么问题来了,这么大的一块堆空间摆在JVM的面前,JVM该划哪一块空间来分配内存呢?随机找一块空间分配算了?or紧挨着之前分配的空间后面进行分配?这里需要说到的是两种分配方式:

1)、 指针碰撞

如果Java堆是绝对规整的:一边是用过的内存,一边是空闲的内存,中间一个指针作为边界指示器,分配内存只需向空闲那边移动指针,这种分配方式称为"指针碰撞"(Bump the Pointer)。这里有个条件就是“绝对规整”,类似下图,左边全是被绿过了的,右边则全是等着被绿的。新分配对象时候就是多绿了一块,边界指示器向后移动!

2)、 空闲列表

如果Java堆不是规整的:用过的和空闲的内存相互交错。需要维护一个列表,记录哪些内存可用。分配内存时查表找到一个足够大的内存,并更新列表,这种分配方式称为"空闲列表"(Free List)。类似下图,好好的一块内存被绿得乱七八糟,用上面指针碰撞的方式是碰不动了!所以就用一个小本本记着哪里有多大的空闲空间可以绿!当然下图的地址编号是虚拟的,空闲列表的样子也是我意淫出来的,表达的意思你懂就行!

我们能看到,导致这两种方式的差异主要取决于java堆是否规整,而java堆是否规整又是由jvm采用的垃圾收集器是否带有压缩功能决定的。使用Serial、ParNew等带Compact过程的收集器时,JVM采用指针碰撞方式分配内存。而使用CMS这种基于标记-清除(Mark-Sweep)算法的收集器时,采用空闲列表方式。(下篇文章会具体介绍不同的垃圾收集器)

不管是指针碰撞还是空闲列表,都会存在同一个问题,那就是在多线程的场景下的线程安全问题。多个线程同时在new的时候把对象分配到同一块内存了咋办,不得干起来么!于是jvm采用了两种方案来解决:

1)、 同步处理:JVM采用CAS(Compare and Swap)机制加上失败重试的方式,保证更新操作的原子性。CAS机制是一种轻量级锁机制,后续在聊多线程的时候再讲!

2)、 本地线程分配缓冲区:把分配的内存按照不同的线程划分在不同的空间进行,每个线程在java堆区预先分配一小块内存,称为本地线程分配缓冲区(Thread Local Allocation Buffer)。哪个线程需要分配就从哪个线程的TLAB上分配,只有在TLAB用完需要分配新的TLAB的时候才需要做同步处理(通过上一点中的CAS机制)。

3、对象初始化

内存分配完后,就需要初始化实例对象了,虚拟机需要将分配到的内存空间中的数据类型都初始化为零值(不包括对象头,如果是使用TLAB,初始化0值的操作提前至分配TLAB时)。接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息,这些信息都存放在对象的对象头中。做完以上以后,从虚拟机视角来看,一个新的对象已经产生了!

4、返回地址

JVM完成对象内存的分配及对象初始化之后,会返回对象的地址,并且压入操作数的栈顶,供后续操作!

二、dup指令

dup命令没猜错的话是duplicate的简写。在讨论dup命令前,我们先看一个简单的例子

public static void main (String[] args){
    int a;
    int b = a = 88;

}

我们看看对应的字节码

public static void main(java.lang.String[]);
Code:
0: bipush 88
2: dup
3: istore_1
4: istore_2
5: return

由于88这个值在一条语句中需要重复赋给两个变量,所以使用dup指令对栈顶的值进行了复制,且压入栈顶。我们在new对象的时候,new指令后面都会紧跟dup指令!然后是invokespecial和astore指令,相信聪明的你应该想到invokespecial和astore指令都会需要从栈顶弹出值来执行!在执行完dup指令后,操作数栈栈顶就有两个指向该对象实例内存的reference数据,如果<init>方法有参数,还需要把参数加载到操作栈。

三、invokespecial指令

invokespecial指令调用对象实例方法<init>,通过符号引用#3定位到的是People对象的实例方法<init>。这时候操作数栈栈顶值(指向对象实例的内存reference)会被弹出(如果<init>方法有参数,参数也会出栈)。执行<init>方法会在java虚拟机栈中创建<init>方法的栈帧,并且把出栈的数据放入栈帧的局部变量表中。变量表中指向对象实例的内存reference就是我们经常用到的this,表示对该对象实例进行操作!执行完该指令后,一个完整的对象就创建完成啦!

四、astore指令

astore依然需要弹出栈顶值,然后存储到编号为1的变量中供后续使用。至此一个完整的对象已经创建且返回对象内存引用给本地变量存储了。

对象的访问定位

我们上面已经把对象创建的问题解决了,同时我们也都知道,引用类型的变量存储的是**对象的引用**!那这个引用类型数据怎么定位到堆中的对象呢?目前主流的对象访问方式有两种:

1、使用句柄

JVM在堆区划分一块内存作为句柄池,引用类型变量中存储就是对象的句柄地址。对象句柄包含两个地址(如下图):

  • 在堆中分配的对象实例数据的地址。
  • 这个对象类型数据地址。

2、使用直接指针

引用类型变量中存储就是在堆中分配的对象实例数据的地址。

句柄池的方式会在句柄池中存放类型对象的相关信息,而直接访问的方式会把类型对象的信息放入实例对象的对象头中(我们知道对象头包含“指向对象类型数据的指针”,其实这并不是必须的,我们常用的HotSpot虚拟机采用的是直接指针的方式,所以对象头中会包含“指向对象类型数据的指针”,如果某类虚拟机采用的是句柄的方式访问对象,那可能就不需要在头部存储这个指针了)。这两种方式都互有优缺点:

  1. 句柄方式访问对象时,多一次指针定位的时间开销。但是对象移动时(垃圾回收时常见的动作),栈上的变量的引用不需要修改,只需改变句柄中实例数据指针。
  2. 直接指针对象相对句柄方式访问节省了一次指针定位的时间开销,性能更好。如果对象访问非常频繁,提升会更明显!但是在对象移动时,栈上的变量的引用也需要变化。

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

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

相关文章

bWAPP靶场安装

bWAPP安装 下载 git地址&#xff1a;https://github.com/raesene/bWAPP 百度网盘地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1Y-LvHxyW7SozGFtHoc9PKA 提取码&#xff1a;4tt8 –来自百度网盘超级会员V5的分享 phpstudy中打开根目录&#xff0c;并将下载的文…

【python】PyQt5事件机制、定时器原理分析和实战演练

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

基于LLM(Large Language Model,大语言模型)的智能问答系统

基于LLM&#xff08;Large Language Model&#xff0c;大语言模型&#xff09;的智能问答系统是一种利用先进的人工智能技术&#xff0c;尤其是自然语言处理&#xff08;NLP&#xff09;技术&#xff0c;来构建能够理解和回答用户问题的系统。这种系统通过训练大量文本数据&…

德国Testing Expo丨落幕不散场!知迪展台风采回顾

德国斯图加特国际展览中心&#xff0c;随着全球汽车产业的目光聚焦&#xff0c;Automotive Testing Expo Europe 2024圆满落幕。在这场汇聚了全球顶尖汽车测试技术的盛会中&#xff0c;知迪科技凭借卓越的技术实力和前瞻性的解决方案&#xff0c;成为了现场诸多专业观众的瞩目焦…

pydub、ffmpeg 音频文件声道选择转换、采样率更改

快速查看音频通道数和每个通道能力判断具体哪个通道说话&#xff1b;一般能量大的那个算是说话 import wave from pydub import AudioSegment import numpy as npdef read_wav_file(file_path):with wave.open(file_path, rb) as wav_file:params wav_file.getparams()num_cha…

红酒与舞蹈:舞动的味觉艺术

在艺术的海洋中&#xff0c;红酒与舞蹈总是能激起人们心中较温柔的涟漪。红酒以其深邃的色泽、馥郁的香气&#xff0c;诠释着味觉的艺术&#xff1b;而舞蹈&#xff0c;则以优雅的姿态、灵动的步伐&#xff0c;演绎着视觉的盛宴。当红酒遇上舞蹈&#xff0c;一场别开生面的艺术…

Ubuntu防火墙相关内容

Ubuntu防火墙相关的命令&#xff0c;主要用于日常使用过程中&#xff0c;忘记命令时查找方便&#xff0c;不用再去各种地方搜索了。以下命令均已root用户执行&#xff0c;如果是非root用户&#xff0c;需要添加sudo 查看防火墙的启用状态 ufw status 说明是启用状态。 启用防…

边缘和条件高斯相乘后的高斯分布形式【模式识别书】

边缘和条件高斯相乘后的高斯分布形式【模式识别书】 结论来自&#xff1a;《Pattern Recognition and Machine Learning》公式(2.115)

前端 原型 原型链的理解

概念 原型 对象中固有的 __proto__ 属性&#xff0c;该属性指向对象的 prototype 原型属性。 原型链 当我们访问一个对象的属性时&#xff0c;如果这个对象内部不存在这个属性&#xff0c;那么它就会去它的原型对象 里找这个属性&#xff0c;这个原型对象又会有自己的原…

自然语言处理与Transformer模型:革新语言理解的新时代

引言 自然语言处理&#xff08;NLP&#xff09;是人工智能和计算机科学的一个重要分支&#xff0c;旨在使计算机能够理解、生成和处理人类语言。随着互联网和数字化信息的爆炸性增长&#xff0c;NLP在许多领域中的应用变得越来越重要&#xff0c;包括&#xff1a; 搜索引擎&am…

.NET 漏洞情报 | 某云平台存在SQL注入漏洞

01阅读须知 此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等&#xff08;包括但不限于&#xff09;进行检测或维护参考&#xff0c;未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失&#xf…

Django学习第二天

启动项目命令 python manage.py runserver 动态获取当前时间 javascript实现数据动态更新代码 <script>setInterval(function() {var currentTimeElement document.getElementById(current-time);var currentTime new Date();currentTimeElement.textContent Curren…

ESP32CAM物联网教学02

ESP32CAM物联网教学02 物联网门锁 小智来到姑姑家门口&#xff0c;按了门铃&#xff1b;还在公司上班的姑姑用电脑给小智开了门&#xff0c;让他先进屋休息。小智对物联网门锁产生了兴趣&#xff1a;什么是物联网&#xff1f;为什么这么厉害&#xff1f; 初识物联网 我们在百…

Mac/Linux安装JMeter压测工具

Mac安装JMeter压测工具 介绍 Apache JMeter™应用程序是开源软件&#xff0c;是一个100%纯的Java应用程序&#xff0c;旨在加载测试功能行为和衡量性能。它最初是为测试Web应用程序而设计的&#xff0c;但后来扩展到其他测试功能。 我能用它做什么&#xff1f; Apache JMet…

SwanLinkOS首批实现与HarmonyOS NEXT互联互通,软通动力子公司鸿湖万联助力鸿蒙生态统一互联

在刚刚落下帷幕的华为开发者大会2024上&#xff0c;伴随全场景智能操作系统HarmonyOS Next的盛大发布&#xff0c;作为基于OpenHarmony的同根同源系统生态&#xff0c;软通动力子公司鸿湖万联全域智能操作系统SwanLinkOS首批实现与HarmonyOS NEXT互联互通&#xff0c;率先攻克基…

二叉树的最近公共祖先-二叉树

236. 二叉树的最近公共祖先 - 力扣&#xff08;LeetCode&#xff09; ​ 递归 lson、rson左右子树&#xff1b; 深度优先遍历&#xff0c;遍历到p或者q就返回ture&#xff1b; class Solution { public:TreeNode* ans;bool dfs(TreeNode* root, TreeNode* p, TreeNode* q){i…

什么游戏加速器好用 网游加速器排行榜

玩游戏经常遇到卡顿和网络延迟等问题&#xff0c;尤其是外服游戏&#xff0c;这时候就需要一个安全稳定快速的加速器&#xff0c;我个人比较推荐“深度加速器。这款款加速器在稳定性和加速效果上都非常不错&#xff0c;而且用户口碑也很好。 在选择加速器时&#xff0c;确实有很…

DGMamba: Domain Generalization via Generalized State Space Model论文笔记

文章目录 DGMamba: Domain Generalization via Generalized State Space Model摘要动机DGMamba设计隐藏状态抑制(HSS)语义感知补丁细化(SPR)免先验扫描域上下文交换上下文patch识别 实验结果 DGMamba: Domain Generalization via Generalized State Space Model paper: https:/…

java到底是值传递还是引用传递

1、一定是值传递&#xff0c;给你的表象也有引用传递是因为对象传递的引用地址&#xff0c;我们在堆里更改了对象的属性值&#xff0c;但是地址没有变更&#xff0c;所以是值传递&#xff0c;可以参考方法的堆栈。 2、本质点看是否new一个新对象&#xff0c;如果new新对象&…

使用 llamaIndex 快速实现智能体

AI 智能体就是可以根据当前环境进行推理&#xff0c;并根据处理结果进行下一步的操作。简单来说 AI 智能体可以与外界环境进行交互&#xff0c;并根据结果执行更复杂的操作。本文将通过llamaIndex 实现一个简单的 Agent 实时获取数据&#xff0c;由于大模型是通过静态数据进行训…