最通俗易懂的JVM内存管理与对象创建原理

前言

对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要像 C/C++程序为每一个new操作去写配对 的delete/free代码,不容易出现内存泄漏和内存溢出问题。也正是因为Java程序员把控制内存的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误、修正问题将会成为一项异常艰难的工作。

本文主要探讨JVM内存管理,对象创建原理。通过本文你将了解我们平时创建一个对象,jvm底层做了什么,存储在哪里,底层结构是怎么存储的。

运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域 有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是 依赖用户线程的启动和结束而建立和销毁。

根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存包括以下几个运行时数据区域:

线程隔离

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

线程共享

  • 方法区
  • 直接内存

程序计数器

程序计数器是每个线程私有的,用于存储指向下一条要执行的字节码指令的地址。

它是控制线程执行流程的关键,每当线程切换时,程序计数器会保存当前线程的执行状态,以便之后能够恢复执行。

程序计数器是唯一一个不会出现 OutOfMemoryError 的内存区域,它的生命周期随着线程的创建而创建,随着线程的结束而死亡。

Java虚拟机栈

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都 会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信 息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表存放了编译期可知的各种Java虚拟机基本数据类型(boolean、byte、char、short、int、 float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始 地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和returnAddress 类型(指向了一条字节码指令的地址)。

在《Java虚拟机规范》中,对这个内存区域规定了两类异常状况:

如果线程请求的栈深度大于虚 拟机所允许的深度,将抛出StackOverflowError异常;

如果Java虚拟机栈容量可以动态扩展,当栈扩 展时无法申请到足够的内存会抛出OutOfMemoryError异常。

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈作用相似,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native) 方法服务。

与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。 

Java堆

对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java 的对象实例都在这里分配内存。

Java堆是垃圾收集器管理的内存区域,称作“GC堆”(Garbage Collected Heap。

从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现“新生代”“老年代”“永久代”“Eden空间”“From Survivor空 间”“To Survivor空间”等名词.

从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区 (Thread Local Allocation Buffer,TLAB),以提升对象分配时的效率。不过无论从什么角度,无论如 何划分,都不会改变Java堆中存储内容的共性,无论是哪个区域,存储的都只能是对象的实例,将Java 堆细分的目的只是为了更好地回收内存,或者更快地分配内存。

方法区

方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载 的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。

运行时常量池

运行时常量池是方法区的一部分。Class文件中除了有类的版本、字 段、方法、接口等描述信息外,还有一项信息是常量池表,用于存放编译期生 成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量 一定只有编译期才能产生,也就是说,并非预置入Class文件中常量池的内容才能进入方法区运行时常 量池,运行期间也可以将新的常量放入池中,这种特性被开发人员利用得比较多的便是String类的 intern()方法。

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存 时会抛出OutOfMemoryError异常。

直接内存

直接内存是一个高速缓冲区,同时也作为JDK 1.4中引入的新IO(NIO)机制的关键部分,极大地提高了数据的读写性能。通过直接内存,JVM可以更高效地处理IO操作,进一步提高系统的并发性能。
直接内存的主要作用有三个:

第一,解决对象回收问题,

第二,减少数据复制开销,

第三,提高IO操作效率。直接内存的创建可以通过ByteBuffer的allocateDirect方法实现。此外,我们还可以使用arthas工具来查看直接内存的大小,并通过调整JVM参数-XX:MaxDirectMemorySize来调整直接内存的最大大小。
需要注意的是,直接内存的回收需要主动调用freeMemory方法,否则可能会导致内存泄漏。在实际使用过程中,JVM会通过Unsafe对象来完成直接内存的分配和回收,而Cleaner则用于监测对象,一旦对象被垃圾回收,就会调用freeMemory方法释放直接内存。

对象创建原理

通过上面的介绍我们大概知道了虚拟机的内存情况,下面我们来详细的了解一下 HotSpot 虚拟机在 Java 堆中对象分配、布局和访问的全过程。

对象的创建

对象的创建分为五步:

1类加载检查-》2分配内存-》3初始化零值-》4设置对象头-》5执行 init 方法

1:类加载检查

虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

2:分配内存

类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式“指针碰撞”“空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定

内存分配的两种方式 (补充):

  • 指针碰撞:
    • 适用场合:堆内存规整(即没有内存碎片)的情况下。
    • 原理:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界指针,只需要向着没用过的内存方向将该指针移动对象内存大小位置即可。
    • 使用该分配方式的 GC 收集器:Serial, ParNew
  • 空闲列表:
    • 适用场合:堆内存不规整的情况下。
    • 原理:虚拟机会维护一个列表,该列表中会记录哪些内存块是可用的,在分配的时候,找一块儿足够大的内存块儿来划分给对象实例,最后更新列表记录。
    • 使用该分配方式的 GC 收集器:CMS

选择以上两种方式中的哪一种,取决于 Java 堆内存是否规整。而 Java 堆内存是否规整,取决于 GC 收集器的算法是"标记-清除",还是"标记-整理"(也称作"标记-压缩"),值得注意的是,复制算法内存也是规整的。

内存分配并发问题(补充)

在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:

  • CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

4:设置对象头

初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

5:执行 init 方法

在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

对象的内存布局

在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头实例数据对齐填充

Hotspot 虚拟机的对象头包括两部分信息第一部分用于存储对象自身的运行时数据(哈希码、GC 分代年龄、锁状态标志等等),另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据部分是对象真正存储的有效信息,也是在程序中所定义的各种类型的字段内容。

对齐填充部分不是必然存在的,也没有什么特别的含义,仅仅起占位作用。 因为 Hotspot 虚拟机的自动内存管理系统要求对象起始地址必须是 8 字节的整数倍,换句话说就是对象的大小必须是 8 字节的整数倍。而对象头部分正好是 8 字节的倍数(1 倍或 2 倍),因此,当对象实例数据部分没有对齐时,就需要通过对齐填充来补全。

对象的访问定位

建立对象就是为了使用对象,我们的 Java 程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式由虚拟机实现而定,目前主流的访问方式有:使用句柄直接指针

句柄

如果使用句柄的话,那么 Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与对象类型数据各自的具体地址信息。

对象的访问定位-使用句柄
直接指针

如果使用直接指针访问,reference 中存储的直接就是对象的地址。

对象的访问定位-直接指针

这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

HotSpot 虚拟机主要使用的就是这种方式来进行对象访问。

如果本文对你有帮助,不要忘记一键三连,我会继续更新更多相关优质内容。

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

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

相关文章

SpringMVC基础知识学习笔记

Universe Infinity Inc. 目录 一、学习SpringMVC主要是学什么1、SpringMVC的基本原理2、SpringMVC学习串联 二、快速体验SpringMVC的开发1、新建项目&#xff0c;转成web项目2、引入依赖3、编写Spring的配置类4、配置web启动类&#xff0c;替代web.xml5、编写Handler&#xff…

都在卷鸿蒙开发,那就推荐 几个鸿蒙开源项目

如果要问2024年最火的技术是什么,那鸿蒙开发必须占据一些位置,HarmonyOS是华为自主研发的物联网操作系统,自2019年8月正式发布以来便受到了广大开发者的追崇。为了方便大家学习鸿蒙开发,本文分享 12 个开源的鸿蒙实战项目,希望能从这些项目中获得启发和实用经验。 小狐浏…

IDEA的database使用

一、数据据库 在使用database之前&#xff0c;首先你的电脑要安装好了数据库并且启动。 MySQL卸载手册 链接&#xff1a;https://pan.baidu.com/doc/share/AVXW5SG6T76puBOWnPegmw-602323264797863 提取码&#xff1a;hlgf MySQL安装图解 链接&#xff1a;https://pan.baidu.…

Win10/11中VMware Workstation设置网络桥接模式

文章目录 一、添加VMware Bridge Protocol服务二、配置桥接参数1.启用系统Device Install Service服务2.配置VMware 需要确认物理网卡是否有添加VMware Bridge Protocol服务 添加VMware Bridge Protocol服务 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参…

艾比森的“增长炼金术”:从四力驱动到三层面增长

在技术驱动的LED显示行业&#xff0c;产品迭代快、周期波动大&#xff0c;企业经营时时承压。 但是&#xff0c;市场上总不乏打破“传统”的个例。根据2023年业绩预告&#xff0c;艾比森预计2023年实现归母净利润3.10亿-3.50亿元&#xff0c;同比增长52.70%-72.40%&#xff1b…

HttpServletRequest getServerPort()、getLocalPort() 、getRemotePort() 区别

getRemotePort() 、getServerPort()、getLocalPort() request.getServerPort()、request.getLocalPort() 和 request.getRemotePort() 这三个方法都是获取与HTTP请求相关的端口信息的 客户端(如浏览器)通过某个随机分配的网络连接端口(7070) 向服务器发送HTTP请求( http://exam…

【Docker】未来已来 | Docker技术在云计算、边缘计算领域的应用前景

欢迎来到英杰社区&#xff1a; https://bbs.csdn.net/topics/617804998 欢迎来到阿Q社区&#xff1a; https://bbs.csdn.net/topics/617897397 &#x1f4d5;作者简介&#xff1a;热爱跑步的恒川&#xff0c;致力于C/C、Java、Python等多编程语言&#xff0c;热爱跑步&#xff…

[小程序]向服务器上传图片和从服务器下载图片

本例的服务器基于flask&#xff0c;配置flask可以参见[Flask]上传多个文件到服务器https://blog.csdn.net/weixin_37878740/article/details/128435136?ops_request_misc%257B%2522request%255Fid%2522%253A%2522170581653516800185854860%2522%252C%2522scm%2522%253A%252220…

【iOS】UICollectionView的基本使用

使用UITableView作为表格来展示数据完全没有问题&#xff0c;但仍有许多局限性&#xff0c;对于一些更加复杂的布局样式&#xff0c;就有些力不从心了 比如&#xff0c;UITableView只允许表格每一行只能显示一个cell&#xff0c;而不能在一行中显示多个cell&#xff0c;对于这…

项目管理该考哪个证书❓NPDP还是软考❓

有小伙伴在纠结是要考NPDP认证呢还是考软考呢❓ 今天小编要给大家好好说说NPDP认证❗️ &#x1f4a1;NPDP全称New Product Development Professional&#xff0c;也就是产品经理国际资格认证。 &#x1f525;NPDP是国际公认的为一的新产品开发专业认证&#xff0c;是集理论、方…

【神经网络】火箭点火发射-诠释一场数据与学习的奇妙之旅

火箭点火发射来理解神经网络的故事细节 在一个充满科技气息的研究室里&#xff0c;一群科学家们正在忙碌地准备着一次重要的火箭点火发射。这次发射不仅是一次航天探索的壮丽征程&#xff0c;更是一场利用神经网络处理数据的智慧之旅。 在火箭发射的背后&#xff0c;神经网络…

使用freessl为网站获取https证书及配置详细步骤

文章目录 一、进入freessl网站二、修改域名解析记录三、创建证书四、配置证书五、服务启动 一、进入freessl网站 首先进入freessl网站&#xff0c;需要注册一个账号 freessl网站 进入网站后填写自己的域名 接下来要求进行DCV配置 二、修改域名解析记录 到域名管理处编辑域名…

MySQL基础笔记(8)多表查询

一.多表关系介绍 项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所以各个表结构之间也会存在着各种联系&#xff0c;分为如下3类&#xff1a; 一对…

【算法详解】力扣162.寻找峰值

​ 目录 一、题目描述二、思路分析 一、题目描述 力扣链接&#xff1a;力扣162.寻找峰值 峰值元素是指其值严格大于左右相邻值的元素。 给你一个整数数组 nums&#xff0c;找到峰值元素并返回其索引。数组可能包含多个峰值&#xff0c;在这种情况下&#xff0c;返回 任何一个…

Vulnhub靶机:FunBox 2

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;FunBox 2&#xff08;10.0.2.27&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://download.vulnhub.com/funbo…

【LeetCode每日一题】2788. 按分隔符拆分字符串

2024-1-20 文章目录 [2788. 按分隔符拆分字符串](https://leetcode.cn/problems/split-strings-by-separator/)思路&#xff1a; 2788. 按分隔符拆分字符串 思路&#xff1a; 对于每个单词&#xff0c;使用一个可变字符串 StringBuilder 来构建拆分后的单词。初始时&#xff0…

蓝桥杯单片机零基础到国二经验分享

我参加的是第十三届蓝桥杯大赛&#xff0c;从最开始的零基础&#xff0c;毫无头绪&#xff0c;到拿下国二&#xff0c;颇有体会&#xff0c;在这里将我的备赛经验分享给大家,希望可以帮到一些正在备赛的蓝桥杯er 目录 一. 蓝桥杯-单片机组介绍 二 . 零基础到国二历程 客观题&…

web架构师编辑器内容-图层拖动排序功能的开发

新的学习方法 用手写简单方法实现一个功能然后用比较成熟的第三方解决方案即能学习原理又能学习第三方库的使用 从两个DEMO开始 Vue Draggable Next: Vue Draggable NextReact Sortable HOC: React Sortable HOC 列表排序的三个阶段 拖动开始&#xff08;dragstart&#x…

【机器学习】李梅的餐饮帝国:美食与数据中隐藏的秘密

从小&#xff0c;李梅就对美食有着浓厚的兴趣。她常常看着母亲在厨房里忙碌&#xff0c;熟练的手法、诱人的香气&#xff0c;都让她对烹饪产生了极大的好奇。随着年龄的增长&#xff0c;她对美食的热爱与日俱增&#xff0c;最终决定投身餐饮业。 李梅的第一家餐厅开在了一个繁…

JVM:Java类加载机制

Java类加载机制的全过程&#xff1a; 加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的&#xff0c;类型的加载过程必须按照这种顺序按部就班地开始&#xff0c;而解析阶段则不一定&#xff1a;它在某些情况下可以在初始化阶段之后再开始&#xff0c; 这是为了支持Java…