JVM(内存区域划分、类加载机制、垃圾回收机制)

目录

一. 内存区域划分

1.本地方法栈(Native Method Stacks)

2.虚拟机栈(JVM Stacks)

3.程序计数器(Program Counter Register)

4.堆(Heap)

5.元数据区(Metaspace)

二.类加载机制

1.加载

2.验证

3.准备

4.解析

5.初始化

"双亲委派模型"

三. GC 垃圾回收机制

GC实际工作过程

1.找到垃圾/判定垃圾

(1).引用计数(Java没有使用)

(2).可达性分析(Java使用的)

2.如何清理垃圾

(1).标记清除​编辑

(2).复制算法​编辑

(3).标记整理​编辑

"分代回收"


一. 内存区域划分

正如一个公司的运作模式,首先公司需要一块场地,再接着需要根据公司的各个部分,将这块场地划分成一块一块的部门,每个部门都有独立的功能。

JVM的内存区域划分也是如此,JVM启动的时候会申请一整个很大的内存区域(JVM是个应用程序,要从操作系统中申请内存),接着根据需求,将整个空间分成几个部分。

1.本地方法栈(Native Method Stacks)

native表示JVM内部的C++代码(JVM是用C++写的),所以本地方法栈就是给调用native方法(JVM内部方法)准备的栈空间

问题:C++代码是不是不用跑在虚拟机上 答:像C、C++、Go、Rust都是把代码变成成native code,也就是可以直接被cpu识别的机械指令 而Java、Python、PHP为了跨平台,都是统一翻译成指定的字节码(字节码都是一样的),然后由对应的虚拟机转换成机械指令

2.虚拟机栈(JVM Stacks)

给Java代码使用的栈

注意:stack栈,在之前数据结构中也学习过,数据结构中的“栈”是“后进先出”的,和这里所说的栈不是一个东西。

这里所提的栈,是JVM中的一个特定的空间。

  • 对于JVM虚拟机栈,这里存储的是方法之间的调用关系
  • 对于本地方法栈,存储的是native方法之间的调用关系

整个栈空间内部,可以认为是包含很多个元素,每个元素表示一个方法,把这里的每个元素,称之为“栈帧”,这一个栈帧里,会包含这个方法的入口地址、方法参数、返回地址、局部变量.....

 

但是由于函数调用中,也有“后进先出”的特点,因此此处的栈也是后进先出的,只是数据结构中的栈是一个更通用的,更广泛的概念,而JVM中的栈是特指JVM上的一块内存空间。

从内存划分图中也可以看出,这里的栈有多个,每个线程有一个(线程是一个独立的执行流),使用jconsole(jdk下bin目录中)可以查看java进程内部的情况,点击线程就可以看到线程调用栈的情况。

问题:这些线程每次创建都分相同空间,那么栈会不会被很快就用完了? 答:栈的整体大小一般都不会很大,但是每个栈帧的大小也是比较小的,遇到无限递归的代码会遇到栈溢出的情况,就正常写代码,创建销毁线程一般是不需要担心这个问题的。

3.程序计数器(Program Counter Register)

记录当前线程执行到哪条指令(很小的一块存一个地址),也是每个线程有一份。

4.堆(Heap)

堆是整个JVM空间的最大的区域,new出来的对象,都是在堆上的,类的成员对象也就在堆上了

  • 堆 是一个进程只有一份的

  • 栈 是每个线程有一份,一个进程有多个线程

  • 堆,多个线程用的都是同一个堆

  • 栈,每个线程用自己的栈

每个jvm就是一个java进程

5.元数据区(Metaspace)

以前这个区域叫做方法区,java8开始改为了元数据区 这里存储的有类对象、常量池、静态成员

元数据区也是一个进程一份,多个线程共用这一份

问题:final修饰的变量在这里存储吗? 答:不一定,有一定概率被优化成字面值常量,也有可能没优化

总结

  • 局部变量在栈
  • 普通成员变量在堆
  • 静态成员变量在方法区/元数据区

二.类加载机制

准确的来说,类加载就是将.class文件,从文件(硬盘)被加载到内存中(元数据区)这样的过程。

1.加载

  • (1).通过一个类的全限定名来获取这个类的二进制字节流
  • (2).将这个字节流多代表的静态存储结构转化成方法区的运行时数据结构
  • (3).在内存中生成一个代表这个类的java.lang.Class对象

但是对应上述的要求并不是具体的,JVM的实现过程和应用是比较灵活的,比如获取类的二进制字节流,并没有说明如何获取,所以就有了从压缩包中获取(jar、war、ear),从网路中获取(Applet),运行时计算生成(动态代理)

对于不是数组的类的加载我们可以自定义去控制字节流的获取方式,而数组类就不一样了,因为数组类本身不是通过类加载进行创建的,而是由JVM直接创建

2.验证

根据jvm虚拟机规范,检查.class文件的格式是否符合要求

3.准备

给类对象分配内存空间(此时内存初始化成全0),在这个阶段里,为静态变量分配内存并设置静态变量初始值。这里说的初始值通常情况下,不是代码中写的初始值,而是数据类型的零值。代码中写的初始值,是在初始化阶段赋值的。如果是静态常量(被final修饰),这个阶段就会被直接赋值为代码中写的初始值

4.解析

针对字符串常量进行初始化,把符号引用转为直接引用

字符串常量,需要有一块内存空间存这个字符的实际内容,还需要一个引用,来保存这个内存空间的起始地址。 在类加载之前,字符串常量是处在.class文件中的,这个时候没有地址这个概念,此时这个"引用"记录的并非是真正的地址,而是他在文件中的"偏移量"(或者是个占位符)。 在类加载之后,才真正把这个字符串常量给放到内存中,此时才有"内存地址",这个原因才能真正赋值成指定的内存地址。

5.初始化

真正针对类对象里面的内容进行初始化,加载父类、执行静态代码块的代码.....

总结

问题:一个类,什么时候会被加载呢? 答:不是java程序一运行就将所有的类都加载了,而是真正用到的时候才加载(赖汉模式),比如构造类的实例、调用这个类的静态方法/使用静态属性、加载子类,就会先加载父类。 而一旦加载过后,后续再使用就不需要重新加载了。

"双亲委派模型"

JVM 默认提供了三个类加载器

  • BootstrapClassLoader 负责加载标准库中的类
  • ExtensionClassLoader 负责加载JVM扩展库中的类
  • ApplicationClassLoader 负责加载用户提供的第三方库,用户项目代码中的类

而上述三个类存在"父子关系",BootstrapClassLoader为最大,ApplicationClassLoader为最小。

此处的"父子关系",不是父类、子类,而是每个ClassLoader中都有一个parent属性指向自己的父 类加载器

上述三个类加载器的工作模式:

首先加载一个类的时候,先从ApplicationClassLoader开始

但是ApplicationClassLoader会把加载任务交给父亲,于是ExtensionClassLoader就会去加载,但是也不是真加载,而是再委托自己的父亲去加载

于是BootstrapClassLoader就去加载,他也想委托给父亲,但是发现父亲为null,所以就由自己加载,那么BootstrapClassLoader就会搜索自己负责的标准库中的类,如果找到就加载,如果没找到就交给自己的子类进行加载

于是ExtensionClassLoader就去加载,搜索自己负责的JVM扩展库中的类,找到则加载,找不到就交给子类

于是ApplicationClassLoader进行加载,搜索用户用的第三方库和项目中的类,找到则加载,找不到的话由于自己没有子类,就只能抛出类找不到这种异常

使用这个顺序进行,主要的目的据说为了保证Bootstrap能够先加载,Application能够后加载,这就避免了用户创建一些奇怪的类而导致的bug

比如用户写了一个java.lang.String这个类,此时能保证JVM加载的还是标准库中的类,而不会加载到用户写的这个类,这样就能保证即使出现上述问题,也不会让jvm的代码混乱,最多就是用户写的代码不生效

三. GC 垃圾回收机制

首先要知道的是在JVM中进行垃圾回收的是"堆",并且GC是以"对象"为基本单位进行回收的

GC回收的是整个对象都不再使用的情况,而那种一部分使用,一部分不使用的对象先不回收了(一个对象里有很多属性,可能其中10个属性后面要用,10个属性后面再也不用了)

GC实际工作过程

1.找到垃圾/判定垃圾

关键思路在于看看到底有没有"引用"指向它,Java中使用对象只有一条路,通过引用来使用,如果一个对象有引用指向它那么就有可能被用到,如果没有引用指向它那么就不可能被用到

那么怎么知道对象是否有引用指向?

(1).引用计数(Java没有使用)

给每个对象分配一个计数器(整数),每次创建一个引用指向该对象,计数器就+1,每次该引用被销毁了计数器就-1

问题:这种方法很好理解,但是为什么Java不使用呢?

答: 1.引用计数内存空间浪费的多,每个对象都要分配一个计数器,计数器按照4个字节算,代码中对象非常少无所谓,怕的就是对象特别多,并且每个对象都比较小,那么这个时候占用的额外空间就会很多(一个对象的体积是1k,多4个字节无所谓,一个对象体积是4个字节,再多4个字节相当于体积扩大一倍)

2.存在循环引用问题

(2).可达性分析(Java使用的)

Java中的对象,都是通过引用来指向并访问的,经常是一个引用指向一个对象,这个对象里的成员又指向别的对象

整个Java中的所有对象,就通过类似这种树形/链式结构,整体串起来

可达性分析,就是把所有这些对象组织的结构视为树,从根节点出发,遍历树,所有能被访问到的对象就标记为"可达"(不能被访问到的,就是不可达)

此处的图中虽然只有root引用,但是上述6个对象都是可达的

  • root-->a
  • root.left-->b
  • root.right-->c
  • root.left.left-->d
  • root.left.left.left-->e

小结:可达性分析需要进行类似于"树遍历"整个操作相比于引用计数来说肯定要慢一些的,但是速度慢没关系,上述遍历操作,并不需要一直执行,只需要每隔一段时间分析一遍即可

进行可达性分析遍历的起点,称为GCroots

1.栈上的局部变量

2.常量池中的对象

3.静态成员变量

一个代码中可能有很多这样的GCroots,把每个起点都往下遍历一遍就完成了扫描过程

2.如何清理垃圾

(1).标记清除

标记清理的特点是简单粗暴,但是会导致内存碎片化问题,被释放的空闲空间是零散的,不是连续的

但是我们申请内存的要求是需要连续的空间,总的空闲空间可能很大,但是每个具体的内存空间可能很小,可能导致申请大一点的内存就失败了

例如总的空闲空间是10k,分成1k一个,一共十个,此时如果需要申请2k的空间,那么就会申请失败

(2).复制算法

复制算法解决了内存的碎片化问题

复制算法把整个内存分为两半,用一半丢一半

把不是"垃圾"的对象复制到另外一半内存空间,然后把之前的一半整个空间删掉

若是后续再触发GC,那么就在右边这一半进行复制算法到左边即可

缺点:空间利用率低下,如果垃圾少,有效对象多,那么复制成本就比较大了

(3).标记整理

标记整理解决了复制算法的缺点

类似于顺序表中的删除中间元素,有元素搬运的操作,这样既保证了空间利用率,也解决了内存碎片化问题

但是,很明显,这种做法,效率也不高,元素搬运如果需要搬运的空间大,那么开销也大

上述的三种方法都有其对应的缺点,那么有没有比较完美的办法呢?

"分代回收"

把垃圾回收,分成不同的场景,对应不同的场景使用上述不同的办法

那么如何定义上述的不同场景呢?也就是分代是如何分的?

这里的分,是基于一个经验规律:如果一个东西(对象),存在的时间长了,那么大概率还会继续的长时间存在下去(比如C语言从197x年就开始诞生了,到如今还依然存在并且活跃,那么我们就有理由相信他还会存在很长时间),而上述规律对应Java中的对象也是有效的,java对象要么就是生命周期特别短,要么就是特别长

根据上方的规律,我们给对象引入一个概念"年龄"(这个年的单位是熬过的GC的轮次),年龄越大那么存在的时间就越久

刚new出来的对象,年龄是0的对象,放到伊甸区(出自圣经,上帝在伊甸园造小人)

熬过一轮GC,对象就要被放到幸存区了,那么伊甸区-->幸存区就使用复制算法(将有效对象复制到幸存区,伊甸区整体释放)

到幸存区后,也要接受GC的考验,如果变成垃圾就要被释放,如果不是垃圾,那么就拷贝到另一个幸存区(这两个幸存区,同一时刻值用一个,在两个幸存区反复横跳),所有这里也是使用复制算法(由于幸存区的体积不大,此处浪费的空间也能接受)

如果这个对象在幸存区中GC了很多次了,那么这个时候就进入到老年代,老年代都是年龄大的对象,生命周期普遍更长,那么针对老年代进行的GC扫描频率就低了,如果老年代的对象是垃圾了,那么就使用标记整理的方式进行释放

小结:

上述的GC中典型的垃圾回收算法:如何确定垃圾、如何清理垃圾,这里的策略,实际上在JVM实现的时候,会有一定的差异,JVM有很多的"垃圾回收实现"称为"垃圾回收器",回收器的具体实现做法,会按照上述算法思想展开,不同的垃圾回收器侧重点不同,有的追求GC扫描快、有点追求扫描好、有点追求用户打扰少(STW短).....

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

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

相关文章

基于OrangePi AIpro的后端服务器构建

一. OrangePi AIpro简介 1.1 OrangePi AIpro外观 1.2 OrangePi AIpro配置 OrangePi AIpro(8T)采用昇腾AI技术路线,具体为4核64位处理器AI处理器,集成图形处理器,支持8TOPS AI算力,拥有8GB/16GB LPDDR4X,可以外接32GB…

CLI举例:负载分担场景下的源NAT配置(主备设备共用同一个地址池)

CLI举例:负载分担场景下的源NAT配置(主备设备共用同一个地址池) 组网需求 如图1所示,企业的两台FW的业务接口都工作在三层,上下行分别连接路由器。FW与上下行路由器之间运行OSPF协议。上行接口连接同一个ISP。 现在希…

md是什么?如何打开md类型的文件?假如使用Typora打开,如何免费激活Typora?

md是什么?如何打开md类型的文件 前言一、md是什么简介常见打开md类型文件的方法使用文本编辑器使用专用Markdown编辑器使用在线Markdown编辑器在浏览器中安装插件打开 二、下载安装Typora三、免费激活Typora激活Typora关闭软件每次启动时的已激活弹窗去除软件左下角…

微信资源混淆,导致的约束布局 Constraintlayout 控件重叠!

问题 1、广告六要素 虽然我不参与广告 sdk 接入等相关工作,但是最近总是听到一个词广告六要素。这到底是什么? 国内下载类广告,尤其是针对移动应用推广的广告,其成功实施往往围绕几个关键要素进行,这些要素能够帮助…

Java之Writer类:探索Java中的输出流

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一…

北京邮电大学人工智能考数据结构,均分370!北京邮电大学计算机考研考情分析!

北京邮电大学(Beijing University of Posts and Telecommunications),简称北邮,是中华人民共和国教育部直属、工业和信息化部共建的全国重点大学,位列国家“211工程”、“985工程优势学科创新平台”、“世界一流学科建…

FreeBSD下使用原生虚拟机管理器bhyve

hbyve简介 自 FreeBSD 10.0-RELEASE 起,BSD 许可的 bhyve 虚拟机管理器已成为底层系统不可或缺的一部分。bhyve 强大而灵活,支持多种客户机操作系统,涵盖 FreeBSD、OpenBSD 以及多个 Linux 发行版。在默认配置下,bhyve 提供对串行…

Android 14 - 绘制体系 - 概览

从Android 12开始,Android的绘制系统有结构性变化, 在绘制的生产消费者模式中,新增BLASTBufferQueue,客户端进程自行进行queue的生产和消费,随后通过Transation提交到SurfaceFlinger,如此可以使得各进程将缓…

2024年上半年系统架构设计师——案例第四题——智能汽车相关

写在前面 趁着还记得请,留个纪念。MongoDB的概念完全没记,综合下来只好选这个了。 这题基本上是有自动驾驶背景知识的人的福音,也是第一次考的和现代热点前沿技术相关的内容,而不是考些老嵌入式架构的(上一次看到的还…

sqpserver——利用scott库练习内连接(一)

一.查找每个员工的姓名&#xff0c;部门编号&#xff0c;薪水和薪水等级 select emp.ename, emp.deptno, emp.sal, SALGRADE.GRADE from emp join SALGRADE on emp.sal>LOSAL and emp.sal<HISAL; 二.查找每个部门的编号&#xf…

APM2.8用USB在线下载固件

1.把APM飞控用安卓手机的USB线插入电脑。 选择COM口&#xff0c;不要选择auto&#xff0c;如果你没有COM口说明你驱动安装有问题。 波特率115200。点击相应的图标就可以下载固件到飞控板。 请注意&#xff1a;烧录APM必须选择INSTALL FIRMWARE LEAGACY,第一个是用于刷pixhawk的…

QT C++ 模型视图结构 QTableView 简单例子

在Qt中&#xff0c;MVC模式被广泛使用于各种用户界面框架中&#xff0c;包括Qt的模型视图结构。Qt的模型视图结构是基于MVC模式设计的&#xff0c;其中包括了Model、View和Delegate三个部分。 QTableView是Qt模型视图结构中的一种视图&#xff0c;它用于以表格形式显示数据。 …

【No More Room in Hell】地狱已满服务器一键开服多人联机教程

1、购买后登录服务器 进入控制面板后会出现正在安装的界面&#xff0c;安装大约5分钟&#xff08;如长时间处于安装中请联系我们的客服人员&#xff09; 2、连接游戏 2.1、安装完成后复制开机下方的IP地址 2.2、打开Steam点击左上角的查看➡服务器 点击收藏➡右下角号 粘贴地址…

解决npm卡死,无法安装依赖

npm卡死&#xff0c;无法安装依赖 异常描述原因分析与解决方法 异常描述 1.无法进入命令行&#xff0c;或是很慢没反应 2.装表格无限滚动的el-table-infinite-scroll依赖一上午了&#xff0c;也不能装&#xff0c;报错提示 原因分析与解决方法 1.命令行的问题&#xff1a;缓…

RabbitMQ 之 死信队列

目录 ​编辑一、死信的概念 二、死信的来源 三、死信实战 1、代码架构图 2、消息 TTL 过期 &#xff08;1&#xff09;消费者 &#xff08;2&#xff09;生产者 &#xff08;3&#xff09;结果展示​编辑 3、队列达到最大长度 &#xff08;1&#xff09;消费者 &…

3DF Zephyr v7 解锁版安装教程 (照片转三维模型软件)

前言 3DF Zephyr是一款照片转三维模型软件&#xff0c;可以导出许多常见的3D格式&#xff0c;甚至无需外部工具即可生成无损视频。此外&#xff0c;可以生成真正的正射影像&#xff0c;数字高程模型&#xff08;DTM&#xff09;&#xff0c;甚至可以计算面积&#xff0c;体积&…

软件技术架构全面详解

软件架构全面详解 软件架构 这个与建筑设计架构类似,建筑设计架构师负责设计建筑物的整体结构、布局和功能分配。 而软件架构师,负责设计软件系统的整体组织结构、模块划分、和功能分配。 两者都需要考虑到业务功能、性能、可扩展性、安全性、以及用户体验等方面。 软件架…

【深度学习】ultralytics, yolo seg,实例分割图绘制,核对yolo seg 的txt标记对不对

这段代码的作用是从指定路径读取图像和标签文件&#xff0c;然后在图像上绘制分割区域和相关点&#xff0c;并保存最终的图像。以下是每个函数的具体作用及其解释&#xff1a; read_labels(label_path): 读取指定路径的标签文件。标签文件的每一行表示一个物体的分割信息&#…

硬盘的分区

目录 概念 硬盘的分区 实操 创建分区 fdisk&#xff08;<2T&#xff09; 创建文件系统 挂载 自动挂载&#xff08;永久挂载&#xff09; gpt区分 swap 交换分区 如何删除已挂载的分区 概念 硬盘&#xff1a;计算机的存储设备。&#xff08;如无特殊说明&#xff0…

sklearn线性回归--岭回归

sklearn线性回归--岭回归 岭回归也是一种用于回归的线性模型&#xff0c;因此它的预测公式与普通最小二乘法相同。但在岭回归中&#xff0c;对系数&#xff08;w&#xff09;的选择不仅要在训练数据上得到好的预测结果&#xff0c;而且还要拟合附加约束&#xff0c;使系数尽量小…