尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

详细介绍:硅基计划6.0 柒 JavaEE 浅谈JVMGC垃圾回收

详细介绍:硅基计划6.0 柒 JavaEE 浅谈JVMGC垃圾回收
📅 发布时间:2026/6/18 15:34:05

详细介绍:硅基计划6.0 柒 JavaEE 浅谈JVM&GC垃圾回收

1750851495554


文章目录

  • 一、初始
  • 二、Java内存区域划分
    • 1. 运行时数据区划分
    • 2. 内存溢出
    • 3. 补充
  • 三、Java类加载机制
    • 1. 类加载流程
      • 1. 加载
      • 2. 验证
      • 3. 准备
      • 4. 针对字符串常量进行初始化
      • 5. 初始化
    • 2. 类加载时机
    • 3. 双亲委派模型
    • 4. GC垃圾回收——重点
      • 1. GC步骤第一步——找垃圾
        • 1. 方案壹——引入计数机制(非Java)
        • 2. 方案贰——引入周期性可达性分析
      • 2. GC步骤第二步——回收垃圾
        • 1. 方案壹——标记清除法
        • 2. 方案贰——复制算法
        • 3. 方案叁——标记整理法
        • 4. 综合方案——分代回收
        • 5. 其他方案——其他垃圾回收器


一、初始

为什么我们Java要引入Java虚拟机JVM呢,Java虚拟机又称为Java解释器/Java执行引擎
因为它充当着翻译官,可以很好的实现跨平台功能,并且更好地兼容操作系统和CPU

二、Java内存区域划分

在每一个Java的进程中,都包含了一个JVM
JVM在启动的时候,就会向操作下申请一块内存空间,因此我们Java程序就可以利用这块内存空间去执行代码逻辑了

1. 运行时数据区划分

  1. 程序计数器:它是一片很小的内存区域,用来存放下一个Java字节码指令的地址
  2. 虚拟机栈:主要服务于Java程序,明确方法之间的调用关系、明确方法内部的局部变量,明确方法结束后返回上一层方法的位置、明确方法返回值,比如
|fuc3|
|fuc2|
|fuc1|
|main|<----每个方法就是一个栈帧
------
  1. 本地方法栈:给C++代码使用的,因为JVM底层是C++实现的,并且在有些方法底层调用的就是C++代码
  2. 堆:存储new出来的对象
  3. 元数据区/方法区:存储一些类的对象或者是静态成员和方法

2. 内存溢出

  1. 栈溢出:栈帧(方法)太多导致的,比如死递归
  2. 堆溢出:new的对象太多了

3. 补充

一个进程中只存在一份堆和元数据区,这就说明一个线程中new的对象可以被另一个线程引用
但是每个线程都有自己的程序计数器、本地方法栈、虚拟机栈

三、Java类加载机制

本质上就是把class文件读取到内存中并且构建的过程

1. 类加载流程

1. 加载

把class文件根据“全限定类名”找到对应的class文件,并且把文件数据读取到内存中

2. 验证

根据读取到的二进制内容判断其是否是一个正确的格式,我们打开Java官方文档中的虚拟机规范
文档链接
image-20251114115138970

好,我们来逐个参数简单看下

  1. U4 maginc指的就是开头四个字节为魔幻数字,就是一种二进制文件的格式
  2. U2 minor_version&major_version指的就是主版本号和副版本号,描述class文件是经过哪个版本的Java编译器生成的
  3. U2 constant_poll_count&cp_info constant_pool指的就是常量池数量以及内部的数据格式cp_info
  4. U2 access_flags指的就是类访问权限
  5. U2 this_class指的就是当前类的编号
  6. U2 super_class指的就是父类的编号
  7. U2 interfaces_count&U2 interfaces[interfaces_count]指的就是描述实现类的接口以及其编号
  8. U2 fields_count&field_info fields[fields_count]指的就是描述类的属性、属性访问权限等等
  9. U2 methods_count&method_info methods[methods_count]指的就是方法的属性、方法的访问权限等等
  10. U2 attributes_count指的就是关于类的一些注解

3. 准备

给要去创建类的对象在JVM的元数据去分配一块内存空间,且默认把新申请的未初始化的设置为0

4. 针对字符串常量进行初始化

要把class文件中的字符串常量池加载到内存,此时我们的字符串就有了起始地址,后续在调用的时候就可以把字符串取出来

5. 初始化

初始化类静态成员、执行静态代码块、加载类等等

2. 类加载时机

本质上是一个懒汉模式,仅需加载一次并且在JVM中是单例存在的,那么什么时候会触发
new实例的时候、调用这个类的静态方法或者是静态成员、针对子类的父类加载

3. 双亲委派模型

在我们类加载流程第一步的时候,对于寻找class文件,涉及到三个模块的类加载器,之间存在父子关系
BootStrapClassLoader(爷):加载Java标准库的类
ExtensionClassLoader(父):加载扩展库类,一般是JDK厂商内置的,但现在少用了
ApplicaitonClassLoader(子):加载第三方库和项目中的类,也是整体的入口
它们之间的关系就是,对于一个类,先直接丢给爷爷去处理,处理不过就向下委派,如果最后还是没找到这个类,就会抛出类加载异常

4. GC垃圾回收——重点

在C语言中,内存泄露是一个非常严重的问题
如果我们进行malloc操作不及时进行释放,就会导致后续我们再进行内存分配的时候产生无内存可用的情况

而在我们Java中,通常会指派一些线程,周期性地对堆上的对象进行扫描,自动判定这个内存空间(对象)是不是不再使用
并且我们回收是针对一整个对象回收,并不存在回收半个的情况
如果不再使用就会自动释放,但是就是因为GC这种机制会有额外的内存和时间的开销,像C/C++这种追求效率的语言就不会采用

下面我们就来谈谈整体的回收流程

1. GC步骤第一步——找垃圾

GC是如何去判断这个对象不再进行使用的呢,答案就是看这个对象是否被强引用
那么如何去判断有没有被强引用呢,这里有几种方案,我们一一阐述

1. 方案壹——引入计数机制(非Java)

给每个对象安排一个空间,存放一个整数,围绕对象引用个数进行计数器更新
但是这会暴露出两个很大的问题

  1. 消耗更多哦内存空间
  2. 循环引用出现误判

我们针对循环引用的误判来做下解读

class Test{
Test t;
}
main{
Test a = new Test();
Test b = new Test();
a.t = b;
b.t = a;
a = null;
b = null;
}

如果我们画个图,就是这样子

image-20251114122334090

当我想使用 0x100对象,发现是 0x200 引用的,那我去找0x200
但是我想使用0x200对象,发现是0x100引用的,那我去找0x100
嗯?不是死循环了吗,我这两个对象明明都使用不了
并且由于其计数器不为0,就不能被释放掉,就会持续占用越来越多的内存资源

2. 方案贰——引入周期性可达性分析

在Java的代码中,一系列的对象都存在类似于树形结构的关系

class Test{A a = new A();B b = new B();}

也就是可能类似于这种结构

  Test/    \
A      B/ \C   D

我们所谓的周期性可达性分析,就是从一个或者多个根节点出发尽可能地遍历这棵树,凡事能经过的对象都标记为可达
结合JVM知道对象数量,减去可达到对象数量,剩下的就是不可达的对象了,并且这种扫描会周期性的进行

对于根节点GCRoots可能有一下几种

  1. 栈的局部变量,因为栈有很多个,每个栈的栈帧也有很多个,每个栈帧中局部变量也有很多个
  2. 常量池引用所指向的对象,可能也有很多个
  3. 所有引用类型的静态成员所指向的对象,也可能有很多个

2. GC步骤第二步——回收垃圾

1. 方案壹——标记清除法

如果采用直接释放,会导致内存碎片问题,当我们想申请一大片连续的内存空间时候
明明总的空闲内存加起来够,但是由于是碎片化的,并没有集中在一起,因此就申请失败

image-20251114123425936

2. 方案贰——复制算法

把内存区域分成两份,同一时刻只使用一份
如果是那种无效对象(垃圾)就会留在原地,其余有效对象就被拷贝到内存的另一部分区域,最后再把原来的那一部分区域整体释放
下一次就会从右边开始,看哪些是“垃圾”,把“垃圾”留下,有效对象就拷贝到内存的另一部分区域,最后再把原来的那一部分区域整体释放
我们画个图就是这样的

image-20251114124158615

我们这两部分内存就会被反复使用,但是这样会导致空间利用效率很低,并且对象复制开销也很大

3. 方案叁——标记整理法

这就类似于我们顺序表中的删除中间元素的方法,即搬运

image-20251114124503275

但是这么搞对象搬运的开销也可能会很大

4. 综合方案——分代回收

JVM会根据对象的情况/特点结合存活时间进行有针对性的回收
我们普遍认为,根据GC扫描轮数,如果某个对象年龄(存活时间)比较久,有很多概率会继续存活下去
因此我们把内存划分为了以下区域

image-20251114125244948

  1. 新new的对象首先会在伊甸区中,大多数对象经过第一轮GC都会被淘汰
  2. 在伊甸区未淘汰的对象通过复制算法进入幸存区,同一时刻只使用一个幸存区
  3. 在幸存区会进行第二轮GC扫描,进一步淘汰一大批对象
  4. 如果在第二轮未被淘汰,则会进入另一个幸存区,重复GC扫描
  5. 当在两个幸存区都没有被GC扫描所淘汰,达到一定阈值,就会进入老年代
  6. 在老年代同样会进行GC扫描,只不过频次慢了些
  7. 这里说个例外情况:如果对象内存非常大则可能会直接进行老年代
5. 其他方案——其他垃圾回收器
  1. CMS——在多线程中尽可能扫描回收
  2. G1(G ONE)——针对内存空间特别大的情况,会把内存划分出更多区域,一次GC只针对一部分区域
  3. 2GC——目前处在实验性阶段,它会使得垃圾回收对于业务逻辑的响应时间更短,即开销更小,据说能达到0.1ms!

本篇文章属于是纯纯的八股文了

面试爱考,实际开发作用不大

END QAQ

相关新闻

  • 2025年微孔板离心机国内知名厂家有哪些厂家生产厂家推荐品牌推荐,微孔板离心机96孔板生产厂家推荐 - 品牌推荐大师1
  • laya列表时计算具体体子项是列表中第几个
  • MySQL 性能和变量调整 - 详解

最新新闻

  • PowerPC 601缓存时序与总线仲裁机制深度解析
  • 一念成仙:看山不是山,看水不是水,为什么OPC创业的核心是商业模式,而非代码本身
  • 国内主流打包机厂家实测排行 适配电商物流多场景 - 起跑123
  • 终端(Terminal)通俗完整讲解
  • 车载雷达架构迭代|全网量产复盘 场景反向定义ODD边界、L2-L4全域硬件升级、分布式转集中架构迭代、多雷达时序融合、整车感知全套工程复现
  • Windows系统优化神器:3分钟让你的电脑焕然一新

日新闻

  • 2026年不锈钢卷板厂家推荐排行榜:冷轧热轧/304/201不锈钢卷板,高颜值耐腐蚀源头厂家实力精选 - 企业推荐官【官方】
  • FLUX.1-dev FP8模型实战指南:24GB以下显卡高效部署方案
  • 2026佛山长途搬家价目表:跨省跨市搬家费用完整计算指南 - 从来都是英雄出少年

周新闻

  • 3步解锁iOS设备:applera1n激活锁绕过完全指南
  • 39 2026 人工智能证书终极盘点,普通人选 AI 证书可以从这些方向入手
  • Redis 暴露公网有多危险?从端口检查到补救步骤

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号