JVM的类加载机制和垃圾回收机制

news2024/11/23 22:28:41

目录

  • 类加载机制
    • 类加载机制的步骤
      • 加载
      • 验证
      • 准备
      • 解析
      • 初始化
    • 双亲委派模型
      • 工作原理
      • 双亲委派模型的优点
  • 垃圾回收机制
    • 死亡对象的判断
      • 可达性分析算法
      • 可达性分析算法的缺点
      • 引用计数算法
      • 循环引用问题
    • 垃圾回收算法
      • 标记-清除算法
      • 复制算法
      • 标记-整理算法
      • 分代算法

类加载机制

对于任意一个类,都需要经历这样一个过程:
类的生命周期
这个过程就是类的生命周期,从加载到初始化,属于类加载过程,其中验证、准备和解析都属于连接操作。

类加载机制的步骤

加载

加载是整个类加载中的一个阶段,该阶段通过类加载器将类的字节码文件加载到内存中,并创建对应的Class对象。说白了就是找到.class文件。

验证

验证加载阶段得到的.class文件是否合法(.class文件的格式是固定的)

准备

为.class文件中的类变量(static修饰)分配内存空间并初始化(不会赋值)。
对于public static int num = 10;
准备阶段num初始化为0而不是10

解析

将字符串常量池中的符号引用变为直接引用,即初始化常量。

符号引用就相当于字符串在文件内部的位置,不能直接通过符号引用访问到字符串;直接引用就是绝对位置,即可以通过这个引用直接访问到字符串。

初始化

执行类的初始化(包括静态变量的赋值和静态代码块的执行,如果有父类先加载父类)
准备阶段和解析阶段是可以顺序互换的,但是都要在初始化阶段之前。

双亲委派模型

双亲委派模型是一种类加载机制,在加载阶段执行。
JVM内置了三种类加载器,分别用于加载不同的类:

  • BootStrapClassLoader(引导类加载器):JVM内置的类加载器,负责加载核心类库
  • ExtensionClassLoader(扩展类加载器):加载Java扩展类库
  • ApplicationClassLoader(应用程序类加载器):加载应用程序的类
    其中ExtClassLoader是ApplicationClassLoader的父类,BootStrapClassLoader是ExtensionClassLoader的父类。
    程序员也可以自定义类加载器(需要继承自ApplicationClassLoader)

工作原理

当一个类收到类加载请求,他不会直接去加载,而是把这个请求委托给父类加载器,如果父类加载器还有父类,就继续向上委托,直到顶层的启动类加载器。顶层的启动类加载器没有父类,就去查找对应范围内是否存在该类,存在就加载并返回,否则交给子类加载器。如果最底层的类加载器也没有找到,则抛出ClassNotFoundException(受查异常)
类加载过程

双亲委派模型的优点

  • 避免了重复加载类的问题,保证类的唯一性

    假如有A和B两个类需要加载,它们的父类是C,在加载A类之前就会先加载C类,此后再去加载B类就不用加载C了;如果直接从顶层类加载器开始加载,就可能出现同一个类被不同的类加载器重复加载的情况,从而得到不同的对象。

  • 保护核心库的安全性,防止恶意篡改代码或替换核心库

    没有双亲委派模型(先访问顶层类加载器),就意味着可以自己实现一些核心类,这显然是不允许的,并且也不能保证安全。

  • 促进了Java程序的模块化和可扩展,可以自定义类加载器处理需求

双亲委派模型并不是强制的,有些情况下需要打破,例如热部署等

垃圾回收机制

在传统的编程语言(如C/C++)中,需要手动分配和释放内存。这往往会出现内存泄漏和内存溢出的问题,为了尽量避免这样的问题,JVM引入了垃圾回收机制,自动的释放无用内存,从而减少开发人员的负担。

死亡对象的判断

可达性分析算法

JVM确认是否为垃圾的算法是可达性分析,它的实现思路是:
遍历所有的GC Roots,找到它们的直接引用,并把这些引用标记为可达;然后遍历这些可达对象,继续找到它们的直接引用,并标记为可达;重复以上步骤直到遍历到没有引用。最后,没有标记为可达的对象就是垃圾。(整个走过的路径就称为引用链)
垃圾确认
在Java语言中,可作为GC Roots的对象包含下面几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象(虚拟机栈用于存储方法调用和局部变量);
  • 方法区类静态属性引用的对象(方法区用于存储类信息、常量、静态变量等);
  • 方法区常量引用的对象;
  • 本地方法栈中 JNI(Native方法)引用的对象(本地方法栈用于支持Java程序与本地代码(如C、C++等)交互的数据结构)。

可达性分析算法的缺点

  • 判断是否为垃圾的过程需要遍历所有的引用链,这会耗费大量资源和时间;
  • 在进行可达性分析时,必须暂停用户所有线程,即STW(Stop The World),这会影响程序性能以及用户体验等。

    为什么要触发STW
    假如在检查某条引用链时,某个其他线程删除了这条引用链上的某个对象,那么从删除位置开始往后的对象都是不可达的,这时可达性分析就可能出现误判。因此必须在进行可达性分析前暂停所有的用户线程。

引用计数算法

不仅仅是Java实现了垃圾回收机制,也有很多其他的语言也实现了垃圾回收机制,如python、php等则是使用引用计数算法来判断是否为垃圾的。
引用计数算法的实现思路为:
给对象增加一个计数器,每当有一个对象引用它时,就让计数器加一,每一个引用失效时,就让计数器减一,当计数器为0时,认为对象不再使用。
引用计数算法的思路简单,效率也比可达性分析算法要高,但是Java却不使用,原因就在于引用计数无法解决循环引用的问题

循环引用问题

例如:

public class Test {
    public Test n;
    public static void main(String[] args) {
        Test test1 = new Test();// test1引用次数为1
        Test test2 = new Test();// test2引用次数为1
        test1.n = test2;// test2引用次数为2
        test2.n = test1;// test1引用次数为2
        test1 = null;// test1引用次数为1
        test2 = null;// test2引用次数为2
    }
}

当把test1和test2都置为null后,显然是没有任何对象会使用到这两个引用的,也就是说这两个对象已经是垃圾了,但由于这两个对象互相引用,计数器不为0,就不进行回收。这就是引用计数法的弊端,无法解决循环引用问题。

垃圾回收算法

确定哪些对象是垃圾后,就要执行垃圾回收算法来清除这些垃圾。主要的垃圾回收算法有:

标记-清除算法

顾名思义,就是先标记再清除。通过前文所述操作对所有用不到的对象标记后,统一将其清除。
标记-清除算法虽然实现思路简单,但是却有着致命缺点
标记清除后会产生大量不连续的内存碎片,在后续申请内存时,如果剩余内存空间还有1G,但是最大连续的剩余内存空间还不到500MB,那这时去申请500MB的内存空间都申请不下来。
标记-清除算法

从上图可以看到,虽然剩余空间还有5格,但是要申请一个大小为三格的对象都申请不下来。

复制算法

为了解决标记-清除算法造成的内存碎片化的问题,引入了复制算法。
复制算法的实现思路为:
首先把内存块分成均匀的两块,每次只使用其中的一块内存,在进行垃圾回收时,把存活的对象复制到另一块内存中,然后再清除这一块内存。这样就解决了内存碎片化的问题。
复制算法
复制算法的实现也比较简单,但是缺点也很明显:
内存空间利用率很低,每次只能用一半丢一半。另外,假如内存中存活的对象很多,而死亡的很少,这时复制算法效率也是很低的。

标记-整理算法

标记-整理算法也是对标记-清除算法的一种改进,在标记完不会立即进行清除,而是把所有的存活对象往一端移动,把所有的存活对象和死亡对象隔开以后,在对死亡对象进行清除。
标记-整理算法
标记-整理算法和复制算法一样,当存活对象很多时,效率会很低。

分代算法

实际开发中的内存情况是多种多样的,因此不能简单的使用上述的某一种算法来实现垃圾回收。分代算法通过区域的划分,在不同的区域执行不同的垃圾回收算法,从而更好地实现垃圾回收。合适的才是最好的。
分代算法将内存区划分为新生代和老年代两大内存区块,根据经验而谈:如果一种事物已经存在了了很久,那么它很可能会继续存在很久。就像音乐一样,如果某一首歌已经火了好多年了,那么它很可能继续火很多年;当然更多的歌都只是昙花一现。依据这条经验,内存区划分为新生代和老年代,在新生代中,每次垃圾回收都有大批对象死去,只有少量存活(就像经典歌曲往往是少数的),因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用标记-清除或者标记-整理算法。

  • 新生代分为伊甸区和幸存区,新创建的对象都放在伊甸区,每轮存活的对象放到幸存区
  • 由于大多数的对象生命周期都很短,因此伊甸区很大,幸存区较小
  • 幸存区分为大小相等的两部分,每次只使用一部分
  • 每次垃圾回收,伊甸区和幸存区的幸村对象一样,都复制到另一半幸存区。
  • 在幸存区幸存的次数达到一定程度后,就会从新生代转移到老年代。
  • 如果创建的对象本身很大,则直接放到老年代。
    分代算法
    垃圾回收算法只是垃圾回收的思想,实际的垃圾回收是JVM基于这些思想并加以扩展实现的垃圾回收器来完成的。

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

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

相关文章

使用 vector + 递归 解决问题

class Solution {const char* To[10] { "","", "abc", "def", "ghi","jkl", "mno", "pqrs","tuv", "wxyz" };//常量字符串初始化string 注意此非定义(缺省值)--实例化…

收好这5个SVG编辑器,轻松实现高效创作

随着设计工作的不断发展,对SVG图形的需求也逐渐增加,SVG编辑器就随之诞生。可市面上的SVG编辑质量参差不齐,设计师无法第一时间找到适合自己的,于是本文就收集整理了5款相对来说比较好用的SVG编辑器为设计师们推荐,一起…

java项目之个人网站(ssm+mysql+jsp)

风定落花生,歌声逐流水,大家好我是风歌,混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的个人网站。技术交流和部署相关看文章末尾! 开发环境: 后端: 开发语言:Java 框架&#xf…

golang macaron静态资源访问配置

1、本地文件目录如下: 2、macaron配置 package mainimport ("log""net/http""gopkg.in/macaron.v1" )func myHandler(ctx *macaron.Context) string {return "the request path is: " ctx.Req.RequestURI }func main() …

基于linux下的高并发服务器开发(第一章)- Makefile(2)1.11

03 / 工作原理 ◼ 命令在执行之前,需要先检查规则中的依赖是否存在  如果存在,执行命令  如果不存在,向下检查其它的规则,检查有没有一个规则是用来生成这个依赖的,如 果找到了,则执行该规则中…

PowerShell 报错:因为在此系统上禁止运行脚本。有关详细信息

PowerShell 报错:因为在此系统上禁止运行脚本。有关详细信息 Import-Module : 无法加载文件 D:\\Documents\WindowsPowerShell\Modules\posh-git\1.1.0\posh-git.psm1,因为在此系统上禁止运行脚本。有关详细信息,请参阅 https:/go.microsoft.…

k8s 查看程序日志输出

总是和k8s大交道,把相关的命令也学习一下 查看程序日志输出: kubectl logs -f ce202307130100024-gy-decision-xkygl-64f795ff7b-mtr67 --tail1000 不重定向的情况下,可以把多进程下的日志都输出出来。挺好用的 kubectl logs -f pod --tailxx…

稳定币成关键指标,DeFi蓝筹拥抱RWA!下轮牛市会由什么叙事掀起?

从上海升级、BRC20、Meme 乃至老调重弹的减半叙事,每一次热炒都会激起新的市场关注。上半年,稳定币市场经历了一波格局调整,不少 DeFi 项目也出现了一些引人注目的叙事。下轮牛市,会是新一轮“DeFi盛夏”吗? 作为一个半…

如何编写测试用例

用例的五个构成元素: 下面从这五个元素的角度,去剖析如何编写测试用例 用例标题 用例标题就是测试点名称。用例标题是用来说明这个用例的测试目的的,好的用例标题是别人看完你这个用例标题后就知道你这个用例是测什么的。但并不是标题越详…

(自动机)+(卡常)+(前缀和)

edu151 C. Strong Password 样例&#xff1a; s123412341234 m2 s11234 s24321nt[i][j]:有了j以后找下一个数的位置的坐标。void solve() {cin>>s;ll lens.size();vector<array<ll,10>>nt(len1);nt[len].fill(len1);for(int ilen-1;i>0;i--){nt[i]nt[i1]…

js每隔两条或者几条数据,则就插入(新增)一条数据

js每隔两条(自定义条数)数据&#xff0c;则添加一个空的 item&#xff08;这个也可以新增你指定的数据&#xff09;,但是最后一条则不需要新增条数 let temp [] // 你需要处理的原数据 // 新的数组 const newData []for (let i 0; i < temp.length; i) {// 将原始数据添…

Vue3 使用存储库Pinia(state)

一、创建state import { defineStore } from "pinia"; export const useStore defineStore(main, {state() {return {name: 张三,age:26 }} })二、引用store.js import {useStore} from "../store/store" // 引入store const store useStore()三、html…

Flutter底部导航BottomNavigationBar

Flutter底部导航BottomNavigationBar 主要代码&#xff1a; bottomNavigationBar: BottomNavigationBar(//选中菜单颜色fixedColor: Colors.red,//图标大小&#xff0c;默认24.0iconSize: 30,//第几个菜单选中currentIndex: currentIndex,//当item数量大于3个时需要设置type属…

线性回归模型(Linear regression)

- 回归问题的标记如下 【假设可以写成h&#xff0c;也可以写成函数f】 - 工作方式 这就是一个监督学习算法的工作方式&#xff0c;我们可以看到这里有我们的训练集&#xff08;房屋平米数x&#xff0c;房屋价格y&#xff09; &#xff0c;我们把它喂给我们的学习算法&#xf…

css3提供的网页布局

css3提供的网页布局 弹性盒子模型&#xff08;flex box&#xff09;&#xff1a; 设置成弹性盒子 默认横着排放&#xff08;div也是&#xff09; 当子盒子给的宽度过大&#xff0c;总的子盒子宽度超过父级盒子&#xff0c;会自动适配&#xff0c;计算整个盒子父级的大小&#…

MMTracking目标跟踪环境搭建(一)

1、环境搭建 创建conda虚拟环境并激活。 conda create -n mmtrack python3.8 -y conda activate mmtrack 按照官方说明安装 PyTorch 和 torchvision 可以通过指定版本号切换到其他版本。 #如果网不好&#xff0c;可以这样安装 pip3 install torch1.8.2cu102 torchvision0.9…

Vue3 使用存储库Pinia(创建)

前言 使用vue编写项目时&#xff0c;经常用到组件传值&#xff0c;比如父传子、子传父、跨组件传值等&#xff0c;如果项目体量不大的情况这种方式完全够用&#xff0c;但如果项目大&#xff0c;内容丰富&#xff0c;那我们就需要用到状态管理工具——Pinia 官方文档 一、什么…

python优化库scipy.optimize测试(notebook版本)

代码链接notebook 数据准备 使用一个混合高斯分布作为测试&#xff08;两个均值不同的高斯分布相加&#xff09;&#xff0c;存在两个极值点(-1,-1)和(1,1) # 二维正态分布生成函数 def gaussian2d(x, y, x_mean, y_mean, x_std, y_std):return np.exp(-((x - x_mean) ** 2 …

Linux上安装jdk8环境

Linux上安装jdk8环境 一、Jdk8环境准备二、开始安装1. 创建压缩包位置2. 上传压缩包3. 解压压缩包4. 配置环境变量5. 查看安装版本 一、Jdk8环境准备 java8安装包下载可参考这篇文章&#xff1a;https://blog.csdn.net/qq_39939541/article/details/128065776 二、开始安装 …

1、Redis入门与应用

Redis入门与应用 Redis的技术全景 Redis一个开源的基于键值对&#xff08;Key-Value&#xff09;NoSQL数据库。使用ANSI C语言编写、支持网络、基于内存但支持持久化。性能优秀&#xff0c;并提供多种语言的API。 我们要首先理解一点&#xff0c;我们把Redis称为KV数据库&am…