当前位置: 首页 > article >正文

[JVM]JVM内存划分, 类加载过程, 双亲委派模型,垃圾回收机制

文章目录

  • 一. JVM内存划分
    • 1. 堆
    • 2. 栈
    • 3. 元数据区
    • 4. 程序计数器
  • 二. 类加载过程
    • 1. 加载
    • 2. 验证
    • 3. 准备
    • 4. 解析
    • 5. 初始化
  • 三. 双亲委派模型
  • 四. JVM的垃圾回收机制GC
    • 1. 找到需要回收的对象
    • 2. 释放垃圾的策略

一. JVM内存划分

JVM就是java进程
这个进程一旦跑起来, 就会从操作系统这里, 申请一大块内存空间
JVM接下来就要进一步的对这个大的空间, 进行划分, 划分成不同区域, 从而每个与都有不同的功能作用
在这里插入图片描述

1. 堆

整个内存区域中, 最大的区域
放的是代码中new出来的对象和成员变量

2. 栈

分为JVM虚拟机栈和本地方法栈

栈, 保存了方法中的调用关系 和 局部变量
JVM虚拟机栈, 是java使用的
本地方法栈, 是给本地方法使用的

3. 元数据区

放的是类对象 Test.class 和 类中的方法 和 常量池
代码中的每个类, 在jvm上运行的时候, 都会有对应的类对象
类有一些方法, 每个方法, 都代表了一系列的"指令集合"(JVM字节码指令)

4. 程序计数器

是内存区域中最小的区域, 只需要保存当前要执行的下一条指令的地址(这个地址就是元数据区里面的一个地址)

在这里插入图片描述
上述代码:
Test类的类对象, 在元数据区
a, 是成员变量, 在堆上
t2, 是成员变量, 在堆上, 存放的是Test2实例的地址
new Test2(), 在堆上
s, 是成员变量, 在堆上, 存放指向"hello"的地址
“hello”, 是常量, 存储在常量池中, 在元数据区
b, 是静态成员变量, 成为了类属性, 在元数据区
t, 是局部变量, 在栈上, 存放的是Test实例的地址
new Test(), 在堆上
在这里插入图片描述

上四个区域, 堆 和 元数据区, 是整个进程只有一份, 每个线程共享
栈 和 程序计数器, 是每个线程都有一份, 各个线程不可共享的

二. 类加载过程

我们写的代码, 是.java文件, 存储在硬盘上
javac编译器将.java文件, 编译成.class文件
类加载的过程, 就是将.class文件加载到JVM中, 得到类对象

1. 加载

在硬盘上, 找到对应的.class文件, 读取文件内容

2. 验证

检查.class里面的内容, 是否符合要求
在这里插入图片描述
这是java虚拟机规范中的.class文件的格式
把读取的内容, 往这个格式里套, 如果能套用, 就没有问题

3. 准备

给类对象, 分配内存空间(元数据区)
把这个空间中的数据都填充成0

4. 解析

针对字符串常量初始化
把刚才的.class文件中的常量内容, 取出来, 放到"元数据区"

5. 初始化

针对类对象 静态成员进行初始化, 执行静态代码块
(不是针对对象初始化, 和构造方法无关)

此时, 类对象就搞定了

三. 双亲委派模型

双亲委派模型, 是类加载中的第一步, 找.class文件的过程, 根据"全限定类名"找到对应的.class文件
“全限定类名”, 就是报名 + 类名 :String => java.lang.String
“类加载器”, 可以当做JVM中包含的一个特定的模块, 这个模块就是负责类加载的工作
JVM内置了三个类加载器:

  1. BootstrapClassLoader
    负责加载标准库中的类(爷爷)
  2. ExtentionClassLoader
    负责加载JVM扩展库的类(父亲)
  3. ApplicationClassLoader
    负责加载第三方库的类, 和你自己写的代码的类(儿子)

这个的父子关系,是通过类加载器中存在一个parent这样的字段, 指向自己父亲

工作过程:
例如找我自己写的类: java.Test
在这里插入图片描述
简单一句话概括, 就是拿到任务, 先交给父亲处理, 父亲处理不了, 再自己处理

上述过程, 主要为了应对这个场景:
比如自己代码里写了一个类, 类的名字和标准库/扩展库冲突了, JVM会确保加载的类是标准库的类, 就不加载你自己写的类了

四. JVM的垃圾回收机制GC

1)程序计数器, 不需要额外回收, 线程销毁, 自然回收
2)栈, 不需要额外回收, 线程销毁, 自然回收
3)元数据区, 存的是类对象, 一般不需要回收
所以, GC回收的是内存, 更准确说, 是对象, 回收的是"堆"上的内存

GC的流程:

  1. 找到谁是垃圾
  2. 释放对应的内存

1. 找到需要回收的对象

一个对象, 什么时候创建, 时机往往是明确的, 但是什么时候不再使用, 实际往往是模糊的
在编程中, 要确保, 代码中使用的对象, 都是有效的, 千万不要出现"提前释放"的情况

所以, 我们判断是否回收的保守办法, 就是判定某个对象, 是否存在引用指向他
在这里插入图片描述
在这里插入图片描述
具体怎么判定某个对象, 是否有引用指向?
下面介绍两种方式
** 1. 引用计数**
注意: 这个方法不是JVM采用的方案, 而是Python/PHP的方案
在这里插入图片描述
这种方法存在两个缺陷:
1.消耗额外的存储空间
如果对象比较大, 浪费的空间还好, 如果对象比较小, 空间占用就多了
2.存在**“循环引用”**问题
在这里插入图片描述
在内存中是这样的:
在这里插入图片描述

现在有下面代码:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

此时, 俩对象相互指对方, 导致两个对象的引用计数, 都不为1, (不为0, 就不是垃圾), 但是外部的代码, 也无法访问到这俩对象, 出现"循环引用"

2. 可达性分析
注意: 可达性分析是JVM采取的方案
JVM把对象之间的引用关系, 理解成了一个**“树形结构”**
JVM就会不停地周期性遍历这样的结构, 把所有能够遍历访问到的对象标记成"可达", 剩下的就是"不可达"
假设对象之间存在这样的树形引用关系:
在这里插入图片描述
此时, 从a出发, 任何一个结点都是可达的, 没有垃圾
如果此时c中的引用f = null, 相当于c.right = null, 那么f 就不可达了, 此时后续进行遍历时, 就会把f标记成垃圾了
如果此时a中c的引用c = null, 相当于a.right = null, 那么c和f都不可达了, 此时后续遍历, 就会把c和f标记成垃圾

由于可达性分析, 需要消耗一定的时间, 因此java垃圾回收, 没法做到"实时性", JVM提供了一组专门负责GC的线程, 不停地进行扫描工作

2. 释放垃圾的策略

1. 标记清除
直接把标记为垃圾的对象对应的内存, 释放掉(简单粗暴)
在这里插入图片描述将灰色的回收掉
这样的做法, 就会存在"内存碎片", 空闲内存被分成一个个小的碎片了, 后续很难申请到大的内存
申请内存, 都是要申请"连续"的内存空间的

2. 复制算法
在这里插入图片描述
比如, 要释放246, 保留135, 不会直接释放246, 而是先把135拷贝到另一块连续的内存上去

在这里插入图片描述
虽然内存碎片问题解决了, 但是空间浪费太多了
3. 标记整理
在这里插入图片描述
采用类似"顺序表删除中间元素"的方法, 向前搬运
先回收2, 把3向前搬运, 再释放4, 5向前搬运…
但是, 这样的搬运, 时间开销很大

其实, JVM实际的方案, 是综合上述的方案, 分代回收
根据对象的’‘年龄’'选择不同的方案
某个对象, 经历了一轮GC之后, 还是存在, 那么GC + 1

实际上堆区是分成Young区和Old区
在这里插入图片描述
Eden: 伊甸区
s0,s1: 生存区/幸存区

把创建的对象, 放在伊甸区中, 伊甸区大部分的对象, 生命周期都是比较短的, 第一轮GC到达的时候, 就会成为垃圾
把第一轮剩下的对象, 通过复制算法, 复制到生存区
每经过一轮GC, 生存区中都会淘汰掉一批对象, 剩下的通过复制算法, 进入到另一个生存区, 同时, 伊甸区中生存下来的也复制到这个生存区中
某些对象, 经历了很多轮GC, 都没有称为垃圾, 就会复制到Old区
Old区的对象, 也是需要GC的, 但是老年代的对象生命周期比较长, 就可以降低GC的扫描频率
对象在Old区, 就通过标记整理方法来进行回收


http://www.kler.cn/news/306646.html

相关文章:

  • 学习笔记JVM篇(一)
  • C语言中的信号量应用
  • 【ArcGIS Pro实操第七期】栅格数据合并、裁剪及统计:以全球不透水面积为例
  • Linux03
  • 使用 Nmap 进行 SSL/TLS 加密套件枚举
  • 什么是上拉,下拉?
  • STM32G070 CubeMX配置多通道/单通道ADC+DMA流程 LL库
  • Unity 粒子系统参数说明
  • cross-plateform 跨平台应用程序-09-phonegap/Apache Cordova 介绍
  • 0911(绘制事件,qt中的网络通信)
  • Introduction to LLMs in Python
  • 细说STM32单片机使用通用定时器生成固定占空比和可变占空比PWM波的方法
  • leetcode 230.二叉搜索树中第k小的元素
  • VMware Fusion虚拟机Mac版 安装Ubuntu操作系统教程
  • YOLOv8目标检测——迁移学习
  • 55页可编辑PPT | 集团制造企业数字化转型顶层设计方案
  • k8s中的认证授权
  • LeetCode双周赛139
  • 鸿蒙开发入门day19-使用NDK接口构建UI(一)
  • 中间件之RocketMQ
  • react js 使用 useEffect 钩子
  • C++函数在库中的地址
  • 【机器学习随笔】概率论与实际问题的对应
  • PHP技术深度探索:构建高效安全的Web应用实践
  • ChatGPT提示词-中文版(awesome-chatgpt-prompts中文版)
  • java重点学习-JVM组成
  • 多张GPU卡
  • 【C++】c++ 11
  • 【Git】初识Git
  • 检查Tomcat是否启动成功