后端(五):JVM

news2024/11/26 0:34:46

目录

JVM 中的内存区域划分

JVM 的类加载机制

 1.  加载

2. 验证

3. 准备

4. 解析

5. 初始化

JVM 中的垃圾回收策略

找,确认垃圾

1. 引用计数

2. 可达行分析

释放”垃圾“对象

1. 标记清除

2. 复制算法

3. 标记整理

分代算法


JVM也就是我们俗称的八股文,作为一个普通的程序员工作中是基本不可能用到的;因为它设计的初衷就是为了让程序员能够比较简单的,感知不到系统层面的一些内容(很多时候,程序员只关注业务逻辑,不需要关注底层实现细节)。

数年前开始,JVM成为了Java程序猿必考的内容。

JVM里的内容非常多,大部分内容咱叶看不懂,JVM本来是写给C/C++ 程序员看的,它的底层实现都是 C/C++ 的代码,研究JVM 的也都是那一批人。

我们这里针对JVM的面试题,作出以下一些讨论。

本篇主要在于以下三点:

  1. JVM 中的内存区域划分
  2. JVM 的类加载机制
  3. JVM 中的垃圾回收策略

本章比的不是你了解多少,比的是你能背下来多少

JVM 中的内存区域划分

JVM 其实是一个 Java 进程, Java 进程 会从操作系统中申请一大块内存区域,给 java代码使用

而这一大块内存区域,就有我们之前 SE 阶段提到的:栈区、堆区、方法区(新版的也叫 元数据区)。

内存区域划分最最核心的部分:

  1. 堆区: new 出来的对象(成员变量)
  2. 栈区: 维护方法之间的调用关系(局部变量)
  3. 方法区/元数据区: 放的是类加载之后的类对象(静态变量)

一般这里的考点就是 给你一段代码,问你某个变量在哪个区。

我们主要就是看这个变量是个什么变量(局部变量,成员变量【注意被 final 修饰过的局部变量】,静态变量)

这里简单举个例子:

void func() {
    Test t = new Test();
}

 这里的 t 对象是个引用类型,t是个局部变量,所以是在栈上的的,而 new Test(),这个对象本体是在堆上的。至于func() 这个方法是存在方法区的,方法在内存中都是以二进制的方式存储的。

我们来看一张更细节的图:

我们在SE 阶段认识的在做细分 。

其中将栈区分为  本地方法栈 和 虚拟机栈 。

这两个栈也很好区分:

  1. 虚拟机栈,是给 Java 代码使用的
  2. 本地方法栈,是个JVM 内部的本地方法使用的,我们说 JVM 源码都是 C++ 实现的(可以简单理解为是给 C++ 使用的)

程序计数器

这个程序计数器是用来记录当前程序指令到了哪一个指令了。

上述的 堆区 和 元数据区 在一个 JVM 进程中只有一份。

而栈(本地方法栈和 虚拟机栈)和 程序计数器 是存在多份的,每一个 线程 都存在一份

JVM 的类加载机制

类加载其实就是 把 .class 文件,加载到内存中,得到 类对象 这个过程。

咱们 祖师爷(冯诺依曼)提出:程序运行,就需要把依赖的"指令和数据" 加载到内存中。

这个类加载的过程非常复杂(一般不需要我们理解)

《深入理解 Java虚拟机(第三版)》这本书中,将类加载总结成了 5 个词 (必备)

类加载的声明周期如下:

 1.  加载

加载其实就是找到 .class 文件,并且读取文件内容

这里涉及到一个非常经典的考点,双亲委派模型,这个等到类加载的最后来讲;

2. 验证

.class 文件有明确的数据格式(二进制的)

 源码中 都有一个 ClassFile 来体现一个类有哪些信息

3. 准备

给类对象 分配内存空间(这里还没有走到 初始化阶段,所以这里的内存空间都是全 0 的)

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

比如此时有这样一行代码:
        public static int value = 123;
它是初始化 value 的 int 值为 0,而非 123。

4. 解析

解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程
这里涉及到 将 符号引用 替换为 直接引用 的过程

我们先来说说符号引用

符号引用就是 字符串常量,我们在 验证 阶段不是加载好了 .class 文件嘛,.class 文件存在了 字符串常量,我们这里就能直到它的相对位置(偏移量),但是并不知道它的实际位置。

这个时候的字符串常量就是 符号引用。

直接引用

当真正加载到内存中,就会把字符串常量填充到内存中的特定地址上;

字符串常量之间的相对位置还是一样的,但是这些字符串有了自己真正的内存地址,此时的字符串就是直接引用。

5. 初始化

其实 4 和 5 可以并做一步,但是人家作者规定了 将其分为两步。

解析是初始化了 字符串常量,而这一步是 初始化 对象【除字符串常量部分】(初始化静态成员,执行静态代码块、如果类存在父类,还需要加载父类)

ok,到这里 上述 五步 也就讲完了。

类加载这个步骤,啥时候会触发呢?

并非 jvm 一启动,就会把所有的 .class 都加载了,整体采纳了 (懒汉设计模式)的策略,非必要不加载。

啥叫必要

  1. 创建了这个类的实例
  2. 使用了这个类的静态  方法/属性
  3. 使用了这个类的子类

总之就是与这个类相关的类、属性、方法、实例化对象...

这里还有一个最关键的考点:双亲委派模型

这个模型所做的就是 加载这个步骤中,找一个 .class 文件这个过程

事实上,这个模型并非是类加载中一个很重要的步骤,但是是个非常重要的考点

JVM 中,加载类,需要用到一组特殊的模块:类加载器

在 JVM 中,内置了三个类加载器

BootStrap ClassLoader 负责加载 Java标准库中的类

Extension ClassLoader 负责加载一些非标准的类,但是 是sun/Oracle 扩展的库的类

Application ClassLoader 负责加载项目中自己写的类,以及第三方库 中的类

当具体加载一个类的时候,其过程如下:

双亲?上面明明就一个 null ,哪里来的双亲?

这个就是个翻译问题,parent 表示双亲中的一个,我们翻译为双亲罢了。

这个双亲委派模型也是可以打破的,自己实现的类加载器,是否遵守上述规则,看个人设计。

JVM 中的垃圾回收策略

垃圾回收机制 也可以叫 GC 回收机制

这个可以帮助程序员自动释放内存

在 C语言中,malloc 需要手动 free,否则就会出现内存泄漏(光申请,不释放,内存用完了,程序也就完了)

Java 等后继编程语言,都采用了 GC 来解决上述问题,能够有限减少内存泄漏出现的概率。

内存的泄露是个比较纠结的问题:

申请的时机是明确的 => 使用到了必须申请;

释放的时机是模糊的 => 彻底不使用了才能释放

啥叫彻底不使用了:没有引用指向它

JVM 中的内存有好几个区,我们要释放的就是 (new 出来的)

我们也来随手聊聊其他区的释放:

  1. 程序计数器,就是个单纯存地址的整数,不要了就直接随着线程一起销毁。
  2. 栈,也是随着线程一起销毁的,方法调用完毕,方法的局部变量自然随着出栈的操作销毁了。
  3. 元数据区/方法区,村的类对象,很少会“卸载”。
  4. 堆,是GC的主要目标,GC也是以对象为单位进行释放的(说是释放内存,本质就是释放对象)

GC 中主要分为两个阶段:

  1. 找,谁是垃圾
  2. 释放,将垃圾对象释放掉

上述两个阶段,主要是了解清楚垃圾回收的算法,这些算法 并不代表 JVM真实的实现方法,现在的JVM 在这些基本算法之上又做出了很多细节的调整和优化。

找,确认垃圾

什么是垃圾,后续不会再使用到的,就是垃圾,Java中使用一个对象,只能通过引用,如果一个对象没了引用就可以确定它是垃圾了。

Java中只是单纯的通过引用没有指向这个操作,来判定垃圾。

单纯的通过引用可能会照成 垃圾释放不及时,但这都是小事;就怕释放太快,将还有引用的对象释放掉,这是大事。

具体来说说,Java怎么样直到一个对象是否还有引用呢?

1. 引用计数

给对象安排一个而外的的空间,保存一个整数,表示该对象有几个引用

(事实上,Java并没有采用这个方案,反倒是 python和PHP采用了)

借助图片来看看:

随着引用的增加,计数器就增加,引用的销毁计数器就销毁。

当,计数器为0,就认为没有了引用,于是就是垃圾了

每个计数器都开辟一个空间, 上述之开辟一个空间,是因为都是同一类型 Test,我要是有其他类型,例如Cat 那么还需要开辟一个空间。

因此,这种方法会出现两个问题:

  1. 对象类型过多,那么就会非常浪费空间
  2. 存在循环引用的情况,那么就会导致引用计数的判定的逻辑出错

假设,由上述这个栗子,此时,a 和 b 销毁了,这个时候,两个对象的引用计数各自减 1 :

此时,这两个引用计数还在相互引用,并不是 0 ,所以不能作为垃圾,但是这个两个对象都不可以再使用 。

2. 可达行分析

可达行分析将对象之间的引用关系,理解成了一棵树形结构,从一些特殊的起点出发,进行遍历,只要能遍历访问到的对象,就是“可达的“,其余”不可达“ 的就作为垃圾处理掉。

 此时,通过 root 这个引用,就可以访问到整个树的任意结点,通过上述方式,root就能引用到所有结点。

如果这里的 e 突然为 null ,g是否就是不可达呢?

不对,既是e 为 null 了,还会存在其他指向指向g,g 任然是可达的。

可达性分析关键要点,进行上述遍历,需要有”起点“

  1. 栈上的局部变量(每个栈的每个局部变量,都是起点)
  2. 常量池中的引用的对象
  3. 方法区中,静态成员引用的对象

可达性分析,总的来说,就是从所有的 gcroots 的起点触发,看看该对象里又通过引用能访问哪些对象,顺腾摸瓜,把所有可以访问的对象都遍历一遍(遍历的同时把对象标记成为”可达“)

可达性分析,克服了引用计数的两个缺点,但是它也存在自身的问题

  1. 这个树有高度,搜易搜索会消耗更多的时间,因此某个对象成为垃圾,不一定能够第一时间发现,扫描会消耗时间
  2. 再进行可达性分析的时候,要顺着树进行搜索,一旦这个过程中,当前代码出现了变化,就更复杂了

因此,为了更准确的完成这个 扫描过程,还需要其他业务暂停工作(STW问题)

所谓的 STW 也就是 stop the world ; 这个是可达性分析最最被诟病的问题,Java发展了这么多年,垃圾回收这个也在不断的更新优化,STW 这个问题,现在以及能够比较好的应对了,但是并不能完全消除,已经可以让 STW 的时间大大缩短了。

上述已经解决了理论的找垃圾了,接下来就是释放垃圾

释放”垃圾“对象

三种典型的 策略

1. 标记清除

"标记-清除"算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段 : 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象:

如图所示, 画了勾的就是我们需要释放的;

我们这块空间很难被重复利用,我们申请的”整块的连续的空间“现在这里空闲的空间都是离散的,有自己独立的空间。

总的空闲的空间 可能超过 1个 G,但是你想申请 500M 都不一定能够申请到(因为他们都是散的)。

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

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

2. 复制算法

我们把整个内存空间,分为两段,一次只使用一半:

还是一样,花了勾的属于垃圾;

我们将不属于垃圾的复制到另一半空间中去,剩下的全部释放掉:

 

复制算法虽然解决了内存碎片的问题,但是也存在相关的缺点:

  1. 内存利用率低
  2. 如果当前的对象大部分都是要保留的,垃圾很少,此时复制成本很高

3. 标记整理

类似于顺序表删除中间的元素:

我们将 有效元素往前搬

随后将搬运处的空间释放 

该算法虽然解决了内存碎片问题,但还存在一些问题:

  1.  搬运的开销比较大

 事实上, JVM 的实现思路,是上述集中的结合,针对不同情况下取长补短:

分代算法

基本思想:给每个对象设置”年龄“ ,这个概念,描述对象存在多久了,如果是一个对象刚刚诞生,认为是 0 岁,经过每一轮扫描(可达性分析),没被标记成垃圾,就长大一岁

通过描述年龄来表示对象存活时间 

算法执行步骤:

  1. 1. 新创建的对象,放到伊甸区,当垃圾回收扫描到伊甸区之后,绝大部分对象都会在第一轮 GC 中被干掉
  2. 如果伊甸区的对象,熬过第一轮,通过复制算法,拷贝到生存区,生存去分为两半,一次只是用一半,垃圾回收扫描伊甸区的对象,也是发现垃圾就淘汰,不是垃圾就复制到另一半
  3. 当这个对象熬过多轮,年龄增长到一定程度,通过复制算法拷贝到老年代
  4. 进入老年代,年龄都挺大了,再消亡的概率小于前面的新生代,针对老年代的 gc 扫描概率小很多,如果发现老年代某个对象是垃圾,使用标记整理的方式清楚
  5. 特殊情况:某个对象很大,直接存放到老年代,因为这个对象搬运起来的开销很大,不适合多次搬运

这些都只是基本思想,具体的实现再这些基础之上,还做出了很多改进和优化;

Java的版本在变,垃圾回收器也会不断变化。

本篇的八股就到这里,校招应该不会考那么多八股文,一定要把这些背下来!!!!!

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

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

相关文章

Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面

Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面 虽然说IE6除了部分要求苛刻的需求以外已经被可以不考虑了,但是WIN7自带的浏览器IE8还是需要支持的。 本文这个方法主要的优点,个人觉得就是准备少,不需要上网寻找大量的图片做素材&…

2003-Can‘t connect to Mysql server on ‘xxx‘ (10060 “Unknown error“)

Navicat连接 阿里云 服务器MySQL5.7数据库报错 解决办法: 进入数据库执行以下sql 1.允许root用户远程连接 GRANT ALL PRIVILEGES ON *.* TO root% IDENTIFIED BY 数据库密码 WITH GRANT OPTION; 2.刷新权限 FLUSH PRIVILEGES;3.执行quit退出数据库 quit; 4.…

PVE虚拟化平台之安装Ubuntu Desktop系统

PVE虚拟化平台之安装Ubuntu Desktop系统 一、Ubuntu介绍1.1 Ubuntu简介1.2 Ubuntu版本1.3 ubuntu命名规则 二、上传镜像到PVE2.1 检查PVE环境2.2 上传镜像到PVE 三、新建虚拟机3.1 设置虚拟机名称3.2 操作系统设置3.3 系统设置3.4 磁盘设置3.5 cpu设置3.6 内存设置3.7 网络设置…

sprinboot摄影跟拍预定管理系统

摄影跟拍预定管理方面的任务繁琐,以至于每年都在摄影跟拍预定管理这方面投入较多的精力却效果甚微,摄影跟拍预定管理系统的目标就是为了能够缓解摄影跟拍预定管理工作方面面临的压力,让摄影跟拍预定管理方面的工作变得更加高效准确。 本项目在开发和设计过程中涉及到原理和技术…

怎么给pdf文件加密?pdf文档如何加密

在数字化时代,保护个人和机密信息的重要性越来越受到关注。PDF(Portable Document Format)是一种广泛使用的文件格式,用于共享和存储各种类型的文档。然而,由于其易于编辑和复制的特性,保护PDF文件中的敏感…

springboot书籍学习平台

本项目在开发和设计过程中涉及到原理和技术有: B/S、java技术和MySQL数据库等等.

送呆萌的她一个皮卡丘(Python实现)

目录 1 呆萌的她 2 思维需要革新 3 送她的一个漂亮皮卡丘 4 Python完整代码奉上 1 呆萌的她 又是一季春风暖阳下, 你是一湾一湾羞涩的春波。 静静感受着, 你垂下的枝膊 在我的脸上轻轻抚摸 一对春燕,低低掠过 涟漪乍起,是你浅浅的笑窝...... 2 思维需…

[Error] invalid preprocessing directive #inclued问题解决

错误代码 报错内容 [Error] invalid preprocessing directive #inclued 错误原因 #inclued写错了应该写成#include

elementUI 非表单格式的校验

在普通表单中对输入框、选择框都有校验案例。 但是在自定义非空中如何进行校验官网并没有说明 关键代码 clearValidate 方法清除校验 this.$refs.formValue.clearValidate(signinimg) 使用案例 <template><div class"stylebg"><Tabs icons"el-…

[QT编程系列-6]:C++图形用户界面编程,QT框架快速入门培训 - 3- QT窗体设计 - 自定义菜单栏

目录 3. QT窗体设计 3.1 自定义菜单 3.1.1 设计目标​编辑 3.1.2 创建过程​编辑 3. QT窗体设计 3.1 自定义菜单 3.1.1 设计目标 3.1.2 创建过程 在Qt中&#xff0c;Windows窗口和Widget窗口是两种不同的窗口类型&#xff0c;它们在创建方式、功能和用途上有所区别。 创建…

【React】- 组件生命周期连续渲染两次问题

最近在整理生命周期相关的知识内容&#xff0c;然后发现一个奇怪的现象&#xff0c;即组件的生命周期会运行2次&#xff01;经过确认不是代码问题&#xff0c;于是开始找度娘&#xff0c;终于找到其原因-React中的严格模式&#xff0c;在这里记录一下 一、问题重现 如图所示&a…

速通matplotlib库

速通matplotlib库 前言 ​ 最近在复习之前学习过的知识点&#xff0c;因此想到把学过的总结一下&#xff0c;方便后面再次复习&#xff0c;所以有了这个系列。 说明 ​ 由于标题写的是“速通”&#xff0c;因此我的想法是可以让大家看完这篇文章&#xff0c;可以上手matplotlib…

WebSocket从入门到精通

WebSocket 是什么&#xff1f; WebSocket是HTML5规范提出的一种协议&#xff1b;目前除了IE浏览器&#xff0c;其他浏览器都基本支持。它是一种协议&#xff0c;万变不离其宗&#xff0c;也是基于TCP协议的&#xff0c;和HTTP协议是并存的两种协议。HTML5 Web Sockets规范定义了…

快速排序算法解析

快速排序算法解析 快速排序法 快速排序是一种经典的分治算法&#xff0c;它采用递归的方式将待排序数组分割成较小的子数组&#xff0c;然后通过基准元素的选择和元素的交换来达到排序的目的。 快速排序的核心思想是通过选取基准元素&#xff0c;并将其他元素与基准元素进行比…

如何在工作中保持稳定的情绪? – 个人看法和建议

文章目录 每日一句正能量前言情绪波动的原因建议情绪调节技巧和策略身处逆境&#xff0c;如何治愈自己 后记 每日一句正能量 所谓的快乐&#xff0c;是指身体的无痛苦和灵魂的无纷扰。——伊壁鸠鲁 前言 在工作中保持稳定的情绪对于每一个职场人来说都是非常重要的。随着工作压…

什么是vlan为什么要划分vlan

首先了解交换机的数据的转发方式。&#xff0c;有两种情况会使用广播方式进行数据分发&#xff0c;第一种就是目的地址是全F的&#xff08;FF-FF-FF-FF-FF-FF&#xff09;&#xff0c;第二种是位置的单播帧&#xff0c;这样的情况下会带来什么杨的问题呢。 场景引入&#xff1…

Vue2 使用vcolorpicker报错问题解决

1、安装步骤省略 2、全局引入步骤省略 3、引入后注册组件会报如下错误 Failed to mount component: template or render function not defined. 按照文档给的例子直接这样写 不用注册组件就不报错了~

菜鸡shader:L10 帧序列动画和极坐标的使用

文章目录 帧序列动画代码最后效果 极坐标代码最后效果 顶点色 这次笔记就直接放最后的效果了&#xff0c;因为课程上老师也没有给代码图片或是什么技巧说明。 下图左边是帧序列动画(鬼火)&#xff0c;右边是极坐标。 帧序列动画 帧序列的原理是对一张有规律行列排序的序列帧…

35. 反转链表

目录 链接&#xff1a; 题目&#xff1a; 思路&#xff1a; 代码&#xff1a; 图片&#xff1a; 链接&#xff1a; 原题链接 题目&#xff1a; 定义一个函数&#xff0c;输入一个链表的头结点&#xff0c;反转该链表并输出反转后链表的头结点。 思考题&#xff1a; 请同时…

逻辑(css)-背景网格制作(linear-gradient)

目录 linear-gradient需求实现 linear-gradient 语法&#xff1a;linear-gradient([direction], color-stop1, color-stop2, ...) 第一个参数为(可选)方向参数&#xff0c;可以是度数也可以是方位名词,方向与读书的关系如下&#xff1a; 角度方位文字说明示例0degto top从下…