深度剖析JVM三个面试常考知识点

news/2024/11/25 17:47:22/

目录

🐳今日良言:只要你足够努力,生命都会庇佑你

🐇一、JVM内存区域划分

🐇二、类加载过程

🐇三、垃圾回收机制(GC)


🐳今日良言:只要你足够努力,生命都会庇佑你

🐇一、JVM内存区域划分

先来了解一下什么是JVM :

JVM 是 Java Virtual Machine 的简称,意为 Java虚拟机。

虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。

常见的虚拟机:JVM、VMwave、Virtual Box。

JVM 和其他两个虚拟机的区别:

1. VMwave与VirtualBox是通过软件模拟物理CPU的指令集,物理系统中会有很多的寄存器;

2. JVM则是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进

行了裁剪。

JVM 是一台被定制过的现实当中不存在的计算机。

对jvm有了简单的认识以后,接下来重点介绍一下 JVM 相关的三个面试常考知识点.

JVM内存区域划分:

JVM 是一个应用程序,一个 JVM 就是一个 java 进程,JVM 在启动的时候,会从操作系统这里申请到一整个很大的内存区域.

JVM 就根据需要将整个空间分成5个部分,各个部分都有不同的作用和功能.如下图:

 本地方法栈:

native 表示是 JVM 内部的 C++ 代码.

本地方法栈就是给调用native方法(JVM内部方法)准备的栈空间,存储的是native方法之间的调用关系.

程序计数器:

记录当前线程执行到哪个指令,每个线程有一份.

虚拟机栈:

给 java 代码使用的栈空间.存储的是方法之间的调用关系.

整个栈内部空间,可以认为是包含很多个元素(每个元素表示一个方法),每个元素又叫做"栈帧",每个栈帧里面包含了这个方法的入口地址、局部变量、参数、返回地址等.每个线程有一份.

堆:

整个 JVM 空间最大的区域, new 出来的对象就是在堆上,类的成员变量也是在堆上.

元数据区(又叫方法区):

存储的是类对象、静态成员、常量池等.

这里相关的面试题最主要的考点是,给一段代码,问某个变量在哪个区域上:

1.局部变量在虚拟机栈上.

2.普通成员变量在堆上.

3.静态成员变量在元数据区/方法区.


🐇二、类加载过程

介绍完了 JVM 的内存区域划分,再介绍第二个知识点:类加载过程.

类加载过程就是将一个 .class 文件(字节码文件, .java文件通过javac(java编译器)得到),从文件(硬盘)加载到内存(元数据区)的过程.

主要有以下流程:

 加载

通过双亲委派模型找到 .clas 文件,打开文件,将文件内容读到内存中.

验证

检查 .class 文件的格式对不对. .class 文件是一个二进制文件.

准备

给类对象分配内存空间(在元数据区占个位置),将静态成员设置成0值.

解析

初始化字符串,将符号引用转为直接引用.

初始化

调用构造方法,进行成员初始化,执行静态代码块,代码块,加载父类等...

发生类加载的时机:

1)构造类的实例.

2)调用类的静态方法/使用类的静态属性.

3)加载子类之前先加载其父类. 

 双亲委派模型:

双亲委派模型描述的就是上述加载阶段找 .class 文件的过程.

JVM 默认提供了三个类加载器.

1) BootstrapClassLoader

    负责加载java标准库中的类.(java规范,无论是哪种 JVM 的实现,都会提供的一样的类)

2)ExtendsionClassLoader

    负责加载 JVM 扩展库中的类.(java规范之外,由实现 JVM 的厂商提供的额外的类)

3)ApplicationClassLoader

    负责加载用户项目/用户提供的第三方库 中的类.

上述这三个类存在"父子关系",并不是继承中的父类和子类,而是每个类加载器中有一个parent 属性,指向自己的父亲---类加载器.

上述类加载器配合流程如下:

首先,加载一个类从ApplicationClassLoader开始,但是 ApplicationClassLoader 并不是真的加载,而是交给自己的父类加载,于是 ExtendsionClassLoader 开始加载, 但是也不是真的加载,而是交给自己的父类加载器BootstrapClassLoade,BootstrapClassLoader发现自己的parent属性为null,于是自己开始加载,搜索自己负责的标准库目录相关的类,找到了就加载,找不到就交给自己的子类加载器,于是ExtendsionClassLoader开始加载,搜索自己负责的扩展库目录相关的类,找到了就加载,找不到就交给自己的子类加载器,于是ApplicationClassLoader开始加载,搜索用户项目相关的类,找到了就加载.找不到就交给自己的子类加载器,但是此时ApplicationClassLoader的子类加载器为空,于是报类找不到这样的异常.

大致流程图如下:

为什么要有上述顺序呢?

上述这套顺序其实是出自于 JVM 实现代码的逻辑.

这段代码大概是类似于"递归"的方式写的.

这个顺序最主要的目的就是为了让Bootstrap能够先加载,Application最后加载,这就可以避免因为用户创建了一些奇怪的类,引起不必要的bug.

比如说:一个用户在自己的代码中写了一个 java.lang.String 这个类,按照上面的类加载流程,此时 JVM 加载的还是标准库中的类,不会加载到用户自己写的这个类,这样就能保证,即使出现上述情况,也不会让 JVM 内部的代码混乱,最多就是让用户自己写的这个类不生效. 


🐇三、垃圾回收机制(GC)

接下来介绍一下最后一个知识点:垃圾回收机制.

首先理解一下什么是"垃圾"?

Java中的垃圾指的是:不再使用的内存.

垃圾回收就是将不再使用的内存自动释放.

GC是最主流的解决垃圾回收的一种方式.

GC优点:非常省心,让程序员写代码简单点,不容易出错.

GC缺点:需要消耗额外的系统资源,也有额外的性能开销.

另外,GC这里还有一个比较关键的问题:STW(Stop The World):

如果有时候,内存中的垃圾太多了,此时触发一次GC操作,开销可能非常大,大到可能就把系统资源吃了很多,另一方面,GC回收垃圾的时候可能会涉及到一些 锁操作,导致业务代码无法正常执行,这样的卡顿,极端情况下,可能出现几十毫秒甚至上百毫秒.

GC主要是针对 堆 进行释放的.

GC是以"对象"为基本单位进行回收的!!(不是字节).

GC回收的是整个对象都不再使用的情况.

这样的设定就是为了"简单".

GC实际工作过程:

1.找到垃圾/判定垃圾

2.进行垃圾释放

1.找到垃圾/判定垃圾

  java中通过引用来使用一个对象,如果没有引用指向该对象了,说明这个对象就不再使用了.

  具体如何知道对象是否有引用指向呢? 

  两种典型实现:

  1).引用计数

       给每个对象分配一个计数器,只要有引用指向这个对象就让计数器+1,指向这个对象的引用销毁了,就让这个计数器-1.

         

{Test t = new Test();   // Test 对象的引用计数 1Test t2 = t;           // Test 对象的引用计数 2Test t3 = t;           // Test 对象的引用计数 3
}

        这是一种简单有效的办法,但是会带来一定问题:

         内存空间浪费的多(利用率低):每个对象都要分配一个计数器,如果按4个字节算,代码中

         的对象非常少,无所谓,如果对象特别多,占用的额外空间就会很多,尤其是每个对象比较

         小的情况.  

         如果一个对象1k,此时多4个字节无所谓.

         如果一个对象4个字节,此时多4个字节,体积扩大一倍.

         还存在循环引用的问题:

         

class Test {Test t = null;
}Test a = new Test();  // 1号对象,引用计数是1Test b = new Test();  // 2号对象,引用计数是1a.t = b;              // a.t也指向2号对象,2号对象引用计数是 2 了.b.t = a;              // b.t也指向1号对象,1号对象引用计数是 2 了.

    接下来,如果a和b引用销毁了,1号对象和2号对象的引用计数都-1,但是结果都是1,不是0,

    虽然不是0,不能释放资源,但是这两个对象都无法访问到了.

    2).可达性分析

    java中的对象都是通过引用来访问的,通常是一个引用指向一个对象,这个对象里的成员又 

    指向其它对象.(比如二叉树的节点)

    可达性分析就是把组织这些所有对象的结构视为是树,就从根节点开始遍历树,所有能访

    问到的节点标记为"可达"(不能访问的就是不可达).

    JVM 自己捏着一个所有对象的名单.通过上述遍历,把可达的标记出来,不可达的就是垃圾

    进行释放.

    可达性分析需要进行类似于"树遍历",这个操作相比于引用计数来说是要慢一些的,但是速度

    慢是没关系的,上述可达性分析遍历操作,并不需要一直执行,只需要每隔一段时间,分析一遍

    就可以了.

    进行可达性分析遍历的起点,称为GCroots,可以作为GCroots的有:

    栈上的局部变量,常量池中的对象,静态成员变量.

    一个代码中,有很多个这样的起点,把每个对象都往下遍历一遍就完成了一次扫描过程.

2.进行垃圾释放

   主要是三种基本做法.

   1)标记清除

      简单粗暴,直接标记垃圾,然后进行释放,但是会带来"内存碎片问题",被释放的空间是零散

      的,不是连续的.

      

     申请空间要求的是连续空间,上述总的空闲可能很大,但是具体的每一个空间又很小,可能

     导致申请大一点的内存就会失败.比如总的空闲内存是10k,分成1k一个,此时申请2k的空

     间就会失败.

   2)复制算法

      解决了"内存碎片问题".将一块大的内存空间分成两部分,用一半丢一半.

      复制算法就是把不是垃圾的对象复制到另一半,然后将整个空间删除.每次触发复制算法,

      都是向另一半进行拷贝.

      

 

        复制算法的缺点:1.空间利用率低(用一半丢一半) 2.(垃圾少的话,复制成本比较大)

3)标记整理

     解决了复制算法的缺点.

     类似于顺序表删除中间元素,会有元素搬运的操作.

      保证了空间利用率,同时也解决了"内存碎片问题".但是这种做法,效率也不高,如果要搬运

      的空间比较大,此时开销也很大.

   

基于上述基本策略,搞了一个复合策略"分代回收"

分代是怎么分的呢?

  分代是基于一个经验规律:如果一个东西,存在的时间比较长了,那么大概率还会持续长时间的存在下去.(要没早就没了)

  上述规律,对于 java 中的对象也是有效的, java 的对象要么生命周期特别长,要么生命周期特别短,根据生命周期的长短,分别使用不同的算法.

  给对象引用一个概念:年龄 (不是以年为单位,而是熬过GC的轮次),年龄越大,这个对象存在的时间就越久.

  熬过GC的轮次是指:经过一轮可达性分析遍历以后,发现这个对象不是垃圾.

  堆划分成两个区域:新生代和老年代

  新生代又划分成三个区域:一个比较大的伊甸区,两个比较小且一样大的幸存区.

  刚 new 出来的对象,年龄是0,放在伊甸区.

  熬过一轮GC以后,就要放到幸存区了.从伊甸区-->幸存区使用复制算法.

  对象到了幸存区以后,就要周期性的接受GC的考验.

  如果变成垃圾就要释放,如果不是垃圾,就要拷贝到另外一个幸存区(两个幸存区同一时刻只能使用一个).在二者之间使用复制算法来回拷贝.由于幸存区体积不大,此处的空间浪费也能接受,

  如果这个对象在两个幸存区来回拷贝很多次了,此时就要进入老年代了.

  老年代都是年纪大的对象,生命周期周期普遍长.

  这个对象在老年代也要周期性的接受GC的扫描,但是扫描频率更低了.

  如果老年代的对象是垃圾了,就要使用标记整理的方式进行释放.


 


http://www.ppmy.cn/news/43181.html

相关文章

8年测试工程师,3年功能,5年自动化,浅谈我的自动化测试进阶之路...

前言 大家好我是静姐,已近从事测试行业8年了,自己也从事过3年的手工测试,从事期间越来越觉得如果一直在手工测试的道路上前进,并不会有很大的发展,所以通过自己的努力,早几年已经成功的转入自动化测试的方…

2023年郑州重点建设项目名单公布,中创“算力数据中心”项目入选!

4月7日,郑州市人民政府网站公布2023年郑州市重点建设项目名单,名单共列项目680个,总投资1.08万亿元,年度计划投资2691亿元。 在创新驱动能力提升项目名单里,中创算力与人民网人民数据(国家大数据灾备中心&a…

CDH6.3.2引入debezium-connector-mysql-1.9.7监听mysql事件

1、首先说明一下为啥选用debezium,它能够根据事务的提交顺序向外推送数据,这一点非常重要。再有一个结合kafka集群能够保证高可用,对于熟悉java语言的朋友后面一篇博文会介绍怎样编写插件将事件自定义路由到你想要的主题甚至分区中。 提高按顺…

C++类和对象终章——友元函数 | 友元类 | 内部类 | 匿名对象 | 关于拷贝对象时一些编译器优化

文章目录💐专栏导读💐文章导读🌷友元🌺概念🌺友元函数🍁友元函数的重要性质🌺友元类🍁友元类的重要性质🌷内部类(不常用)🌺内部类的性…

2023.04.16 学习周报

文章目录摘要文献阅读1.题目2.摘要3.简介4.Dual-Stage Attention-Based RNN4.1 问题定义4.2 模型4.2.1 Encoder with input attention4.2.2 Decoder with temporal attention4.2.3 Training procedure5.实验5.1 数据集5.2 参数设置和评价指标5.3 实验结果6.结论MDS降维算法梯度…

实力爆表,日日新成为AI领航者

目录正式发布自建算力SenseChat编程能力图像生成后言上周五,阿里发布大模型通义千问,正式开始邀请内测。本周一,人工智能巨头商汤科技正式发布“日日新”大模型体系,全面丰富的产品体系,多个功能表现超预期&#xff0c…

13.Java面向对象----嵌套类

Java面向对象—嵌套类、内部类、匿名类 一、static静态 在《Java编程思想》有这样一段话:   “static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来…

SadTalker项目上手教程

背景 最近发现一个很有趣的GitHub项目SadTalker,它能够将一张图片跟一段音频合成一段视频,看起来毫无违和感,如果不仔细看,甚至很难辨别真假,预计未来某一天,一大波网红即将失业。 虽然这个项目目前的主要…