【JavaEE初阶】JVM内存划分和类加载过程以及垃圾回收

news2024/9/23 15:29:39

目录

🌲内存划分

🚩堆(线程共享)

🚩栈

🚩元数据区

🍃类加载过程

🚩双亲委派模型

🎄垃圾回收机制(GC)

🚩找到谁是垃圾(不被继续使用的对象)

🚩释放对应的内存

🏀标记-清除

🏀复制算法

🏀标记-整理

🏀分代回收


🌲内存划分

JVM也就是Java进程,这个进程一旦跑起来之后,就会从操作系统这里,申请一大块内存空间,JVM接下来就要进一步的对这个大的空间进行划分,划分成不同区域,每个区域都有不同的作用。

具体如何划分的呢?

JVM运行时数据区域也叫内存布局,但需要注意的是它和Java内存模型((JavaMemoryModel,简 称JMM)完全不同,属于完全不同的两个概念,它由以下5大部分组成:

🚩堆(线程共享)

堆的作用:程序中创建的所有对象都在保存在堆中。(也就是new出来的对象)

成员变量也是在堆中,new出来的对象包含了成员变量,这些东西是一起的

对于里面的新生代来年代后续讲述

🚩栈

分为Java虚拟机栈和本地方法栈。

保存了方法的调用关系,例如写代码时A调用B,B调用C......,这里的调用就是使用栈来维护,只不过虚拟机栈放的Java代码的调用关系,而本地方法栈是针对JVM内部的调用关系,也就是C++代码的调用关系

注意:上述的栈和堆与数据结构的栈和堆没有任何关系,只是名字相同

🚩元数据区

以前叫做方法区,从Java8开始,叫做元数据区。

里面放的是"类对象"。

还放了方法相关信息,类中有一些方法,每个方法都代表了一系列指令集合(JVM字节码指令),还有常量池,编译出来的字节码。

🚩程序计数器(PC) 

他是内存区域中最小的区域,只需要保存当前要执行的下一条指令(JVM字节码)的地址。

具体代码实现:

基本原则:

一个对象在哪个区域,取决于对应变量的形态。

  • 1)局部变量  =>栈上
  • 2)成员变量  =>堆上
  • 3)静态成员变量  =>元数据区/方法区

补充:上述四个区域中,堆和元数据区是整个进程只有一份,栈和程序计数器是每个线程都有一份,则堆和元数据区都是多个线程共享同一份数据,每个线程的局部变量,则不是共享的,每个线程都是有自己的一份。

🍃类加载过程

当前写的Java代码,是一个 .java文件,是在硬盘上的,一个Java进程要跑起来,需要先把 .java文件变成 .class文件,还是在硬盘上,在加载到内存中,得到"类对象"。

一个Java进程要跑起来,也就是要执行指令,要执行的cpu指令,都是通过字节码让JVM翻译出来,也就需要让字节码进入到内存中。

接下来我们来看下类加载的执行流程。

对于一个类来说,它的生命周期是这样的:

  • 1)加载

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

  • 2)验证

检查 .class文件的内容是否符号要求。

.class文件是由javac编译器生成的,具体生成的 .class文件里面具体是什么样的格式,在Java官方文档中是有明确定义的。

  • 3)准备

给类对象分配内存空间。

  • 4)解析

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

  • 5)初始化

针对"类对象"中的各个部分进行初始化(不是针对对象初始化,和构造方法无关),给执行静态成员,执行静态代码块进行初始化等。

面试:记住上述5个步骤,以及各个变量的内存区域

🚩双亲委派模型

双亲委派模型出现在上述"加载"这个环节,根据代码中写的"全限定类名"找到对应的 .class 文件。

全限定类名指 包名 + 类名。例如String => java.long.String ,List => java.util.List。      

双亲委派模型描述了JVM加载 .class文件过程中,找文件的过程。这就涉及到"类加载器"

"类加载器"在JVM中包含了一个特定的模块/类,这个类负责完成后续类加载的过程。

JVM中内置了三个类加载器:负责加载不同的类

  • 1)BootstrapClassLoader:负责加载标准库的类
  • 2)ExtentionClassLoader:负责加载JVM扩展库的类(前面学习过程中没有涉及到任何扩展类,历史遗留,本身很少使用)
  • 3)ApplicationClassLoader:负责加载第三方库的类和你自己写的代码的类

他们三个类存在一个父子关系:这个父子关系不是继承表示的,而是通过类加载器中存在一个"parent"这样的字段指向自己的"父亲"。

注意:"双亲委派模型"本身翻译是不标准的,更准确的翻译为"父亲委派模型"。

工作过程:

例如,给定了一个类的"全限定类名",自己写的类 => java111.Test

这就是双亲委派模型,拿到任务,先交给父亲处理,父亲处理不了,再自己处理。

上述过程主要为了应对场景:

比如你自己代码中写了一个类,这个类的名字和标准库/扩展库冲突了,JVM就会确保加载的类是标准库中的类(就不加载你自己写的类了)。相当于我自己写了一个java.long.String,那么这套模型就能够确保最终在JVM中加载原有的java.long.String了。

类加载过程中的双亲委派模型也是一个经典面试题。

🎄垃圾回收机制(GC)

垃圾回收机制,是Java提供的对于内存(变量或者对象)自动回收的机制。

GC回收的是"内存",更准确的说是对象,回收的是堆上是内存。

一定是一次回收一个完整的对象,不能回收"一部分对象"。

GC的具体流程,主要有两个步骤:

🚩找到谁是垃圾(不被继续使用的对象)

谁是垃圾这个事情,并不太好找,一个对象什么时候创建这个是明确的,但什么时候不在使用,这个时机往往很模糊。在编程中,一定要确保代码中使用的每个对象,都得是有效的,千万不要出现"体现释放"的情况。

因此判定一个对象是否是垃圾,判定方式就比较保守。比如,如果使用"上次使用时间"的方式来判定垃圾,就是不行的,这就容易错杀。

此处就引入了一个比较保守的做法,判定某个对象,是否存在引用指向它。在代码中都是通过对象的引用来使用的,那么如果没有引用指向这个对象,意味着这个对象注定无法在代码中被使用了。这就可以视为这个对象是垃圾了。

例如:Test t = new Test(); t = null; => 修改t的指向,new Test对象没有引用指向了,就视为垃圾。

具体怎么判定某个对象是否有引用的指向呢?方式有很多,此处介绍两种方式:

  • 1)引用计数(不是JVM采取的方案,而是Python / PHP的方案)

引用计数描述的算法为:
给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已"死"。

看似比较好用,但是存在两个缺陷:

a)消耗额外的存储空间

如果你的对象比较大浪费空间还好,如果对象比较小,并且对象数目还多,空间占用多了,空间的浪费的就多了。

b)存在"循环引用"的问题(面试官考引用计数,也就是靠你循环引用问题)

例如:

两行实例代码对应的图示

接下来:a.t = b,进行引用复制,把b里面的地址复制给Test类中的引用类型的成员,也就是把b地址复制给a对象中t成员,此时就有两个引用指向0x200了,那么0x200中的引用计数器就为2。b.t = a也是同理。

然后再执行 a = null; b = null;,此时a中就为null,意味着0x100中的引用计数器就为1,b为null,意味着0x200中的引用计数器就为1,这时候这两个对象相互指向对方,就导致了两个对象的引用计数都为1(不为0,不是垃圾),但是你外部代码也无法访问这两个对象!!!

  • 2)可达性分析(是JVM采取的方案)

这个解决了空间的问题,也解决了循环引用问题,也付出了时间上的代价。

核心思想:"遍历",JVM把对象之间的引用关系理解成了一个"树形结构",JVM就会不停的遍历这样的结构,把所有能遍历访问到的对象标记成"可达",剩下的就是"不可达"。

在这里面,是有很多课这样的树(不一定是二叉树),这些树的根节点如何确定的?(GC roots)

🚩释放对应的内存

🏀标记-清除

直接把标记为垃圾的对象对应的内存,释放掉(简单粗暴)。

"标记-清除"算法的不足主要有两个 :

  1. 效率问题 : 标记和清除这两个过程的效率都不高
  2. 空间问题 : 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中

需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收

🏀复制算法

"复制"算法是为了解决"标记-清理"的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。

当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。

此算法实现简单,运行高效。算法的执行流程如下图 :

这里面最大的问题,空间浪费的太多了,另一方面要保留的对象比较多,时间花费也不少。

🏀标记-整理

能解决内存碎片,也能解决

标记过程仍与"标记-清除"过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动(类似于顺序表删除中间元素),然后直接清理掉端边界以外的内存。流程图如下:空间利用率的问题。

这样的搬运时间开销更大。在JVM中实际的方案,是综合上述的方案,更复杂的策略。

🏀分代回收

也就是分情况讨论,根据不同的场景/特点,选择合适的方案。根据对象的年龄来讨论的(我们说GC有一组线程会进行周期性的扫描,某个对象经历了一轮GC扫描之后,还是存在,没有成为垃圾,那么年龄 +1,依此内推)。

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

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

相关文章

纷享销客CRM渠道分销之多维度数据分析介绍

预设渠道报表驾驶舱 基于渠道分销场景,系统预设了一个全面的渠道订货数据驾驶舱,旨在通过直观的数据分析,为企业提供深度的市场洞察和业务决策支持。该驾驶舱提供渠道订货的概览,快速把握整体订货动态。 渠道订货波动分析&#…

Scratch 角色绘制

引言 在Scratch这款强大的可视化编程环境中,不仅可以通过编程来实现各种有趣的互动项目,还能利用内置的绘图编辑器来创造独一无二的角色。本文将引导你如何使用Scratch中的绘图编辑器,绘制出属于你自己的简单图形角色。 准备工作 首先&#…

【数据分享】2000—2023年我国250米分辨率逐月植被覆盖度(FVC)栅格数据

植被覆盖度(Fractional Vegetation Cover,简称FVC)是指植被(包括叶、枝、茎)在水平地面的垂直投影面积占研究区总面积的百分比。植被覆盖度是生态学、地理学、气候学等多个学科研究的基础数据,对于理解生态…

多线程——创建

线程的创建与启动 Java中,所有的线程对象都必须是Thread类或其子类的实例。 三种创建方式: 集成Thread类创建线程类 继承Thread类,重写run方法,run方法的方法体代表线程需要完成的任务,称为线程执行体。 创建子类的实…

鸿蒙开发5.0【Code Linter实现代码检查】

Code Linter针对ArkTS/TS代码进行最佳实践/编程规范方面的检查。 检查方法: 编辑器自带Code Linter。 在已打开的代码编辑器窗口单击右键点击Code Linter,或在工程管理窗口中鼠标选中单个或多个工程文件/目录,右键选择Code Linter执行代码检…

2024年医疗器械企业5款CRM系统对比评测

医疗器械行业是一个多学科交叉、知识密集型、资金密集型的高新技术产业,进入门槛较高,产品种类繁多,技术含量较高。 随着医改的深入推进,医疗器械集采常态化成为行业新常态,中国的医疗器械行业不仅面临着巨大的市场潜…

zabbix6.4配置监控k8s 1.28集群

zabbix6.4配置监控rke2 rancher k8s集群 1. 说明1.1 为什么要使用zabbix6.x监控k8s1.2 部署环境1.3 部署前的一些问题 2. 使用helm3部署zabbix proxy和zabbix agent2.1 添加仓库2.2 修改配置2.3 部署2.4 确认部署情况 3. 在zabbix web页面配置连接zabbix proxy3.1 添加Proxy代理…

kali (linux) 配置windows远程桌面(mstsc.exe)连接

Kali 安装 tightvncserver 一、软件说明 1) tightvncserver是一个轻量级,只能建立桌面,不能查看TTY7/TTY1正在显示的桌面,但x11vnc可以,相比x11vnc安全传输差一些。反之,x11 vnc:安全传输较好,但占用资源比 tightvncs…

PB级内存计算项目实战-富华保险

一、保险项目的基本介绍 项目名称:富华阳光人寿保险 1. 行业背景介绍 在保险行业中,最为核心技术就是精算,精算简单来说就是根据人的年龄来计算应交保费问题,通过精算,让整个保险行业更加专业化,精细化 从而取代之间依靠经验判断的方式 精算到目前为止,并不仅仅计算保费,主要包…

Leetcode 1143. 最长公共子序列 记忆化搜索 优化 C++实现

Leetcode 1143. 最长公共子序列 问题:给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。如果不存在公共子序列,返回 0 。 一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对…

一体化智能电动窗帘:开启智能生活新时尚

史新华 在科技不断进步的今天,人们对生活品质的追求也越来越高。电动窗帘作为智能家居的重要组成部分,以其便捷、智能、时尚的特点,正逐渐走进千家万户。 添加图片注释,不超过 140 字(可选) 一、电动窗帘…

KEYSIGHT U2020 X系列 USB峰值和均值功率传感器

​ _是德(KEYSIGHT) _ U2020 X系列 USB峰值和均值功率传感器 苏州新利通仪器仪表 U2020 X 系列功率传感器得到 Keysight BenchVue 软件的支持。使用 BenchVue 软件,您无需编程便可轻松控制功率计记录数据,并以各种形式显示测量结果。 只需将传感器…

AI大模型与量子纠缠理论的结合,以及相关应用思考

大家好,我是微学AI,今天给大家介绍一下AI大模型与量子纠缠理论的结合,以及相关应用思考。将大模型(LLM)的基本原理与量子纠缠理论相结合是一个高度抽象的概念。我们首先需要理解这两个领域的基本原理,然后探…

#ARM开发 笔记

课程介绍 ARM开发 --> Linux移植 --> 驱动开发 前后联系:ARM和系统移植为驱动开发学习做准备工作 所需知识:C语言基础及STM32需要的硬件知识 学习方法 学习流程、思想和解决问题的方法即可 知道驱动的基本框架以及基本开发要求 底层课程导学 接口技…

NTFS安全权限和文件共享

一.常见文件系统 NTFS 描述: Windows最常使用的文件系统(New Technology File System)微软公司开发的一种专用于 Windows 操作系统的文件系统。 特点: 效率性 可以提高磁盘的读写性能; 可靠性 加密文件系统访问控制列…

Vue组件:使用Prop实现父组件向子组件传递数据

1、Prop 基本用法 由于组件实例的作用域是孤立的,因此子组件的模板无法直接应用父组件的数据。如果想要通过父组件向子组件传递数据,就需要定义 Prop。Prop 是父组件用来传递数据的一个自定义属性,这样的属性需要定义在组件选项对象的 props…

并发集合(二):CopyOnWriteArrayList

1、CopyOnWriteArrayList介绍 CopyOnWriteArrayList 是一个线程安全的ArrayList。 CopyOnWriteArrayList 是基于Lock锁和线程副本的形式来保证线程安全的, 在写数据时,先获取Lock锁,然后复制一个副本,添加数据时&…

Delphi7实现Json对象的序列化与反序列化

在高版本的 Delphi 中,实现序列化和反序列化非常简单。然而,在 Delphi 7 中,这个过程仍然需要一些额外的努力。为了简化这个问题,我花了一些时间封装了一个支持序列化和反序列化的 JSON 解析库。 type{$M}TStartupParameters cla…

MySQL的服务器与客户端:架构解析与实践

文章目录 MySQL的服务器和客户端服务端处理客户端请求连接管理解析与优化查询缓存语法解析查询优化 存储引擎不同的存储引擎查看支持的存储引擎为不同的表设置存储引擎 MySQL是一个广泛使用的开源关系数据库管理系统,其核心架构由服务器端和客户端两大部分组成。本文…

9/3 链表-力扣160 、203、206

160.相交链表 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交: 题目数据 保证 整个链式结构中不存在环。 注意,函…