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

2024-09-04 00:28

本文主要是介绍【JavaEE初阶】JVM内存划分和类加载过程以及垃圾回收,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

🌲内存划分

🚩堆(线程共享)

🚩栈

🚩元数据区

🍃类加载过程

🚩双亲委派模型

🎄垃圾回收机制(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,依此内推)。

这篇关于【JavaEE初阶】JVM内存划分和类加载过程以及垃圾回收的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1134472

相关文章

Java数组初始化的五种方式

《Java数组初始化的五种方式》数组是Java中最基础且常用的数据结构之一,其初始化方式多样且各具特点,本文详细讲解Java数组初始化的五种方式,分析其适用场景、优劣势对比及注意事项,帮助避免常见陷阱... 目录1. 静态初始化:简洁但固定代码示例核心特点适用场景注意事项2. 动态初始化:灵活但需手动管理代

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

将Java项目提交到云服务器的流程步骤

《将Java项目提交到云服务器的流程步骤》所谓将项目提交到云服务器即将你的项目打成一个jar包然后提交到云服务器即可,因此我们需要准备服务器环境为:Linux+JDK+MariDB(MySQL)+Gi... 目录1. 安装 jdk1.1 查看 jdk 版本1.2 下载 jdk2. 安装 mariadb(my

SpringBoot中配置Redis连接池的完整指南

《SpringBoot中配置Redis连接池的完整指南》这篇文章主要为大家详细介绍了SpringBoot中配置Redis连接池的完整指南,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以... 目录一、添加依赖二、配置 Redis 连接池三、测试 Redis 操作四、完整示例代码(一)pom.

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

基于Java实现回调监听工具类

《基于Java实现回调监听工具类》这篇文章主要为大家详细介绍了如何基于Java实现一个回调监听工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录监听接口类 Listenable实际用法打印结果首先,会用到 函数式接口 Consumer, 通过这个可以解耦回调方法,下面先写一个

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析