JVM—内存管理(运行时数据区)、垃圾回收
背景介绍
当JVM类加载器加载完字节码文件之后,会交给执行引擎执行,在执行的过程中会有一块JVM内存区域来存放程序运行过程中的数据,也就是我们图中放的运行时数据区,那这一块运行时数据区究竟帮我们做了哪些工作?我们常说的线上内存泄漏和内存溢出是因为什么?我们今儿来揭开看看它神秘的面纱。

过程
在讲述运行时数据区有哪些部分之前先讨论以下对象的创建流程
一、对象创建流程

在上一篇文章中已经讲述了类加载的过程,虚拟机需要给new的新对象分配内存空间,重点来说说它的下一步——分配内存
在分配内存的时候有两种方式:
1、指针碰撞
假设Java堆内存规整,所有的内存占用是连续空间,通过一个指针将已经使用和未使用的空间隔开,指针作为临界。
2、空闲列表
假设Java堆内存不规整,内存占用是零散的,此时JVM通过一个空闲列表(Free List)维护空闲内存信息,里面记录了哪些内存空间是可用的。再次分配新对象的时候直接从空闲列表中找哪块空间可以使用进行分配即可
但是在并发情况下可能会出现线程安全的问题,对象1和对象2同时拿到指针,对象1在分配完内存空间之后对象2也会分配内存空间,对象2就会把对象1的空间覆盖,导致数据被覆盖。那怎么解决这个问题呢?
两种方案解决线程不安全:
1、CAS:自旋锁不断重试
2、TLAB:在堆内存给每个线程预先分配一块内存,线程中每次要开辟空间就在预分配的内存中开辟
下面就进入我们的正题——内存管理

- 线程共有:堆、方法区
- 线程私有:程序计数器、Java虚拟机栈、本地方法栈
二、内存管理
1、 程序计数器(PC)
上官方百度百科介绍:

作用:存放当前线程执行的下一条指令地址。
在多线程环境下线程之间会涉及到线程切换问题,为了保证线程切换之后还能继续按照上一次切换的位置继续执行,PC会进行指令的记录。PC是线程独有的,不可共享
2、Java虚拟机栈
作用:
每个线程在创建时都会创建一个虚拟机栈,保存了一个一个的栈帧 。针对栈帧我们来具体说说,下图为Java虚拟机中栈帧的内部结构,每执行一个方法都会创建一个栈帧(一个方法对应一个栈帧) ,而栈帧中包含了四部分:局部变量表、操作数栈、方法返回地址、动态链接,而每一个栈帧执行的过程也是入栈、出栈的过程。

包括:
- 操作数栈(Operand Stack):用来在执行字节码指令过程中用来计算的
- 局部变量表(LocalVariables):在方法执行过程中实时记录每个局部变量对应的值
- 方法返回地址(Return Address):地址
- 动态链接(Dynamic Linking):符号引用转换为调用方法的直接引用
特点:
- 线程私有
- 遵守栈FIFO规则,方法开始执行栈帧入栈,方法执行完栈帧弹出,所以虚拟机不需要垃圾回收
存在的问题:
- 如果线程太多了,但是没有足够空间创建虚拟机栈,会发生栈溢出
- 方法调用层次太多,可能出现StackOverflowError
3、本地方法栈(Native Method Stacks)
作用:存储Native方法
4、方法区(Method Area)
作用:
存储被Java虚拟机加载过后的class类信息、常量、静态变量、编译后的代码
不知道大家是否还记得上一篇分享中讲到的类加载过程,其中加载这一步会通过类全限定名加载成class类对象,其中就会把class信息、静态变量、常量等信息加载到方法区,看下面这张图

5、堆(Heap)

所有线程共享的一块内存区域,在new对象的时候会在Heap分配内存空间。可以细分为:
- 年轻代和老年代,对应的比例为2:1 ,新生代存放朝生夕死的对象,老年代存放生命周期较长的对象
- 年轻代又可以分为Eden、From Survivor、ToSurvivor三个区,对应的比例为:8:1:1(默认情况下,可以通过-XX:SurvivorRatio来调整)
那三个区域中对象是如何进行流转的呢?我们具体来看一下
1、在最开始讲述对象创建流程中包含了一步是分配内存,就是通过指针碰撞或空间散列在Heap中分配内存,新new对象的对象JVM会默认优先分配在Eden区,当Eden区空间逐渐减少(可以默认配置Eden空间容量)的时候,就会触发Young GC来清理,Eden区对象就会放入Survivor区;
2、Survivor区每次分配内存只使用其中一块,Eden和Survivor存活对象会复制到另一块Survivor区中,Eden和原来的Survivor区对象会被清理掉。(这也是为什么图中我只在其中一个Survivor区画了对象,另一块Survivor区没画的原因)
3、在对象头中记录了对象迭代的年龄(年龄计数器),当进入Survivor区开始每YoungGC一次年龄就会+1,当年龄达到15的时候就会进入老年代
从图上我们看到进入老年代的条件远不止年龄>=15这一个,对象会进入老年代的方式共有四个:
- 长期存活:对象头中记录了对象迭代的年龄,每次迭代都会—+1,当年龄达到15(默认)
- 超大对象:占用大量连续空间
- 动态年龄判断:servivor中相同年龄对象的总和>survivor空间一半
- 空间分配担保:Young GC后,新生代有大量对象对象存活,需要老年代分配担保
三、垃圾回收

垃圾回收是什么?
清理不再使用的对象,释放内存空间
为什么要进行垃圾回收?
如果不清理这些垃圾对象,那么它们会一直占用着内存,而不能给其他对象是用,最终垃圾对象越来越多,就会出现OOM
什么样的对象是垃圾?
JVM没有任何引用指向它的对象
如何判断对象是垃圾?
1、引用计数法
每个对象都保存一个引用计数器属性,用户记录对象被引用的次数,每被引用一次计数器值就+1;当引用失效时就-1。当计数器为0则表示是垃圾对象
2、可达性分析
从GC Roots开始,遍历,一层一层的往下级找引用对象,找到的对象就是存活对象,没找到的就是垃圾对象

垃圾回收的三种方式分别是哪些?
1、标记-清除算法(Mark-Sweep)
标记:标记出未引用对象
清除:回收所有被标记的未引用对象
问题:
- 如果堆内包含了大量的对象都是需要被回收,这时会执行大量标记和清除操作,导致执行效率降低
- 内存空间碎片化,标记和清除后会产生大量不连续的内存碎片,导致在之后在给对象分配内存空间的时候因为内存不足而再次触发Young GC
2、标记-复制算法(Mark-Copying)
基于标记-清除算法,解决碎片化问题。上文中我也提到了新生代中Survivor分为了From、To两个区域,每次只使用其中一块,当其中一块内存用完了,会将存活的对象复制到另一块区域上,然后清理掉使用的survivor区域,保证内存区域连续可用。
问题:
空间一分为2,利用率低,空间浪费
3、标记-整理(Mark-Compact)
基于标记-清除算法,解决内存碎片和空间浪费问题。将存活的对象向一端移动,清除标记的垃圾对象,保证区域连续可用
问题:
内存变动大,当对象位置移动相应的引用地址也会变动
如何选用使用什么回收算法呢?
分代回收:基于heap各个区域对象生命周期来看,每个区域采用不同的回收算法:
- 新生代:标记-复制
- 老年代:标记-清理/标记-整理
相关文章:
JVM—内存管理(运行时数据区)、垃圾回收
背景介绍 当JVM类加载器加载完字节码文件之后,会交给执行引擎执行,在执行的过程中会有一块JVM内存区域来存放程序运行过程中的数据,也就是我们图中放的运行时数据区,那这一块运行时数据区究竟帮我们做了哪些工作?我们…...
一百五十一、Kettle——Linux上安装的kettle8.2开启carte服务
一、目的 kettle8.2在Linux上安装好可以启动界面、并且可以连接MySQL、Hive、ClickHouse等数据库后,准备在Linux上启动kettle的carte服务 二、实施步骤 (一)carte服务文件路径 kettle的Linux运行的carte服务文件是carte.sh (二…...
19. python从入门到精通——Web编程
HTTP协议 HTTP协议的常用方法 方法 描述 GET 请求指定的页面信息,并返回实体主体。 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 …...
PostMan 教程
安装https://www.cnblogs.com/mafly/p/postman.html Postman 使用方法详解https://blog.csdn.net/fxbin123/article/details/80428216 postman进行http接口测试https://blog.csdn.net/five3/article/details/53021084 postman的使用方法详解!最全面的教程https:/…...
Http常见状态码
一、状态码大类 状态码分类说明1xx响应中——临时状态码,表示请求已经接受,告诉客户端应该继续请求或者如果它已经完成则忽略它2xx成功——表示请求已经被成功接收,处理已完成3xx重定向——重定向到其它地方:它让客户端再发起一个…...
C语言之位运算
一、什么是位运算 所谓位运算是指进行二进制位的运算 在系统软件中,常要处理二进位的问题 例如,将一个存储单元中的各二进位左移或右移一位,两个数按位相加等 二、位运算符和位运算 1、按位与 运算符(&) 参加运算的两个数据ÿ…...
c语言进阶部分详解(数据在内存中的存储)
大家好,今天要进行梳理的内容是数据在内存中的存储相关内容。 在C语言中,数据在内存中的存储是一个非常重要的概念。了解数据在内存中的存储方式可以帮助我们更好地理解程序的执行过程,优化内存使用,提高程序的性能。 目录 一.数…...
VIOOVI的ECRS工时分析软件分析:SOP的核心和特征是什么?
制定SOP的主要目的是为企业做技术储备、提供企业的工作效率、防止同样的错误反复出现、让员工作业有标准化的行为准则。以规定的成本、规定的工作时间,生产质量均匀、符合规范的产品。为了能够达到上述要求,如果制造现场的操作混乱,比如制作工…...
无涯教程-Perl - lock函数
描述 此函数将咨询锁放在共享变量或THING中包含的引用对象上,直到该锁超出范围。 lock()是一个"弱关键字":这意味着,如果您在调用该函数之前已通过该名称定义了该函数,则将改为调用该函数。 语法 以下是此函数的简单语法- lock THING返回值 此函数不返回任何值…...
SpringBoot案例-部门管理-前后端联调
前后端联调 教学资料中提供了“前端工程”,将其解压即可使用nginx,启动nginx后,访问:http://localhost:90 小结 开发流程 明确需求、阅读接口文档、思路分析、接口开发(遵循接口文档)接口调试 postman测…...
每天一道leetcode:139. 单词拆分(动态规划中等)
今日份题目: 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。 示例1 输入: s "leetcode", …...
【C++】友元(含内部类)
一、友元是什么 我把你添加为我的友元,那么你可以访问我的成员。特别注意:它是单向的。即,我把你添加为我的友元,我却不能访问你的成员,除非你把我添加为你的友元。 以下代码可以让你粗略了解友元的使用。 #includ…...
SQL | 检索数据
1-检索数据 1.1-检索单个列 SELECT prod_name FROM Products; 上述SELECT语句从Products表中检索一个名为prod_name的列。 所要查找的列在select后面,from关键字指出从那个表查询数据。 输出如下: prod_name8 inch teddy bear12 inch teddy bear18…...
typeScript 之 运算符
工具: PlayGround 算术运算符 运算符描述加-减*乘/除%取模(求余)自增–自减 注意和--,实例: let value 0; console.log(value); //0, 先显示再增加后为1 console.log(value); //2,先增加后为2再显示关系运算符 运算符描述 …...
BGP实验
题目 IP地址配置 172.16.X.0/24为模拟用户环回接口接口 172.16.7.X/32为BGP邻居关系建立的环回接口 R1: R2: R3: R4: R5: R6: R7: R8: BGP邻居关系建立、宣告和反射器、联邦配置 R…...
pytest fixture 常用参数
fixture 常用的参数 参数一:autouse,作用:自动运行,无需调用 举例一:我们在类中定义一个function 范围的fixture; 设置它自动执行autouseTrue,那么我们看下它执行结果 输出: 说明:…...
vue项目里面有多个模块的服务,前端处理url转发
先看下vue的代理配置里面: 现在是在 /pca 基础上增加了 2个模块的服务: /dca、 /api 现在服务器的nginx 没有在/pca 服务里面做转发接受 /dca、 /api的服务,所以需要前端自己去配置每个服务模块对应的 URL 先拿登录的api 做示例吧: 先定义…...
web表单
在了解了 Flask Bootstrap 基本框架之后,我们来了解一下 Flask 框架的 表单( form ),以帮助我们创建交互式的 Web 应用,最后会有个提交个人信息的例子。 Flask-WTF 是 Flask 框架的一个扩展,用来做表单的交互,是对 WT…...
C++BUG记录:文件无法创建,文件路径正确但使用了Format
问题1:xx.Format()不存在与参数列表匹配的重载函数 问题:文件的路径名字是通过Format转换组合而成的,会报错“FileName.Format()不存在与参数列表匹配的重载函数”。 FileName.Format("%s%d", FilePath, num);//报错:…...
nodejs框架 express koa介绍以及从零搭建 koa 模板
express 下载 npm install express搭建服务 const express require("express");const app express();app.get("/home", (req, res) > {res.send("home"); });app.listen(3000, () > {console.log("http://127.0.0.1:3000")…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
PostgreSQL——环境搭建
一、Linux # 安装 PostgreSQL 15 仓库 sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm# 安装之前先确认是否已经存在PostgreSQL rpm -qa | grep postgres# 如果存在࿰…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
密码学基础——SM4算法
博客主页:christine-rr-CSDN博客 专栏主页:密码学 📌 【今日更新】📌 对称密码算法——SM4 目录 一、国密SM系列算法概述 二、SM4算法 2.1算法背景 2.2算法特点 2.3 基本部件 2.3.1 S盒 2.3.2 非线性变换 编辑…...
前端打包工具简单介绍
前端打包工具简单介绍 一、Webpack 架构与插件机制 1. Webpack 架构核心组成 Entry(入口) 指定应用的起点文件,比如 src/index.js。 Module(模块) Webpack 把项目当作模块图,模块可以是 JS、CSS、图片等…...
