【JavaEE】JVM 内存区域划分,以及 Java 垃圾回收机制引用计数器,可达性分析等

news2024/9/23 11:27:31

目录

1. JVM执行流程

2. JVM运行时数据区

2.1 堆

2.2 Java虚拟机栈(线程私有)

2.3本地方法栈(线程私有)

2.4 程序计数器

2.5 元数据区

3. JVM的类加载机制

1) 加载

2) 验证

3) 准备

4) 解析

5) 初始化

双亲委派模型

4. java垃圾回收

4.1 死亡对象判断方法

a) 引用计数算法

b) 可达性分析算法(JVM使用此方法)

4.2 垃圾回收算法

a) 标记清除算法

b) 复制算法

c) 标记-整理算法

d) 分代算法


1. JVM执行流程

JVM是Java运行的基础,也是实现一次编译到处执行的关键,那么JVM是如何执行的呢?

程序在执行之前先要把java代码转换成字节码 (class文件) , JVM 首先需要把字节码通过一定的方式类加载器(ClassLoader) 把文件加载到内存中 运行时数据区(Runtime Data Area), 而字节码文件是 JVM 的一套指令规范, 并不能直接交给底层操作系统去执行, 因此需要特定的命令解析器 执行引擎(Execution Engine) 将字节码翻译成底层系统指令, 再交由CPU去执行, 而这个过程需要调用其他语言的接口 本地库接口(Native Interface) 来实现整个程序的功能, 这就是4个主要组成部分的职责与功能.

总的来看, JVM 主要通过分为一下4个部分, 来执行Java程序的, 它们分别是:

1. 来加载器(ClassLoader)

2. 运行时数据区 (Runtime Data Area)

3. 执行引擎 (Execution Engine)

4. 本地库接口 (Native Interface)

2. JVM运行时数据区

jvm 从系统申请了一大块内存, 这一大块内存给 java 程序使用的时候, 右会根据实际的使用用途来换分出不同的空间, 这个就是区域划分.

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

2.1 堆

堆的作用: 程序中创建(new) 的所有对象都保存在堆中.

我们常见的JVM参数设置-Xms10m最小启动内存是针对堆的,-Xmx10m最大运行内存也是针对堆的。
ms 是 memory start 简称,mx 是 memory max 的简称。

堆里面分为两个区域:新生代和老生代,新生代放新建的对象,当经过一-定 GC次数之后还存活的对象会放入老生代。新生代还有3个区域:一个Endn +两个Survivor (S0/S1) 。

垃圾回收的时候会将Endn中存活的对象放到一个未使用的Survivor中,并把当前的Endn和正在使
用的Survivor清楚掉。
 

2.2 Java虚拟机栈(线程私有)

栈分为本地方法栈和/虚拟机栈, 其中包含了方法调用关系和局部变量;

虚拟机栈记录了 java 代码的调用关系和java代码的局部变量 ;

本地方法栈记录了本地方法的调用关系和局部变量, 也就是在 jvm 内部通过 C++ 写的代码的调用关系和局部变量. (一般不会关注本地方法栈)

Java虚拟机栈的作用: Java 虚拟机栈的生命周期和线程相同,Java 虚拟机栈描述的是Java方法执行的内存模型: 每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。咱们常说的堆内存、栈内存中,栈内存指的就是虚拟机栈。

Java虚拟机栈中包含了以下4部分:

1. 局部变量表: 存放了编译器可知的各种基本数据类型(8大基本数据类型)和对象引用. 局部变量表是所需内存空间在编译期间完成的分配, 当进入一个方法时, 这个方法需要在帧中分配多大的局部变量是完全确定的, 在执行期间不会改变局部变量表的大小. 简单来说就是存放方法参数和局部变量.

2.操作栈: 每个方法会生成一个先进后出的操作栈。

3.动态链接: 指向运行时常量池的方法引用。

4.方法返回地址: PC 寄存器的地址。

什么是线程私有?
由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置, 每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储。我们就把类似这类区域称之为"线程私有"的内存。

2.3本地方法栈(线程私有)

本地方法栈和虚拟机栈类似,只不过Java虚拟机栈是给JVM使用的,而本地方法栈是给本地方法使
用的。

2.4 程序计数器

这个区域比较小的空间, 专门用来存储下一条要执行的 java 指令的地址

程序计数器的作用: 用来记录当前线程执行的行号的.

程序计数器是一块比较小的内存空间, 可以看作是当前线程所执行的字节码的行号指示器.

如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地
址;如果正在执行的是一个Native方法,这个计数器值为空。

程序计数器内存区域是唯一 一个 在JVM规范中没有规定任何OOM情况的区域!
 

2.5 元数据区

"元数据" 是计算机中的一个常见术语(mete data), 往往指的是一些辅助性质的, 描述性质的属性.

一个程序有哪些类, 每个类都有哪些方法, 每个方法都要包含哪些指令, 都会记录在元数据区

经典笔试题

小结

3. JVM的类加载机制

类加载指的是 java 进程运行的时候, 需要把.class 文件从硬盘读取到内存, 并进行一系列校验解析的过程.

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

其中前5步是固定的顺序并且也是类加载的过程,其中中间的3步我们都属于连接,所以对于类加载
来说总共分为以下几个步骤:
1.加载
2.连接
        a.验证
        b.准备
        c.解析
3.初始化

下面我们分别来看每个步骤的具体执行内容。

1) 加载

把硬盘上的.class 文件找到, 打开文件读取文件的内容(认为读到的是二进制的数据)

“加载”(Loading) 阶段是整个“类加载”(Class Loading)过程中的一个阶段,它和类加载
Class Loading是不同的,一个是加载Loading另一个是类加载Class Loading,所以不要把二者搞混
了。

在加载Loading阶段,Java虚拟机需要完成以下三件事情:

1) 通过一个类的全限定名来获取定义此类的二进制字节流。
2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3) 在内存中生成-个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
 

2) 验证

需要确保读到的文件的内容, 是合法的.class 文件(字节码文件)格式

具体的验证依据, 在java的虚拟机规范中, 有明确的格式说明.

验证是连接阶段的第一步,这一阶段的目的是确保Class文件的字节流中包含的信息符合《Java虚 拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
验证选项:

  • 文件格式验证
  • 字节码验证
  • 符号引用验证....
     

3) 准备

准备阶段是正式为类中定义的变量(即静态变量,被static修饰的变量)分配内存设置类变量初始值
的阶段。

比如此时有这样一行代码:

public static int value= 123;

它是初始化value的int值为0,而非123。
 

4) 解析

解析阶段是Java虛拟机 将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程。

符号应用和直接应用:

5) 初始化

初始化阶段,Java 虚拟机真正开始执行类中编写的Java程序代码,将主导权移交给应用程序。初始化阶段就是执行类构造器方法的过程。

把类对象的各个部分的属性进行赋值填充, 触发对父类的加载, 初始化静态成员, 执行静态代码块.

双亲委派模型

双亲委派模型用于加载环节, 描述了如何查找 .class 文件的策略

双亲委派模型是一种Java类加载器和字节码加载器的实现策略。它主要用于解决类加载器之间的循环依赖问题。

在双亲委派模型中,当一个类加载器收到一个类的加载请求时,它首先不会自己去加载这个类,而是把这个请求委托给父类加载器去完成。只有当父类加载器无法完成这个加载任务时,子类加载器才会尝试自己去加载这个类。这样可以确保Java核心库中的类总是由其父类加载器(如Bootstrap ClassLoader)来加载,从而避免了类加载器之间的循环依赖问题。

JVM 中进行类加载的操作, 是有一个专门的模块, 称为"类加载器"(ClassLoader)

JVM 中的类加载器默认是有三个(也可以自定义)

BootStrapClassLoader : 负责查找标准库中的目录.

ExtensionClassLoader: 负责查找扩展库的目录

ApplicationClassLoader: 负责查找当前项目的代码目录以及第三方库的目录

图示过程: 

双亲委派模型的优点:

1.避免重复加载类: 比如A类和B类都有一个父类C类, 那么当A启动时就会将C类加载起来, 那么在B类进行加载时就不需要在重复加载C类了.

2.安全性: 使用双亲委派模型也可以保证了 Java 的核心 API 不被篡改, 如果没有双亲委派模型, 而是每个类加载器加载自己的话就会出现一些问题, 比如我们编写一个称为 java.lang.Object类的话,那么程序运行的时候, 系统就会出现多个不同的Object来, 而有些Object类又是用户自己提供的, 因此安全性就不能得到保证了.

4. java垃圾回收

上面讲了Java运行时内存的各个区域。对于程序计数器、虚拟机栈、本地方法栈这三部分区域而言,其生命周期与相关线程有关,随线程而生,随线程而灭。并且这三个区域的内存分配与回收具有确定性,因为当方法结束或者线程结束时,内存就自然跟着线程回收了。因此我们本节课所讲的有关内存分配和回收关注的为Java堆与方法区这两个区域。

Java堆中存放着几乎所有的对象实例,垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象哪些还存活,哪些已经"死去"。判断对象是否已"死"有如下几种算法
 

4.1 死亡对象判断方法

a) 引用计数算法

引用计数描述的算法为:

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

引用计数法实现简单,判定效率也比较高,在大部分情况下都是一一个不错的算法。比如Python语言 就采用引用计数法进行内存管理。

但是,在主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题.

引用计数缺点:

1.消耗额外的内存空间.

要给每个对象都安排一个计数器.(如果计数器按照2个字节算), 如果整个程序中对象数目很多, 总的消耗空间也会非常多.

尤其是如果每个对象体积很小, (假设每个对象4字节), 计数器消耗的空间已经到达对象的一般

2.引用计数器可能会产生"循环引用的问题", 此时, 引用计数器就无法正常工作了.

b) 可达性分析算法(JVM使用此方法)

在.上面我们讲了,Java并不采用引用计数法来判断对象是否已"死",而采用"可达性分析"来判断对象是否存活(同样采用此法的还有C#、Lisp-最 早的- -门采用动态内存分配的语言)。

此算法的核心思想为:通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的。以下图为例: 

核心思路: 在写代码的过程中, 会定义很多的变量, 比如栈上的局部变量/方法区中的静态类型的变量/常量池中引用的对象....

就可以从这些变量作为起点出发, 尝试去进行"遍历", 所谓的遍历就是会沿着这些变量中持有的引用类型的成员, 再进一步的往下进行访问...

所有能被访问到的对象, 自然不是垃圾了, 剩下的遍历一圈也访问不到的对象, 自然就是垃圾.

4.2 垃圾回收算法

通过上面的学习我们可以将死亡对象标记出来了,标记出来之后我们就可以进行垃圾回收操作了,在正式学习垃圾收集器之前,我们先看下垃圾回收机器使用的几种算法(这些算法是垃圾收集器的指导思想)

a) 标记清除算法

"标记 - 清除" 算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段: 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。后续的收集算法都是基于这种思路并对其不足加以改进而已。

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

1.效率问题: 标记和清除这两个过程的效率都不高
2.空间问题: 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。

b) 复制算法

"复制" 算法是为了解决 "标记清理" 的效率问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。这样做的好处是每次都是对整个半区进行内存回收,内存分配时也就不需要考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配即可。此算法实现简单,运行高效。算法的执行流程如下图:

c) 标记-整理算法

复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法。

针对老年代的特点,提出了一种称之为 "标记整理算法"。标记过程仍与 "标记-清除" 过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。流程图如下:

d) 分代算法

分代算法和上面讲的3种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策
略,从而实现更好的垃圾回收。这就好比中国的一国两制方针一样,对于不同的情况和地域设置更符合当地的规则,从而实现更好的管理,这就时分代算法的设计思想。

当前JVM垃圾收集都采用的是 "分代收集(Generational Collection)" 算法,这个算法并没有新思想,
只是根据对象存活周期的不同将内存划分为几块。一般是把Java堆分为新生代和老年代。在新生代
中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法; 而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用 "标记清理" 或者 "标记-整理" 算法。

哪些对象会进入新生代? 哪些对象会进入老年代?

新生代:  一般创建的对象都会进入新生代;

老年代: 大对象和经历了N次(一般情况默认是15次)垃圾回收依然存活下来的对象会从新生代
移动到老年代。

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

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

相关文章

超精细CG杰作:8K壁纸级官方艺术插画,展现极致美丽与细节的汉服女孩

极致精美的数字艺术杰作:8K壁纸级别的官方插画,展现超高清细节与和谐统一的美感,女孩的精致面容与眼神在光影下熠熠生辉,汉服主题下的超高分辨率作品,文件巨大,细节丰富,令人惊叹。 正向提示词…

内存泄漏之如何使用Visual Studio的调试工具跟踪内存泄漏?

使用Visual Studio的调试工具跟踪内存泄漏是一个系统性的过程,主要包括启用内存泄漏检测、运行程序、分析内存使用情况以及定位泄漏源等步骤。 Visual Studio提供了多种方式来检测内存泄漏,你可以根据自己的需求选择合适的方法。 注意:下面…

父页面选项式api,子页面组合式api,子页面如何获取父页面的方法以及传值到将子页面的值传给父页面

开发的项目中是vue3的项目,但是有些同事用vue2中的选项式api写法,有些同事使用的是vue3组合式api的写法,此时子页面需要获取父页面的方法把数据传入父页面的方法中 父页面: 在父页面中order-item组件中创建自定义方法navigation和…

Leetcode每日刷题之剑指offer 57.和为s的两个数字(C++)

1.题目解析 现在题目改名为LCR.查找总价值为目标值的两个商品,虽然题目改变但是核心并未变化,都是需要寻找出和为指定数字的两数 2.算法原理 我们由题目知道给出的数组是递增的,所以在数组的首尾固定两个指针,判断其和是否为指定数…

Ceph篇之利用shell脚本实现批量创建bucket桶

Ceph创建bucket桶 在 Ceph 中创建桶(bucket)需要使用 Ceph 对象网关(RGW)。 注:如果查看shell批量创建脚本请直接参见目录3 1. 利用radosgw-admin工具创建桶 确保 Ceph 集群和对象网关已正确配置 确保你的 Ceph 集群…

快速了解Vi 和 Vim 编辑器三种模式及快捷键使用

😀前言 本篇博文是关于Vi 和 Vim 编辑器的三种模式及快捷键使用,希望你能够喜欢 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意…

大数据产业链图谱_产业链全景图_大数据行业市场分析

数据作为新型生产要素,是数字化、网络化、智能化的基础,已快速融入生产、分配、流通、消费和社会服务管理等各环节,影响着千行百业,推动着我国数字经济的蓬勃发展。 大数据又称巨量数据、海量数据,是由数量巨大、结构…

C语言 | Leetcode C语言题解之第341题扁平化嵌套列表迭代器

题目&#xff1a; 题解&#xff1a; struct NestedIterator {int *vals;int size;int cur; };void dfs(struct NestedIterator *iter, struct NestedInteger **nestedList, int nestedListSize) {for (int i 0; i < nestedListSize; i) {if (NestedIntegerIsInteger(neste…

Sprache:轻量级C#解析器构建,可用于字符串验证等。

我们在开发中&#xff0c;经常需要对一些结构化文本进行解析&#xff0c;用于验证是否符合规则。我们一般会使用正则表达式&#xff0c;同时正则表达式也非常强大&#xff0c;但正则表达式在语法不便阅读与维护。 下面介绍一个简单、轻量级的库&#xff0c;方便我们在C#代码中…

React 学习——打包后,包体积可视化

1、安装插件 &#xff08; source-map-explorer &#xff09; npm i source-map-explorer 2、在配置文件package.json中加入 &#xff08; "analyze": "source-map-explorer build/static/js/*.js" &#xff09;&#xff0c;位置截图 "analyze&q…

Flask 线上高并发部署方案实现

目录 1、Flask默认多线程执行 2、使用gevent.pywsgi实现 3、是用uWSGI服务器实现 1、Flask默认多线程执行 前言&#xff1a;在Flask的较早版本中&#xff0c;默认并不支持多线程模式。然而&#xff0c;从Flask 0.9版本开始&#xff0c;引入了多线程模式的支持&#xff0c;并…

红酒与旅游攻略:旅行途中的风味之选

在旅行的道路上&#xff0c;我们总是渴望寻找那些能够触动心灵、留下深刻记忆的不同体验。而红酒&#xff0c;作为一种充满韵味和故事的饮品&#xff0c;无疑是旅行途中的风味之选。洒派红酒&#xff08;Bold & Generous&#xff09;&#xff0c;这款定制红酒&#xff0c;以…

基于xilinx IP的频域脉冲压缩算法的实现和matlab仿真

工具&#xff1a;matlabR2021b&#xff0c;vivado2018.3. 脉冲压缩的原理 脉冲压缩实际上就是对接收信号进行匹配滤波处理。根据发射的波形不同&#xff0c;脉冲压缩时选择不同的匹配滤波器系数。 数字脉冲压缩的实现方式有两种: 一是时域卷积法; 二是频域乘积法。依据傅里叶…

智能化包括自动化与非自动化

智能化通常指的是系统或设备具备智能功能&#xff0c;以提高其自主性和效率。智能化可以分为自动化与非自动化两大类&#xff0c;每一类都有其独特的特点和应用场景。 一、自动化 自动化指的是系统能够在无需人为干预的情况下完成任务或操作。自动化系统通常依赖于预设的规则、…

基于LangChain手工测试用例转接口自动化测试生成工具!

接口自动化测试用例是一个老生常谈的问题&#xff0c;在未引入人工智能之前&#xff0c;也有非常多的生成方案&#xff0c;比如如下所示&#xff0c;通过har生成接口自动化测试用例&#xff1a; 但是以上的生成方式依然是有一些弊端&#xff0c;比如 har 本身虽然能表述一定的接…

铁威马NAS教程丨TOS应用中心无法下载应用,显示0%或“准备中“?

故障排除 适用机型 所有 TNAS型号 原因分析 该现象通常是网络配置不正确或文件系统异常引起&#xff1a; 1.获取不到网关&#xff0c;状态栏甚至显示红色的“未连接” 2.路由器自动分配的DNS无法解析出下载服务器的域名 3.文件系统为只读文件系统 解决方法 1.重新获取…

中间件|day1.Redis

Redis 定义 Redis 是一个开源&#xff08;BSD许可&#xff09;的&#xff0c;内存中的数据结构存储系统&#xff0c;它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构&#xff0c;如 字符串&#xff08;strings&#xff09;&#xff0c; 散列&#xff08;hash…

TongHttpServer安装部署

TongHttpServer安装部署 毫无背景不是你堕落的理由&#xff0c;而更应该是你前进的动力。你该花时间思考&#xff0c;如何打好一副烂牌&#xff1b;而不是抱怨命运&#xff0c;或者干脆撕牌。 部署环境 TongHttpServer V6.0是一款轻量级负载均衡软件&#xff0c;支持 0SI 四层…

【瑞芯微RV1126(深度学习模型部署)】部署自己训练的yolov8-seg,实现足型检测!

前言 如果按照本系列第一篇博客那样交叉编译了opencv&#xff0c;那本文有些步骤就不用了&#xff0c;比如交叉编译工具链的下载&#xff0c;所以自己斟酌步骤。 本系列第一篇&#xff1a;https://blog.csdn.net/m0_71523511/article/details/139636367 本系列第二篇&#xff…

Android-RK356x GT9XX多点触控设置为单点触控的方法

本文基于RK356x Android11系统描述GT9XX驱动芯片由多点触摸改为单点触摸功能。本次介绍的是触觉智能的Purple Pi OH鸿蒙开源主板&#xff0c;Purple Pi OH是华为Laval官方社区主荐的一款鸿蒙开发主板。 该主板主要针对学生党&#xff0c;极客&#xff0c;工程师&#xff0c;极大…