浅谈 JVM 的内存划分、类加载、垃圾回收机制

文章目录

  • 一、JVM内存划分
    • 1.1、JVM为什么要进行内存划分?
  • 二、JVM类加载
    • 2.1、什么是类加载?
    • 2.2、类加载大体过程
    • 2.3、何时触发类加载?
    • 2.4、双亲委派模型[!面试高频问题]
      • 2.4.1、类加载器
      • 2.4.1、什么是双亲委派模型?
  • 三、JVM 垃圾回收机制(GC)
    • 3.1、什么是GC
    • 3.2、GC回收哪部分内存?
    • 3.3、GC机制具体怎么回收垃圾?
      • 3.3.1、先 找出垃圾(判定哪个对象是垃圾)
      • 3.3.2、再 回收垃圾(释放内存)

一、JVM内存划分

JVM(Java Virtual Machine) ,Java虚拟机。

1.1、JVM为什么要进行内存划分?

java程序,是一个名字为 java 的进程,这个进程就是我们所说的 “JVM”。JVM(进程)会先从OS(操作系统)处申请一大块内存空间,在这个基础上再把这个内存空间划分成几个小的内存区域,每个区域都有自己的功能作用。

在这里插入图片描述
那么JVM里的内存区域大概划分主要为:(jdk 1.7 是这样的)
在这里插入图片描述
jdk1.8 之后,就把之前的方法区换成了元数据区。之前方法区是在 JVM 从OS 中申请到的一大块内存上划分内存区域,而元数据区,用的是本地内存 (JVM内部的C++代码里弄的内存)

面试中,一般关于JVM内存划分的问题,直接按照 jdk1.7 版本的划分方式回答。

有的面试官会通过简答题的形式让你详谈关于JVM的内存划分、内加载、垃圾回收机制是怎么一回事,有的面试官会通过代码题的形式考察JVM的知识点。

比如类似这样:

public class Test{private int a = 90;private static int b = 100;public static void main(String[] args){Test t = new Test();} 
}

请问:
1、当前代码中的 t 是在内存中的哪个区域? —> 栈区
2、a 是在内存中的哪个区域? —> 堆区
3、b 是在内存中的哪个区域? —> 方法区

其实大多数同学会认为,t 是一个引用变量,所以 t 应该是在堆区,包括我自己也是常常有这个误区。

但是其实变量在哪个部分,和变量类型无关,和变量的形态有关。局部变量存在栈区,成员变量存在堆区,静态的存在方法区。

二、JVM类加载

类加载,是一个很复杂的事情。

2.1、什么是类加载?

java程序在运行之前需要先编译(将 .java文件变成 .class文件(二进制字节码文件))

java程序运行时,java进程(即 JVM)就会读取对应的 .class 文件,并且解析内容,在内存中构造出类对象,并进行初始化…

类对象:描述了这个类是什么样的,类里包含哪些属性,这些属性的名字、类型,被什么修饰词修饰;类里包含哪些方法,这些方法的名字、类型是什么;还描述了这些类继承自哪个父类,实现了哪些接口。因此类对象也是创建实例的具体依据。

类加载就是:将类从文件加载到内存中。

类对象是类加载之后的结果。

2.2、类加载大体过程

1、加载
      java进程找到.class文件,从.class文件中读取内容并解析。
2、连接
       (2.1)、验证
      检查当前解析的.class文件其内容格式是否符合要求。
       (2.2)、准备
       给类里的静态变量分配内存空间,int类型就是分配4个字节的内存空间,同时这些空间初始情况全是0.
       (2.3)、解析
      初始化字符串常量。会把符号引用(占位符)替换成直接引用(内存地址)
       .class 文件里包含很多字符串常量,比如 String x = “helloworld!” 就是一个字符串常量。在类加载之前,”helloworld!“ 这个字符串是没有分配内存空间的(类加载之后才有内存空间),没有内存空间,x 就无法保存这个字符串常量的真实地址,只能显示用一个占位符标记一下这块地方是字符串常量 x 的地址,等到真正给 x 分配内存之后,就可以用真正的地址替代之前的占位符。

3、初始化
      针对类进行初始化,初始化静态成员,初始化静态代码块,并加载父类…

2.3、何时触发类加载?

使用到一个类的时候,就会触发类加载(类并不一定是程序一启动就加载,是第一次使用时才加载(懒汉模式))

那怎么样才算是使用到一个类呢??
1)、创建这个类的实例时
2)、使用了类的静态方法或静态属性
3)、使用了类的子类(加载子类会触发加载父类)

2.4、双亲委派模型[!面试高频问题]

类加载的重要环节其实是:解析.class文件,校验.class文件,构造.class对象。所以相对来说双亲委派没那么重要。但是奇怪的是,面试中考察双亲委派却比较多。

2.4.1、类加载器

JVM加载类,是由 类加载器(class loader) 这样的模块负责的。

JVM自带了多个类加载器(包括程序员也可以自己实现类加载器):
1)、bootstrap ClassLoader
负责加载标准库中的类。
2)、Extension ClassLoader
负责加载 JVM 扩展的库里的类(即语言规范中未记录的,但 JVM 实现了的)
3)、Application ClassLoader
负责加载开发人员开发的项目中自己自定义的类

这3个 类加载器 各自负责各自的一个片区(即:各自负责自己的一组目录)

2.4.1、什么是双亲委派模型?

在这里插入图片描述

描述上述 类加载器 相互配合的工作过程,就是双亲委派模型。

那类加载器如何相互配合呢??
1)、上述3个类加载器存在如上父子关系。Bootstrap ClassLoader 是 Extension ClassLoader 的父 类加载器,Extension ClassLoader 是 Application ClassLoader 的父 类加载器。
2)、进行类加载的时候,输入的内容叫做 全限定类名,形如:java.lang.Thread
3)、加载的时候从 Application ClassLoader 开始
4)、某个类加载器开始加载的时候,不会立即扫描自己负责的路径,而是先把任务委派给父 “类加载器” 来先进行处理。
5)、找到最上面的 Bootstrap ClassLoader, 再往上,就没有父类加载器了,Bootstrap ClassLoader 只能自己手动加载了。
在这里插入图片描述
6)、如果父亲没有找到类,就交给自己的儿子,继续加载。
在这里插入图片描述
7)、如果一直找到下面的 Appliacation ClassLoader 也没找到类,就会抛出一个 “类没找到” 的异常,表示类加载失败。

按照这个顺序/规则加载类,好处是:
假设开发人员自定义的类,其全限定类名与标准库中的类名一致,此时仍然可以保证类加载可以加载到标准库的类,防止了加载错类,代码报错的问题。

三、JVM 垃圾回收机制(GC)

3.1、什么是GC

GC(垃圾回收) 用于解决 内存泄漏 的问题。程序员只需要负责申请内存,释放内存的工作,交给 JVM 来完成。JVM会自动判定当前内存是否还在使用,不再使用的话,就进行释放。

使用GC最大的问题在于引入了额外的开销:时间 + 空间 的成本。时间:STW问题(Stop The World)程序在进行GC时,反映在用户这里的问题就是产生明显的卡顿,这样的卡顿十分影响用户体验。空间:消耗了额外的CPU/内存等资源。

但GC仍然是开发中必不可少的!但由于C++一般追求的是效率,所以C++的垃圾回收不采取GC机制,而是采用智能指针的方式。其他语言,Python/Java/Go/PHP…这些都是采用GC机制回收垃圾预防内存泄漏。

3.2、GC回收哪部分内存?

在JVM内存区域划分处我们知道内存区域主要划分成4部分:栈区、堆区、方法去、程序计数器。
在这里插入图片描述
而GC主要针对堆区这部分的内存进行回收。
堆区又可以细分成这几部分:
在这里插入图片描述

我们GC时,千万不能将 正在使用的内存 回收了,一定要确保是已经不再使用的内存,才能够回收。否则回收错了,会影响程序的正确运行。

3.3、GC机制具体怎么回收垃圾?

3.3.1、先 找出垃圾(判定哪个对象是垃圾)

那怎么找出垃圾??涉及一系列复杂策略。

当一个对象再也不用了,就说明其是垃圾了。在 Java 中,对象的使用,需要凭借 引用,假设一个对象,已经没有任何引用指向它的时候,这个对象自然就无法再被使用了。

所以找出垃圾最关键的要点就是通过引用判定当前对象是否还能被使用,没有引用就视为是无法被使用。

两种典型的,判定对象是否存在引用的办法:
1、引用计数 [不是 JVM 采取的办法]
给每个对象都加上个计数器,这个计数器就表示 “当前的对象有几个引用”。
举例此处有个对象:MyTest m = new MyTest();
在这里插入图片描述
每次多一个引用指向该对象,计数器就 +1;每次少一个引用指向该对象,计数器就 -1(比如引用是一个局部变量,出了作用域,引用计数就-1;比如引用是一个成员变量,所在对象销毁了,引用计数就+1)。

当引用计数为0时,就说明当前这个对象已经无人能够使用了,此时这个对象就能够被回收了。

(我上面画的图,m、n引用不一定是局部变量,也可能是成员变量,因此他们可以和 MyTest 对象在同一条内存条上。其次,这个内存空间有可能是机器上上的整条内存空间)

引用计数的优点:
简单容易,执行效率高。
引用计数的缺点:
(1)、空间利用率低,尤其是小对象。比如当计数器是int时,对象本身只有一个int成员,本来对象里只需要存储一个int成员,只需要花费4个字节,但是加上int计数器,就需要花费8个字节。
(2)、可能会出现循环引用的情况。
举个例子:
在这里插入图片描述
就像是你有两套房子,房1的钥匙锁房2里,房2的钥匙锁房1里了,此时两套房子的门都打不开了。因此循环引用就好比死锁的循环依赖,因此Java不采用引用计数的方式判定对象当前含有几个引用,而是采用可达性分析的方式,其他语言采用引用计数的方式是因为他们采用的别的手段来预防引用计数的循环引用。

1、可达性分析 [是 JVM 采取的办法]
约定一些特定的变量,称为 “GC roots”。每隔一段时间,从 GC roots 出发,进行遍历,看看当前哪些变量是能够被访问到的。能够访问到的变量就称为 “可达”,不能够访问到的就称为 “不可达”。

GC roots 有哪些?
(1)、栈上的变量
(2)、常量池引用的对象
(3)、方法区引用类型的静态变量
每一组都有一些变量,每个变量都可以视为是起点。从这些起点出发,尽可能遍历,就能够找到所有能够访问到的对象。

在这里插入图片描述

为什么要指出是否是 JVM 采取的办法吗是因为,面试时,如果面试官问:针对垃圾回收机制判定对象的引用是否存在采用的方法有哪些??引用计数、可达性分析这两个都可以答。但是如果面试官问:针对Java的垃圾回收机制判定对象的引用是否存在采用的方法有哪些??只回答 可达性分析!

3.3.2、再 回收垃圾(释放内存)

那怎么回收垃圾??涉及一系列复杂策略。主要有4个方法:
1、标记清除
在这里插入图片描述
标记清除这种方式虽然简单便捷,不过它有一个问题就是:内存碎片:本来我们的内存空间是一大片、一整片的内存空间,现在释放了一些内存空间之后,导致整个内存空间变得十分零碎。

假设上述图片中的每个灰色区域是1K,此时整条内存就有4K空闲空间。但是由于内存是离散的,导致我们想申请2K的内存空间(连续的),也申请不了。因此内存碎片导致内存利用率低,故这是GC中回收内存的一种办法,但是由于内存碎片的问题,它并不被广泛使用。

2、复制算法
复制算法是针对标记清除方法中的内存碎片问题而引入的一种方法。

复制算法就是将一整块内存空间分成两半,使用左半侧空间时,右半侧不用;使用右半侧空间时,左半侧空间不用。但是并不是不用的那半侧空间没有效果,它会在垃圾回收的时候起到效果。
在这里插入图片描述
但是复制算法也有自己的问题:
1、空间利用率比较低(一整块内存只用一半,用一半丢一半)
2、如果一轮 GC 下来,大部分对象要保留,只有少数对象要回收,这个时候复制算法的开销就很大的了。

3、标记整理
标记整理是针对复制算法的缺点应运而生的。

标记整理类似于顺序表的删除元素,含有搬运元素的操作。
在这里插入图片描述
标记整理相对于上述复制算法来说,空间利用率高了,同时还能解决内存碎片问题,但是标记整理的搬运操作也很耗时。

因此我们需要因地制宜的选择合适的回收方式。

4、分代回收
分代回收,是将上面的3个方法综合起来,根据不同对象的特点,采取不同的回收方式。主要是根据对象年龄的特点来划分的,对象年龄是根据GC的轮次来算,GC轮次是相当于有一组线程周期性扫描线程里的所有对象,如果一个对象经历了一次GC但没有被回收,此时就表示它的年龄+1,以此类推,经理几轮GC,对象年龄就加几。

对象年龄不一样,对象所表现出的特点则不一样:如果一个对象年龄越大,它可能会活得越久。就像C语言,已经存在了50多年,我们可以合理推测,它还能继续存在50多年。

基于上述内容,就可以针对对象的年龄进行分类,把堆里的对象分成:新生代(年龄小的对象)和老年代(年龄大的对象)。新生代的对象容易被GC,老年代的对象不容易被GC。

在这里插入图片描述

分代回收还有一个特例:如果一个对象很大,那么这个对象可以直接进入老年代。一个大对象,从新生代到老年代,需要经过复制算法,但是大对象进行复制算法开销很大,因此大对象直接进入老年代;同时一个大对象,创建出来也不是为了立即销毁的,因此可以直接进入老年代。

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

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

相关文章

Flink SQL 的工作机制

前言 Flink SQL 引擎的工作流总结如图所示。 从图中可以看出,一段查询 SQL / 使用TableAPI 编写的程序(以下简称 TableAPI 代码)从输入到编译为可执行的 JobGraph 主要经历如下几个阶段: 将 SQL文本 / TableAPI 代码转化为逻辑执…

书生大模型实战营--L1关卡-OpenCompass 评测 InternLM-1.8B 实践

一、使用 OpenCompass 评测 internlm2-chat-1.8b 模型在 MMLU 数据集上的性能 1、使用lmdeploy部署 internlm2-chat-1.8b模型 2、根据OpenCompass官网教程安装并下载数据集 opencompass/README_zh-CN.md at main open-compass/opencompass GitHub 注意: pyhton…

【Java】this关键字、构造方法、标准javabean类(009)

目录 ♦️构造方法 🎃无参数构造方法(空参构造) 🎃有参数构造方法 ♦️this关键字 🎃就近原则 🎃使用this关键字调用本类中的属性 ​编辑 🎃使用this关键字调用成员方法 ​编辑 &#x…

Collention集合基础知识

Array 数组是一种连续的内存空间存储相同数据类型数据的线性数据结构 数组获取其他元素的地址值 寻址公式 a[i] baseaddress i*datatypesize 为什么数组索引从0开始 从1开始不行吗 从0开始寻址公式 a[i] baseaddress i*datatypesize 从1开始寻址公式 a[i] baseadd…

【计算机网络】无线网络和移动网络(第9章)大纲(共70+页)

最后只复习了1.5天,应用层简单过了一遍。 本来是mindmap的,但是太大了只能导出成提纲了,凑合看吧orz。 如果你找我要源文件,最好是在2024年,不然我可能就找不到了()。

基于STC8H系列单片机的中断系统

基于STC8H系列单片机的中断系统 STC8H4K64TL单片机介绍STC8H4K64TL单片机管脚图(48个引脚)STC8H4K64TL单片机串口仿真与串口通信STC8H4K64TL单片机管脚图(32个引脚)STC8H4K64TL单片机管脚图(20个引脚)STC8H系列单片机管脚说明STC8H系列单片机I/O口STC8H系列单片机I/O口相…

【C++】:红黑树的应用 --- 封装map和set

点击跳转至文章:【C】:红黑树深度剖析 — 手撕红黑树! 目录 前言一,红黑树的改造1. 红黑树的主体框架2. 对红黑树节点结构的改造3. 红黑树的迭代器3.1 迭代器类3.2 Begin() 和 End() 四,红黑树相关接口的改造4.1 Find…

centos stream 9安装 Kubernetes v1.30 集群

1、版本说明: 系统版本:centos stream 9 Kubernetes版本:最新版(v1.30) docker版本:27.1.1 节点主机名ip主节点k8s-master172.31.0.10节点1k8s-node1172.31.0.11节点2k8s-node2172.31.0.12 2、首先,使用Vagrant和Virt…

【2024年国际高等学校数学建模竞赛IMMCHE】问题 B:太空移民计划和战略 问题分析及数学模型及求解代码

【2024年国际高等学校数学建模竞赛IMMCHE】问题 B:太空移民计划和战略 问题分析及数学模型及求解代码 Problem B: Space Migration Program and Strategy 1 题目 我们的未来有两种可能:第一,我们将留在地球上,直到完全灭绝&…

Hive3:Hive初体验

1、创建表 CREATE TABLE test(id INT, name STRING, gender STRING);2、新增数据 INSERT INTO test VALUES(1, 王力红, 男); INSERT INTO test VALUES(2, 钉钉盯, 女); INSERT INTO test VALUES(3, 咔咔咔, 女);3、查询数据 简单查询 select * from test;带聚合函数的查询 …

Halcon 引擎方式调试

1.C# 端添加代码 启动调试模式 public HDevEngine MyEngine new HDevEngine(); // halcon引擎;// 启动调试服务 MyEngine.StartDebugServer();2.Halcon程序添加到进程 打开Halcon程序 【执行】>【附加到进程】 点击【确定】 3.C# 程序执行到相关位置 C# 程序执行调用…

vector深度剖析及模拟实现

目录 前言vector核心框架模拟实现1. 前期准备2. 构造和销毁补充: 隐式类型转换和多参数构造的区别 3. 迭代器相关4. 容器相关补充: memcpy拷贝问题 5. 元素访问6. vector的修改测试代码 总结 前言 本文重点模拟实现vector的核心接口, 帮助我们更好的理解底层逻辑, 以及对vecto…

科学又省力 宠物浮毛怎么去掉便捷高效?除毛秘籍养宠空气净化器

上次和朋友逛完街去她家,她家的猫哈基米一开门就飞奔过来,朋友直接抱起它狂亲。结果,猫毛和汗水粘得到处都是,手臂上、脸上都是,看得我这鼻炎星人直起鸡皮疙瘩。很多养宠物的朋友都说,天天给猫狗梳毛&#…

Android Studio导入源码

在有源码并且编译环境可用的情况下: 1.生成导入AS所需的配置文件 在源码的根目录执行以下命令: source build/ensetup.sh lunch 要编译的项目 make idegen //这一步会生成out/host/linux-x86/framework/idegen.jar development/tools/idegen/idegen.sh…

利用OSMnx求路网最短路径并可视化(二)

书接上回,为了增加多路径的可视化效果和坐标匹配最近点来实现最短路可视化,我们使用图形化工具matplotlib结合OSMnx的绘图功能来展示整个路网图,并特别高亮显示计算出的最短路径。 多起终点最短路路径并计算距离和时间 完整代码#运行环境 P…

《昇思25天学习打卡营第24天|基于MindSpore通过GPT实现情感分类》

基于MindSpore通过GPT实现情感分类 %%capture captured_output # 实验环境已经预装了mindspore2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号 !pip uninstall mindspore -y !pip install -i https://pypi.mirrors.ustc.edu.cn/simple mind…

自动化测试 pytest 中 scope 限制 fixture使用范围!

导读 fixture 是 pytest 中一个非常重要的模块,可以让代码更加简洁。 fixture 的 autouse 为 True 可以自动化加载 fixture。 如果不想每条用例执行前都运行初始化方法(可能多个fixture)怎么办?可不可以只运行一次初始化方法? 答&#xf…

C语言进阶 11.结构体

C语言进阶 11.结构体 文章目录 C语言进阶 11.结构体11.1. 枚举11.2. 结构类型11.3. 结构与函数11.4. 结构中的结构11.5. 类型定义11.6. 联合11.7. PAT11-0. 平面向量加法(10)11-1. 通讯录的录入与显示(10) 11.1. 枚举 常量符号化: 用符号而不是具体的数字表示程序中的数字 cons…

【C++深度探索】AVL树与红黑树的原理与特性

🔥 个人主页:大耳朵土土垚 🔥 所属专栏:C从入门至进阶 这里将会不定期更新有关C/C的内容,欢迎大家点赞,收藏,评论🥳🥳🎉🎉🎉 前言 前…

渣土车与搅拌车安全问题解析及智能监控解决方案

一、背景分析 近年来,渣土车在货物运输中由于超载超速、违规驾驶、车辆盲区过大等问题导致的事故频发,严重影响了人们的生命财产安全。而搅拌车作为一种特殊的运输车辆,在混凝土输送过程中也存在类似的隐患。针对这些问题,对搅拌…