当前位置: 首页 > article >正文

【JVM】关于JVM的内部原理你到底了解多少(八股文面经知识点)

前言

🌟🌟本期讲解关于HTTPS的重要的加密原理~~~

🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客

🔥 你的点赞就是小编不断更新的最大动力                                       

🎆那么废话不多说直接开整吧~~

 

目录

​编辑

📚️1.认识JVM

📚️2.JVM的内部解析

2.1JVM的内存区域划分

 1.堆区(只有一份)

 2.栈区(可以有N份)

3.程序计数器

4.元数据区

2.2JVM类加载机制

1.加载

2.验证

3.准备

 4.解析

5.初始化

2.3垃圾回收机制(GC)

1.GC的前言

2.回收的内存

3.识别垃圾

引用计数

可达性分析

4. 内存空间的释放

标记清除

复制算法

标记整理

分代回收

📚️3.总结


 

📚️1.认识JVM

在开始认识JVM的时候,这个本就不是我们工作中所运用得到的,那么我们在开始学习JAVA的时候,我们就听说过JVM,即如下三个:

jdk:Java开发工具包

jre:Java运行时环境

jvm:Java虚拟机

并且还了解过:编译型语言和解释型语言,而这里我们所学习的Java就是一个“半编译型”与“半解释型”的语言;

所以我们这里Java这样实现的目的就是为了跨平台,例如:C++编会编译成二进制的机器指令,而不同的CPU的机器指令是不一样的

具体的实现过程:

首先通过java.c将代码.java文件转化为.class字节码文件,然后再具体的系统平台上执行的时候,通过jvm将上述的字节码文件转化为CPU能够识别的机器语言

总结:所谓的jvm就是在Java编程中实现跨平台,充当中间人翻译官的角色

📚️2.JVM的内部解析

2.1JVM的内存区域划分

这里的jvm其实也是一个进程,那么我们之前在学习了解的计算机的工作原理:【后端开发】JavaEE初阶——计算机是如何工作的???-CSDN博客

了解到进程是需要操作系统分配内存空间的,这里支持了Java程序的执行,我们在Java程序中变量分配到的内存资源,其实就是jvm从操作系统内分配到的内存资源;而不同功能会对应分割不同的区域,这就是“内存的区域划分

这里的内存内部如下:

 1.堆区(只有一份)

即在代码中new出来的对象都是存储在这个区域里,对象中持有的非静态成员变量也是在这个堆里;

堆⾥⾯分为两个区域:新⽣代和⽼⽣代,新⽣代放新建的对象,当经过⼀定 GC 次数之后还存活的对象 会放⼊⽼⽣代。新⽣代还有 3 个区域:⼀个 Endn + 两个 Survivor(S0/S1),后面会将

 2.栈区(可以有N份)

本地方法栈/虚拟机栈,描述了方法的调用的关系,和局部变量

补充:

动态链接:指向运⾏时常量池的⽅法引⽤。
⽅法返回地址:PC 寄存器的地址
3.程序计数器

占有比较小的空间,专门又来存储下一条要执行的java程序指令的地址

4.元数据区

元数据区是一个计算机常见的概念,这里主要指定就是辅助性质的,描述性质的属性;

例如:在硬盘上,不仅仅要存储文件的数据信息,还有其他的辅助属性:“文件的大小,文件的存储位置,文件的拥有者,文件的访问权限”统称为元数据

那么此时就有如下的面试题:

class Test{
    private int n;
    private static int m;
 
}

public static void mian(String[] arg){
    Test t=new Test();
}

此时分析:n,t,m在内存区域的哪里???

n:由于n是一个成员变量,那么此时就是在堆上面的

t:是一个引用类型的局部变量,那么此时就是在栈上面

m:由于m是static修饰的变量,叫类对象,那么此时就是在元数据区里

被static修饰的变量叫类属性,修饰的方法叫类方法

非static修饰的变量叫实例属性,非修饰的方法叫实例方法 

所谓的类对象:就是Test.class,在.class文件加载到内存上面的时候,就会将信息用对象来表示,这个对象就是 类对象,包含了很多的属性

2.2JVM类加载机制

类加载:所谓的加载其实指定就是Java进程运行吧.class文件从硬盘读取到内存并进行一系列的校验解析的过程;

这里的加载的步骤主要分为5步

1.加载

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

重点(双亲委派模型)

所谓的双亲委派模型就是描述了如何查找.class文件的策略;

jvm在进行类加载的操作中,有个专门的模块称之为“类加载器”,这里的的类加载器默认是3个;

BootstrapClassLoader(负责查找目标库的目录)

ExtensionClassLoader(负责查找扩展库的目录)

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

注意:上述的三个类加载器的关系是父子关系,但是这里的父子关系指的就是二叉树的指针关系

具体的工作的流程:

ApplicationClassLoader入口进入,但是自己不会搜索自己负责的目录,而是会发给ExtensionClassLoader,同上也不会搜索,继续给父亲BootstrapClassLoader,然后由于没有父亲,那么就会开始目标库目录,如果找到了就直接进入打开文件,若没有找到,那么给ExtensionClassLoader开始搜索扩展库目录,同上找到了打开文件,没有继续给儿子;

最后没有找到会继续在孩子这一辈的类加载器搜索,但是默认 ApplicationClassLoader是没有孩子的,那么就会类加载失败,抛出ClassNotFountExecption

具体的模型如下:

优点: 

可以有效的避免自己写的类不小心和标准库的名字重复后,导致标准库的类功能失效

2.验证

当前需要知道保证读到的文件的内容是合法的.class文件内容

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

解释:

u4:四字节无符号整数

u2:两个字节无符号整数

magic:魔幻数字,表示当前二进制的文件的格式是那种类型

field_info:另一个结构体

3.准备

 给类对象申请内存的空间,此时申请到的内存空间,里面默认值就全部是0(这个阶段的类对象中的静态成员变量的值就相当于是0)

 4.解析

主要针对的就是类中的字符串常量进行处理

例如:

class Test{
  private String s="Hello";
}

 那么此时的.class文件就会包含这个hello,那么此时的存储的情况就是如下的:

解释:

我们可以知道这里的s变量存储了hello的字符串常量的地址,那么在文件中不存在地址这个概念的,那么这里的红线就是偏移量来代替了地址; 

后面.class文件加载到内存中的时候,就可以直接替换成真实的“hello”的地址了;

5.初始化

就是针对这个类对象完成后续的初始化;还要执行静态代码块的逻辑,或者触发父类的加载

2.3垃圾回收机制(GC)

1.GC的前言

在C语言中,动态管理内存,那么就是molloc申请内存,free释放内存,那么此时申请的内存是跟随整个进程的,这一点对于服务器是非常不友好的,如果申请了内存不释放就会导致内存泄漏的问题,但是free需要手动调用,就会又不确定的因素

所以在java中就引入了自动释放内存的机制就是:GC

但是这里的C语言C++追求的是极致性能,所以就没有引入,因为垃圾回收有一个重要的问题

(stop the world)STW问题

当触发垃圾回收机制的时候,很有可能当前程序的其他的业务逻辑就会暂停;但是现在的STW的时间控制在1ms以内了,也是可以接受的

2.回收的内存

说到垃圾回收机制,我们知道在内存中的区域进行了分区的,那么垃圾回收,回收的是哪一部分的“垃圾”呢?

程序计数器:不需要GC

栈:不需要GC,局部变量在代码执行结束之后会自动的销毁,与垃圾回收没有关系

元数据区:不需要GC,这里只涉及类加载,很少涉及类卸载

堆:这里就是GC的主要战场

在垃圾回收中,跟准确的说法就是“回收对象” 

在堆中,内存的回收具体是如下所示的:

解释:

中间的那块,代表就是一半不回收,一半回收,一般就是不进行回收,叫做“骑强派” 这里包含的空闲的内存就不用管了

3.识别垃圾

所谓的垃圾识别就是判断出当前这个对象是否还继续进行使用,如果没有继续进行使用,那么这个对象就可以进行回收了;

具体举例:

void func{
   Test t=new Test();

   t.func1;
}

那么此时的情况就是如下的:

解释:

那么我们可以知道,局部变量的生命的周期是很短的,那么接下来在执行到“}”的时候,这里就被释放了,没有指向这个地址,那么这个堆里的对象就是垃圾了;

那么此时涉及下面的代码,那么就比较复杂了:

Test t1=new Test();
Test t2=t1;
Test t3=t2;

 那么就引入了一个新的概念,来解决这个问题复杂的情况;

引用计数

这种方法在JVM中并没有进行使用,那么此时主要是用在其他的主流的语言上面的(Python,PHP)

具体方法:

就是给对象安排一个额外的空间,来保存当前这个对象有几个引用

具体实例如下:

Test a=new Test();
Test b=a;
a=null;
b=null;

对应的图示:

解释:

此时我们可以看到,两个引用指向这个对象,那么申请空间的保存的就是2,两个引用,如果栈的局部变量被释放后,或者为“null”那么这个里面的引用就是0,此时就可以进行释放了;

缺点:

问题1:消耗额外的内存空间:如果对每个对象安排一个计数器,如果程序中的对象太多了,那么此时就会造成额外的空间消耗

问题2:引用计数器可能会导致“循环引用的问题”,应用计数就无法进行正常的工作了;

 什么是循环引用,具体代码如下:

class Test{
  Test t;
}

Test a=new Test();
Test B=new Test();

a.t=b;
b.t=a;
a=null;
b=mull;

那么具体的图示就是如下所示的:

解释:

那么此时就会发现,由于a.t=b,这个操作,导致在堆里的new的对象中多出来了一个地址的指向,那么此时就会发现,计数器中的值就是2,如果a,b都为null了,那么就会出现计数器为1,但是没有任何指向对象的引用; 

我们可以知道此时就相当于是死锁的情况了,两个对象不能调用,但是这两个对象却不是垃圾;那么此时就引入了另一个垃圾识别的分析

可达性分析

这里的可达性其实就类似于二叉树的遍历,若其中一个点被断了,那么后面的子结点就是不可达的,那么此时后面的就是垃圾,因该被回收了;

具体的情况图示如下:

解释:

如果此时的结点就都是可达的,若其中b=null,那么包括b和它的子结点都是不可达的,那么就是垃圾,就应该被回收了; 

4. 内存空间的释放

在上述的垃圾的识别标记后,那么此时就应该执行内存空间的释放的操作了,具体的内存释放的操作即如下三种的操作的方式

标记清除

做法:将标记的垃圾直接进行清除(直接释放掉)

具体的图示如下:

那么此时的黑色部分就是垃圾被清除的部分内存;

问题:

此时就造成了内存的碎片问题,因为在申请内存空间的时候是申请的一段连续的内存空间,此时就会造成内存总空间够,但是申请不了,因为内存不是连续的是断层的 

复制算法

做法:将不是垃圾的内存对象复制到另一半内存中,然后将复制前的内存空间连垃圾一起释放掉

那么此时的图示就是如下的:

解释:

那么此时就是将不是垃圾的内存对象复制到另一边,然后将左边区域直接全部释放掉,此时就直接规避了内存的碎片化的问题

 缺点:

缺点显而易见,就是总共可用的内存直接性的下降了,并且复制的对象很多的时候,复制的开销也会变大

标记整理

做法:类似与顺序表中的删除中间元素的操作(搬运),就是将非垃圾的内存对象把垃圾给覆盖掉

具体的实例如下:

解释:

此时就将非垃圾的内存对象给往前面进行搬运,这样就会解决内存碎片化问题,以及内存总空间减少的问题,但是会涉及到内存搬运的开销 

分代回收

做法:就是依据不同类的对象,采取不同的方式

此时就是jvm中有专门负责周期性的扫描,一个对象被扫描一次可达性满足,那么就会年龄+1;jvm机会根据年龄的差异把堆内存的空间分为两个部分,“新生代/老年代” 

具体的图示如下:

解释:

 new出来的新的对象就存储在伊甸区,经过扫描后,大部分的对象都直接GG,然后幸存下来的就在生存区,再次进行伊甸区和生存区的扫描,若在伊甸区就和上面一样,若是生存区,那么就拷贝到另一个区域,反复如此(每次扫描年龄+1)若若干轮的GC任然存在,那么就认为这个对象的生存的周期比较长,那么就会拷贝在老年区域了;(此时的扫描的频率就会下降)若老年区的对象变成垃圾了,那就会通过标记整理的方式进行释放内存;

ok以上就是垃圾回收的具体方法步骤,当然这里还涉及到“垃圾收集器”,小伙伴们可以去了解一下CMS,G1,ZGC这三个

📚️3.总结

💬💬本期小编主要总结了JVM面经的常考题,主要讲解了JVM的内部实现原理包括内存区域的划分,类加载机制,垃圾的回收机制(GC)从什么是垃圾,如何进行识别,到最后的内存对象的释放,都做了比较详细的介绍~~~

🌅🌅🌅~~~~最后希望与诸君共勉,共同进步!!!


💪💪💪以上就是本期内容了, 感兴趣的话,就关注小编吧。

                 😊😊  期待你的关注~~~


http://www.kler.cn/a/393068.html

相关文章:

  • Java面向对象编程进阶之包装类
  • 若依笔记(八):芋道的Docker容器化部署
  • 【Golang】Channel的ring buffer实现
  • ❤React-React 组件基础(类组件)
  • 大语言模型:解锁自然语言处理的无限可能
  • Linux探秘坊-------1.系统核心的低语:基础指令的奥秘解析(1)
  • 推荐一款好用的postman替代工具2024
  • php 字符串与变量
  • web浏览器环境下使用window.open()打开PDF文件不是预览,而是下载文件?
  • 第四十五章 Vue之Vuex模块化创建(module)
  • 十大经典排序算法-希尔排序与归并排序
  • Ubuntu 安装和使用 1Panel
  • 电工电子原理笔记
  • 应用程序部署(IIS的相关使用,sql server的相关使用)
  • Java项目实战II基于微信阅读网站小程序的设计与实现(开发文档+数据库+源码)
  • 【VLANPWN】一款针对VLAN的安全研究和渗透测试工具
  • 谷歌邮箱域名设置指南:轻松管理电子邮件!
  • 使用JS实现文件流转换excel?
  • 【深度解析】CSS工程化全攻略(1)
  • AUTOSAR CP Ethernet State Manager(EthSM)规范的主要功能以及工作原理导读
  • 网络服务综合项目-博客
  • 武汉EI学术会议一览表
  • HBase理论_背景特点及数据单元及与Hive对比
  • 使用 md-editor-v3 开发自定义 Markdown 编辑器组件
  • MySQL技巧之跨服务器数据查询:基础篇-删除语句如何写
  • CSS教程(三)- CSS 三大特性