浅谈 JVM

news/2025/1/20 14:10:53/

JVM 内存划分

JVM 内存划分为 四个区域,分别为 程序计数器、元数据区、栈、堆

程序计数器是记录当前指令执行到哪个地址

元数据区存储存储的是当前类加载好的数据,包括常量池和类对象的信息,.java 编译之后产生 .class 文件,运行代码的时候会把了类对象的信息保存在内存里,其中类元信息包括:类名、类的父类,实现哪些接口,类的权限public/private…
方法元信息:方法名,参数类型,返回值…
类元信息和方法元信息都是类对象里面包含的。

栈:分为本地方法栈和虚拟机栈,本质上都是存储方法的栈帧,每次调用方法的时候,会进行压栈,方法调用完会执行出栈操作,本地方法栈执行的是C++方法,虚拟机栈执行的是 Java方法。

堆:存放 new 出来的对象的实例。
Test t = new Test();
new Test() 这个对象的实例一定是存储在堆上的

t 需要分类讨论,如果 t 是一个局部变量,t 就是在栈上的;如果 t 是 成员变量,t 就是在堆上的;如果 t 是 静态成员变量,t 就是在元数据区里的。

在这里插入图片描述

元数据区 和 堆,整个Java进程共用一份
程序计数器和栈,每个线程有一份

在面试时,如果遇到谈一下栈和堆的话, 要注意是数据结构里的栈和堆, 还是JVM的栈和堆

类加载

1)加载:根据类的全限定名(包名+类名)找到对应的 .class 文件,然后打开文件,将文件的内容读取到内存中里。
2)验证:解析和校验 .class 文件的内容是否合法,如果合法就把这里的内容转化成结构化的数据
3)准备:给类对象申请 全0 的内存空间
4)解析:针对字符串常量进行初始化,将 .class 文件中的字符串常量放入元数据区的常量池中
5)初始化:将类对象进行最终的初始化,包括对类对象各种属性的填充,类中的静态成员,父类,如果父类没有被加载过,会触发父类的类加载。

类加载触发的时机为懒加载,当Java代码需要用到哪个类才会触发哪个类的加载

双亲委派模型

双亲委派模型的根据JVM实现类加载的源码提出的,也就是说是先有JVM的类加载源码,才有后面的双亲委派模型。

JVM 默认提供了三种类加载器,分别为 BootstrapClassLoader、ExtensionClassLoader、ApplicationClassLoader

你可以认为这三个类加载器的关系如下:
在这里插入图片描述
它们之间的联系,你可以认为是这三个类加载器都有一个引用指向上一个类加载器,ApplationClassLoader 的父亲的 ExtensionClassLoader,ExtensionClassLoader 的父亲是 BootstrapClassLoader,BootstrapClassLoader 的父亲为 null。

BootstrapClassLoader 负责加载Java 标准库的 .class 文件
ExtensionClassLoader 负责加载 Java 扩展库的 .class 文件
ApplicationClassLoader 负责加载 Java第三方库的.class 文件

随着时代的发展,Java的扩展库我们很少使用,取而代之的是Java第三方库,例如使用maven 加载的库就是第三方库

双亲委派模型的执行流程
进行类加载,通过全限定类名寻找 .class 文件,先从 ApplicationClassLoader 最为入口开始,然后将类加载任务委托上一级 ExtensionClassLoader ,再将类加载任务委托给上一级 BootstrapClassLoader,由于 BootstrapClsssLoader 没有上一级,开始执行类加载任务,将需要的Java标准库里的 .class 文件加载到内存里,然后将任务下放给下一级 ExtensionClassLoader ,ExtensionClassLoader 将所需要的Java扩展库的 .class 文件加载到内存里,最后将任务交给下一级 ApplicationClassLoader,ApplicationClassLoader 负责加载Java第三方库的 .class 文件。

如果没有找到对应的 .class 文件,就会抛出异常 ClassNotFoundException

在这里插入图片描述

垃圾回收 GC

在Java中你不需要手动释放内存,这是因为JVM 内部实现了 GC,也就是垃圾回收机制,这个机制可以自动回收内存空间,这使得Java程序员不需要考虑内存泄漏的问题。

垃圾回收有两个步骤:首先就是找到垃圾,然后再回收垃圾

如何找到垃圾???

找到垃圾这里介绍两个算法,一个是引用计数,另一个是可达性分析

引用计数

在创建的对象旁边开辟一个空间,用来存放该对象被多少个引用引用着,当被引用的数量为 0 的时候就会进行垃圾回收:

在这里插入图片描述

但这也有缺点:
1)内存消耗多,尤其是对象本身比较小的时候,引用计数占据的空间的比例就会很大,例如:假设对象自身占8个字节,引用计数可能占 4 个字节

2)可能出现 “ 循环引用” 的问题
假设一个类里面还定义了一个引用,用这个类创建的两个对象的引用相互指向对方,那这两个对象是不可能被垃圾回收掉的:
在这里插入图片描述
当我们将 a 和 b 两个引用置为 null 的时候,我们创建的两个对象的实例是不会被回收掉的,因为此时两个对象都被对方引用着,双方的引用计数均为 1,这就是 “循环引用”

可达性分析(JVM 使用)

为了解决上述引用计数产生的两个弊端(空间浪费和循环引用),这里提出了可达性分析算法。

可达性分析算法采用时间换空间的策略,这也是 JVM 寻找垃圾使用的算法

首先对代码的一些特定对象作为遍历的 “起点”:这些特定的对象包括 栈上的局部变量(引用变量)、常量池引用指向的对象、静态成员(引用类型的)【这些对象在程序运行到任何时刻都容易被捕获到的】
【因为不会扫描堆上的对象,所以不会存在因为 “循环引用” 而影响垃圾判断的现象】

然后进行遍历:判断某个对象是否能被访问到,在能访问到的对象标记为 “可达”。

当完成遍历后,把未标记为 “可达” 的对象标记为 “不可达”

JVM 通过上述操作,就知道哪些对象是 可达的,哪些是 不可达的,接下来就是回收垃圾的操作了。

举个例子:

class Node {Node left;Node right;
}public class Test2 {public Node build() {Node a = new Node();Node b = new Node();Node c = new Node();Node d = new Node();Node e = new Node();Node f = new Node();Node g = new Node();a.left = b;a.right = c;b.left = d;b.right = e;f.right = g;return a;}
}

在这里插入图片描述
由于方法最后只返回 a 这个节点,这时候 f 和 g 节点就会被回收掉。

如何处理找到的垃圾???

这里介绍四种处理方式:标记–清除 算法,复制算法,标记整理 算法,分代算法,其中最后一个算法 分代算法是 JVM 真正使用的。

标记–清除 算法

在需要回收的内存上做上标记,然后进行回收清除操作,这是最直接的算法,但是存在一个缺点就是会产生内存碎片。

举个例子:假设一共有 7 块内存块,经过可行性分析算法得出 2、4、6 这三块内存为需要回收的内存,先做上标记,然后进行回收释放,但是空出来的内存碎片可能难以得到利用。
在这里插入图片描述

标记整理算法

由于标记清除算法会产生大量的内存碎片标记整理算法直接将垃圾回收设计为类似顺序表删除的形式, 将非垃圾的对象排到一起, 这样就可以空出大块的空闲内存块了.

但是缺点也是显而易见的, 就是系统开销比较大, 尤其是移动较大的对象的时候.
在这里插入图片描述

复制算法

将内存空间一分为二,当需要进行垃圾回收的时候,将非垃圾的内存块依次紧凑地复制到另一半内存中。

但是也存在两个缺点
1)内存消耗大,内存的空间利用率低
2)一旦非垃圾的内存块较多的时候,复制的成本就会较高,尤其是复制包含较大的对象的时候。

举个例子:要回收 1 和 4 号内存块。
在这里插入图片描述

分代算法 (JVM 使用)

分代算法是结合了上面两种算法的优点,取长补短的综合算法

JVM 将对象划分为 新生代和老年代,新生代又划分了三个区域分别是伊甸区、两个幸存区【空间占比为8:1:1】

分代算法的代指的是对象的年龄大小, 年龄的大小和 GC 轮次相关

在这里插入图片描述

命名的来源于圣经,人类发源地伊甸园,在这里指新生成的对象的存放位置,由于灭世洪水的降临,人类打造了诺亚方舟,成功躲避天灾的人门就是幸存者,这里的幸存区是指对象经过GC之后还能存留下来的存储位置

我们认为对象一开始生成的时候,是最容易被 GC 掉的,所以新生成的对象保存在 伊甸区里,经过 一轮GC 之后剩下的对象不会很多,将这些幸存的对象放到幸存区里,两个幸存区就是为了执行复制算法,经过多轮 GC 之后,还能在幸存区里存活的对象将放入到老年代。

一般来说,如果内存占用空间比较大的对象我们一般是直接放入老年代, 减少系统开销

新生代的 GC 频次较高
老年代的 GC 频次较低

因为我们认为要 GC 掉的早就 GC 掉了,之所以能存活在老年代,说明这个对象经常被使用,也就是说老年代的对象不需要高频次的 GC 次数,GC 的周期可以长一点,减少系统开销。


http://www.ppmy.cn/news/1564671.html

相关文章

【springboot 集成 mybatis-plus】

springboot 集成 mybatis-plus 前言实战代码生成器自动填充字段 前言 正如MyBatis-Plus官网所说,MyBatis-Plus 是一个 MyBatis 的增强工具,提供了强大的CRUD操作,支持主键自动生成,代码生成器,自动填充字段等等&#…

【ComfyUI专栏】ComfyUI的环境配置

对于常规的用户来说,我们碰到需要非常注意的问题,就是我们的ComfyUI的各个节点可能会有不兼容的情况,因此我们最好建立独立的Python虚拟环境。如何建立虚拟的环境呢?其实非常简单。 执行的命令如下: Python -m venv venv #创建Venv名称的虚拟环境 cd venv #进入到Venv …

InVideo AI技术浅析(四):机器学习

一、视频剪辑与合成 1. 工作原理 视频剪辑与合成是视频编辑中的核心任务,旨在将多个视频片段、音频和字幕等元素组合成一个连贯且富有吸引力的视频。InVideo AI 使用机器学习技术自动化这一过程,通过分析视频内容、识别重要片段并进行智能剪辑和合成。其核心目标是提升观众…

【Linux系统编程】—— 深度解析进程等待与终止:系统高效运行的关键

文章目录 进程创建再次认识fork()函数fork()函数返回值 写时拷贝fork常规⽤法以及调用失败的原因 进程终⽌进程终止对应的三种情况进程常⻅退出⽅法_exit函数exit函数return退出 进程等待进程等待的必要性进程等待的⽅法 进程创建 再次认识fork()函数 fork函数初识&#xff1…

人脸识别【python-基于OpenCV】

1. 导入并显示图片 #导入模块 import cv2 as cv#读取图片 imgcv.imread(img/wx(1).jpg) #路径名为全英文,出现中文 图片加载失败,"D:\picture\wx.jpg" #显示图片 (显示标题,显示图片对象) cv.imshow(read_picture,im…

【AI论文】迈向大型语言模型(LLM)训练开放数据集的最佳实践

摘要:许多人工智能公司未经版权所有者许可,就在其数据上训练大型语言模型(LLM)。这一行为的合法性因司法管辖区而异:在欧盟和日本等国家,这种行为在特定限制下是被允许的,而在美国,法…

vue3+vite+ts+router4+Pinia+Axios+sass 从0到1搭建

1、使用vite构建项目 npm create vitelatest 填写项目名的时候不能大写 2、跑起来之后配置下 import { defineConfig } from vite import vue from vitejs/plugin-vue import { resolve } from path // https://vite.dev/config/ export default defineConfig({plugins: [vue…

业务幂等性技术架构体系之服务幂等深入剖析

简单来说,服务幂等是指一个操作(或请求)无论被执行多少次,其对系统状态的影响都是一样的,就好像这个操作只执行了一次一样。换句话说,对于同一组输入参数,幂等的服务会给出相同的结果&#xff0…