一篇文章带你解决笔试面试中的jvm问题

article/2025/7/16 21:25:16

JVM内存区域划分

JVM启动的时候,会申请到一整个很大的内存区域.JVM是一个应用程序,要从操作系统里申请内存.JVM就根据需要,把空间分为几个部分,每个部分各自有不同的功能.具体划分如下:

分为:栈,堆,程序计数器,元数据区

Heap(堆):整个JVM空间最大的区域.new出来的对象,类的成员变量,都在堆上.**堆是一个进程只有一份,栈是每个线程有一份.**一个进程里有多个线程.所以一个进程有多个栈.每个jvm就是一个java进程.

元数据区:即方法区.一个进程里只有一块,多个线程共用一块.

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

JVM类加载机制

类加载整体流程

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

.class文件可以有多个类对象
.class文件是编译后的Java源代码,它包含了编译后的字节码指令.
类加载主要分为以下几个过程:

  • 加载:把.class文件找到(找的过程),打开文件,地文件,把文件内容读到内存中.

  • 验证:检查.class文件格式是否正确. .class文件是一个二进制文件,这里的格式是有严格说明的,官方提供了JVM虚拟机规范.规范文档上描述了.class的格式:

  • 准备:给类对象分配内存空间(此时内存初始化为全0)

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

字符串常量,得有一块内存空间,存这个字符的实际内容,还得有一个引用,用来保存这个内存空间的起始地址.
在类加载之前,字符串常量,此时是处在.class文件中的,此时这个"引用"记录的并非是字符串常量的真正地址,而是它在文件中的"偏移量".(或者是占位符)(符号引用)
类加载之后,才真正在把这个字符串常量放到内存中,此时才有"内存地址",这个引用才能被真正赋予成指定内存地址.(直接引用)

举个🌰: 小学的时候,学校组织以班为单位大家看电影,
但是到电影院之前我不知道自己的真实地址(真实座位),但是我知道我前面是A,后面是B,此时到了电影院,我们仨也是挨着的.(符号引用)
此时我只知道自己的相对位置. 到了电影院之后,老师组织同学们坐下.我坐下之后才知道我们的真正位置.(直接引用)

• 初始化:调用构造方法,进行成员初始化,执行代码块,静态代码块,加载父类.

类加载的时机

一个类,不是java程序已运行,就把所有的类都加载了.而是真正使用的时候才加载(懒汉模式)
在以下场景中会触发类加载:

  1. 构造类的实例
  2. 调用这个类的静态方法/使用静态属性
  3. 加载子类,就会先加载其父类

以上都是用到了再加载,一旦加载过后,后续使用就不用再加载.

双亲委派模型(经典)

此过程发生在加载阶段.
双亲委派模型:描述的是加载 找.class文件的基本过程.
JVM默认提供了三个类加载器:

  1. 启动类加载器(BootStrap ClassLoader):负责加载Java标准库中的类(无论是哪种JVM的实现,都会提供这些一样的类)
  2. • 扩展类加载器(Extension ClassLoader):负责加载JVM扩展库中的类.(由实现JVM的厂商/组织提供的额外功能)
  3. • 应用程序类加载器(Application ClassLoader):负责加载项目中自己写的类以及第三方库中的.

上述三个类,存在"父子关系"(不是父类子类,相当于每个class loader有一个parent属性,指向自己的父 类加载器).
那么上述类加载器如何配合工作呢?

首先加载一个类的时候,实现从ApplicationClassLoader开始.但是ApplicationClassLoader会把家在任务交给父亲,让父亲去进行.于是ExtensionClassLoader要去加载了,但是也不是真的加载,而是再委托给自己的父亲.于是BootstrapClassLoader要去加载了,也是想委托给自己的父亲,结果发现,自己的父亲是null. 没有父亲/父亲加载完了,没找着类,才由自己进行加载,此时BootstrapClassLoader就会搜索自己负责的标准库目录的相关的类,如果找到就加载,如果没找到,就继续由子类加载器进行加载.然后由ExtensionClassLoader真正搜索扩展库相关的目录,如果找到了就加载,如果没找到就由子类加载器进行加载.然后由ApplicationClassLoader真正搜索用户项目相关的目录,如果找到就加载,如果没找到就由子类加载器进行加载(由于当前没有子了,只能抛出 类找不到这样的异常)

那么为什么要有上述顺序呢?.

上述这套顺序其实是出自于JVM实现代码的逻辑,这段代码大概是使用递归的方式写的.但这个顺序,最主要的目的就是为了保证Bootstrap能够先加载,Application能够后加载.这样就可以避免说因为用户创建了一些奇怪的类,引起不必要的bug.

假设用户在自己的代码中写了个java.lang.String按照上述加载流程,此时JVM加载的还是标准库的类,不会加载到用户自己写的这个类. 这样就能保证,即使出现上述问题,也不会让jvm已有代码混乱,最多是用户自己写的类不生效.

另一方面,类加载器,其实是可以用户自定义的.上述三个类加载器是jvm自带的.用户自定义的类加载器,也可以加入到上述流程中,就可以和现有的加载器配合使用,

JVM垃圾回收机制(GC)

垃圾:指的是不再使用的内存空间.垃圾回收,就是把不用的内存帮我们自动释放.

在执行程序时,要在堆上申请一块内存空间.在C/C++中,上述内存空间需要手动方式进行释放.如果不手动释放的话,这块内存的空间就会持续存在,一直存在到进程结束(堆上的内存生命周期比较长.不像栈,栈的空间会随着方法执行结束,栈帧销毁而自动释放,堆则默认不能自动释放.)那么这就可能会导致一个严重的问题—内存泄漏.如果内存一直占着不用,又不释放,就会导致剩余空间越来越少.进一步导致后续的内存申请操作失败.因此,大佬们想了一些办法,来解决内存泄漏的问题.

GC是其中最为主流的方式.(Java Go Python PHP JS大部分的主流语言都是使用GC解决内存泄漏的问题的)

但GC中有一个比较关键的问题:STW问题(stop the world)
如果有时候,内存中的垃圾已经非常多了,此时触发一次GC操作,开销可能非常大,大到可能把系统资源吃了很多.另一方面GC回收垃圾的时候可能会涉及到一些锁操作.导致业务代码无法正常执行.这样的卡顿,极端情况下,可能是出现即使毫秒甚至上百毫秒.

JVM中有很多内存区域:
1. 堆
2. 栈
3. 程序计数器
4. 元数据区
5. …
GC主要是针对堆进行释放的.
GC是以"对象"为基本单位,进行回收的.而不是字节!

GC回收的是整个对象都不再使用的情况.而一部分使用一部分不使用的对象,暂且不回收.

GC实际工作过程

1. 找到垃圾/判定垃圾
2. 再进行对象的释放

找到垃圾/判定垃圾

找到垃圾/判定垃圾的关键就是看到底有没有"引用"指向它.如果一个对象,有引用指向它,就可能被使用到;如果一个对象,没有引用指向了,就不会再被使用了.

具体如何知道对象是否有引用指向呢?

1. 引用计数(不是java的做法,Python/PHP)

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

{Test t = new Test();//Test对象的引用计数1Test t2 = t;//t2也指向了t,引用计数2Test t3 = t;//引用计数就是3
}
//大括号结束,上述三个引用超出作用域失效.此时引用计数就是0了,此时new Test()对象就是垃圾了.

这个办法简单有效,但是java没有采用.但是这个办法也有一定的缺点:
1. 内存空间浪费的多(利用率低)
每个对象都要分配一个计数器,如果按四个字节计算.代码中的对象非常少就无所谓.但是如果

对象特别多,占用的额外空间就会很多,尤其是每个对象都比较小的情 况.例如:一个对象体积1k,此时多4个字节,无所谓.一个对象体积事4字节,此时多4个字节,相当于体积扩大一倍.

2. 存在循环引用的问题

class Test{Test t = null;
}
Test a = new Test();//1号对象,引用计数是1
Test b = new Test();//2号对象,引用计数是1
a.t = b;//a.t也指向2号对象了,2号对象的引用是2
b.t = a;//b.t也指向1号对象了,1号对象的引用是2

 接下来,a和b引用销毁,此时a和b计数-1,但引用计数结果还都是1,不能释放资源,但实际这两个对象已经无法访问了。python/php使用引用计数,需要搭配其他机制来避免循环引用.

2. 可达性分析(Java的做法)

把对象之间的引用关系理解成了一个树形结构,从一些特殊的起点出发,进行遍历,只要能遍历访问到的对象,就是"可达的",再把"不可达的"当作垃圾即可.

就像上述的二叉树,root指向根节点a.
如果root.right.right = null ,此时就表示f不可达
如果root.right = null此时就表示c不可达,f也不可达了。

可达性分析,总的来所,就是从所有的roots的起点出发,看看该对象里又通过引用能访问那些对象,依次遍历,把所有可以访问的对象都给遍历一遍(遍历的同时把对象标记成"可达"),剩下的遍历不到的对象就是"不可达".

可达性分析的特点:可达性分析克服了引用计数的两个缺点,但是也有自己的缺点:

1.消耗的时间更多,因此某个对象成了垃圾,也不一定能第一时间发现,因为扫描的过程,需要消耗时间
2. 在进行可达性分析的时候,依次遍历,一旦这个过程中,当前代码中的对象引用关系发生了变化,这就会使情况变得更加复杂。比如,当一个对象指向下一个对象,刚遍历完这个对象,这个对象的引用变了。因此,我们为了更准确的遍历,需要让其他的业务线程暂停工作(STW问题)。

 清理垃圾

1. 标记清除

这种策略,就是直接把垃圾对象的内存释放,但是这个方式的缺点就是会产生内存碎片.

 

我们从内存中申请空间的时候,都是整块的连续的空间,现在这里空闲的空间是离散的,独立的空间,总的空间可能很大.假如总的空闲的空间可能超过了1G,但是你想申请500MB可能都不一定申请到。

2. 复制算法

为了解决内存碎片的问题,又引入了复制算法.复制算法,是把整个内存空间,分成两半,一次只用一半

现在将2和4标记为垃圾,要释放垃圾,复制算法会将左边不需要释放内存的空间复制到右边的空间中,然后整体释放左边空间的内存.
复制算法,就是把"不是垃圾"的对象复制到另外一半,然后把整个空间删除掉.
每次触发复制算法,都是向另外─侧进行复制,内存中的数据拷贝过去.

缺点:

  1. 空间利用率低
  2. 如果要是垃圾少,有效对象多,复制成本就比较大了~~
3. 标记整理

这种方法,保证了空间利用率,同时也解决了内存碎片问题
但是这种做法的缺点:
效率不高如果要搬运的空间比较大,此时开销也很大

4.分代回收

 基于上述这些基本策略,搞了一个复合策略"分代回收"
把垃圾回收,分成不同的场景,有的场景有这个算法,有的场景有那个,各展所长.

分代是怎么分的?
基于一个经验规律:如果一个东西,存在的时间比较长了,那么大概率还会继续的长时间持续存在下去.(要没早就没了,既然存在,肯定有点用)

java的对象要么就是生命周期特别短,要么就是特别长.根据生命周期的长短,分别使用不同的算法.
给对象引入一个概念,年龄.(单位不是年,而是熬过GC的轮次)(经过了这一轮可达性分析的遍历,发现这个对象还不是垃圾.这就是"熬过一轮GC") 年龄越大,这个对象存在的时间就越久.

刚new 出来的,年龄是0的对象,放到伊甸区.(出自圣经,上帝在伊甸园造小人)
熬过一轮GC,对象就要被放到幸存区了.虽然看起来幸存区很小,伊甸区很大,一般够放.

伊甸区到幸存区,采用的是复制算法.
幸存区之后,也要周期性的接受GC的考验.
如果变成垃圾,就要被释放.如果不是垃圾,拷贝到另外一个幸存区(这俩幸存区同一时刻只用一个),在两者之间来回拷贝(复制算法),由于幸存区体积不大, 此处的空间浪费也能接受.如果这个对象已经再两个幸存区中来回拷贝很多次了这个时候就要进入老年代了·
老年代都是年纪大的对象.生命周期普遍更长.针对老年代,也要周期性GC扫描,但是频率更低了
如果老年代的对象是垃圾了,使用标记整理的方式进行释放.

本期的jvm讲解就到这里结束了,咱们的javaEE初阶之旅就到这里了,后面就要开启进阶的学习啦!!!!!


http://www.hkcw.cn/article/UOCbxweEKM.shtml

相关文章

通义灵码2.5——基于编程智能体开发Wiki多功能搜索引擎

引言 在智能化浪潮重塑软件开发范式的今天,我借助开发一个基于编程智能体开发Wiki 多功能搜索引擎,深度体验了通义灵码2.5这一阿里云旗舰级AI编码助手,构建智能协作新范式。 该平台通过三大技术突破赋能开发全流程:基于编程智能…

基于SpringBoot的商家销售管理网站的设计与实现

湖南软件职业技术大学 本科毕业设计(论文) 设计(论文)题目 基于SpringBoot的商家销售管理网站的设计与实现 学生姓名 学生学号 所在学院 专业班级 校内指导教师 企业指导教师 毕业设计(论文)真实性承诺及声明 学生对毕业设计(论文)真实性承诺 本人郑重声明:所提交的毕…

各种噪声电流激励下电源PDN网络对系统时钟性能的影响

点击上面“蓝字”关注我们 电源分配是支持所有类型硅产品运行的基础设施的重要组成部分,但在设计过程中常被忽视。电源质量可能限制电路性能,并决定其工作可靠性。要真正解决电源分配问题,必须考虑包括芯片、封装和PCB在内的整个系统。 芯片…

【大模型】情绪对话模型项目研发

一、使用框架: Qwen大模型后端Open-webui前端实现使用LLamaFactory的STF微调数据集,vllm后端部署, 二、框架安装 下载千问大模型 安装魔塔社区库文件 pip install modelscope Download.py 内容 from modelscope import snapshot_downlo…

关于位图Bitmaps的介绍

目录 1、基本概念 1.1、介绍 1.2、关键字 1.3、结构原理 2、常用命令 2.1、SETBIT 2.2、GETBIT 2.3、BITCOUNT 2.4、BITOP 2.5、BITPOS 3、应用场景 4、使用示例 前言 Redis的Bitmaps是一种基于字符串的数据结构,用于处理位级别的操作。 Bitmaps在Redis…

【软件设计】通过软件设计提高 Flash 的擦写次数

目录 0. 个人简介 && 授权须知1. Flash 和 EEROM 基本情况2. 场景要求3. 软件设计思路4. 代码展示4.1 flash.h4.2 flash.c 0. 个人简介 && 授权须知 📋 个人简介 💖 作者简介:大家好,我是喜欢记录零碎知识点的菜鸟…

C 语言练习--初级

#学习C 代码, 做小练习时,自己运行代码竟然发现很多错误,记录一下。 1、计算器 根据输入的数值和符合,输出相应结果。 结果: #include "stdio.h"int Primary_math(int a, int b, char sign){int num0;swit…

pikachu通关教程-CSRF XSS

XSS XSS漏洞原理 XSS被称为跨站脚本攻击(Cross Site Scripting),由于和层叠样式表(Cascading Style Sheets,CSS)重名,改为XSS。主要基于JavaScript语言进行恶意攻击,因为js非常灵活…

E. Melody 【CF1026 (Div. 2)】 (求欧拉路径之Hierholzer算法)

E. Melody 思路 将所有出现过的音量和音高看作一个点,一个声音看作一条边,连接起来。那么很容易知道要找的就是图上的一条欧拉路径(类似一笔画问题) 又已知存在欧拉路径的充要条件为:度数为奇数的点的个数为0或者2个…

历年中国科学技术大学计算机保研上机真题

2025中国科学技术大学计算机保研上机真题 2024中国科学技术大学计算机保研上机真题 2023中国科学技术大学计算机保研上机真题 在线测评链接:https://pgcode.cn/school?classification1 拆分数字 题目描述 给定一个数字,拆分成若干个数字之和&#xff…

2025陕西省赛补题

A 贪心 题意:给一个长度为n的序列,每次操作可以花费 w [ c [ i ] ] ( r − l 1 ) w[c[i]](r-l1) w[c[i]](r−l1)的代价,把区间 [ l , r ] [l,r] [l,r]染成染色 。 思路:对任意颜色,[l,r]中如果有cnt个连续的该颜色段…

Linux详谈进程地址空间

目录 第一谈:简单了解 第二谈:与操作系统的联系 内核空间与用户空间 步骤1:用户态代码执行 步骤2:跳转到内核代码 步骤3:内核代码访问用户数据 步骤4:返回到用户态 对于操作系统的本质:…

RabbitMQ vs MQTT:深入比较与最新发展

RabbitMQ vs MQTT:深入比较与最新发展 引言 在消息队列和物联网(IoT)通信领域,RabbitMQ 和 MQTT 是两种备受瞩目的技术,各自针对不同的需求和场景提供了强大的解决方案。随着 2025 年的到来,这两项技术都…

【Dify学习笔记】:Dify离线安装插件教程

Dify离线安装插件教程 1.本地下载插件 插件点击详情页面,安装右边的下载按钮,下载到本地 2.dify插件打包工具 dify-plugin-repackaging 下载后,进入到工具所在目录dify-plugin-repackaging/ git clone https://github.com/junjiem/dif…

2025年全国青少年信息素养大赛 scratch图形化编程挑战赛 小高组初赛 内部模拟试卷解析

2025年信息素养大赛初赛scratch模拟题 博主推荐 所有考级比赛学习相关资料合集【推荐收藏】 scratch资料 Scratch3.0系列视频课程资料零基础学习scratch3.0【入门教学 免费】零基础学习scratch3.0【视频教程 114节 免费】 历届蓝桥杯scratch国赛真题解析历届蓝桥杯scratch…

eBest智能价格引擎系统 助力屈臣氏饮料落地「价格大脑」+「智慧通路」数字基建​

从价格策略到终端执行,数字化正在重构饮料行业竞争壁垒! 近日,eBest为屈臣氏饮料提供的智能价格引擎系统已正式上线并投入运营。同时,基于eBest SFA方案且与屈臣氏饮料业务场景深度耦合的Smart Field Operation智慧通路项目正式启…

开发效率提升小技巧:快速提取图标资源的解决方案

在日常使用电脑的过程中,我们经常会遇到想要提取某个软件图标的情况,比如用于美化桌面、制作快捷方式,或者个人收藏等。这一款高效又实用的图标提取工具,帮助你轻松获取软件中的图标资源! ResHacker 是一个绿色免安装…

keepalived定制日志bug

keepalived定制日志bug 源码安装apt安装endl 源码安装 在/etc/rsyslog.d/目录下创建 keepalived的日志配置文件keepalived.conf [rootubuntu24-13:~]# vim /etc/rsyslog.d/keepalived.conf [rootubuntu24-13:~]# cat /etc/rsyslog.d/keepalived.conf local6.* /var/log/keepa…

SpringCloud——Docker

1.命令解读 docker run -d 解释:创建并运行一个容器,-d则是让容器以后台进程运行 --name mysql 解释: 给容器起个名字叫mysql -p 3306:3306 解释:-p 宿主机端口:容器内端口,设置端口映射 注意: 1、…

2.测试项目启动和研读需求文档

软件质量需求 定义: 用于确定测试目标,反映用户对软件的要求分类依据: 分为功能和非功能两大类,其中非功能包含性能、界面等8个子类 软件质量需求的分类 功能需求: 软件能做什么的核心能力非功能需求: 性能:运行效率和资源占用界面&#xf…