2022 - JVM相关面试总结

笔记

参考 : jvm基本概念

一: jvm基本概念

1. java程序的编译过程

           程序员编写 .java 文件 -> 由javac编译为 .class 类文件 (让jvm认识的文件)  -> 由jvm 编译为计算机认识的二进制码文件

2. jvm的组成

jvm

    两个子系统  
   			 :  类加载系统 : 负责将 .class文件加载到 jvm的运行时数据区 中的方法区中 
   			 :  执行引擎系统 : 执行编译后的class中的指令
     两个组件 : jvm运行时数据区 : 即jvm的内存 , 负责存储和执行类文件中的代码指令
     		 : 本地接口库 : 与 native libraries 交互 , 是其他变成语言交互的接口

3. jvm的运行流程

       首先由javac将java文件编译为.class文件,由jvm类加载器将.class字节码加载到jvm内存中的方法区内,
     
     而这里的字节码是jvm自身的一套指令集规范编码,计算机底层并不认识, 因此需要特定的命令解析器也就
     
     是jvm执行引擎去将字节码翻译成系统底层的指令 , 交给cpu去执行, 而在这个过程中需要调用其他语言的
     
     本地库接口来实现整个程序的功能!

4. jvm运行时数据区的组成

运行时数据区

     1. 线程私有 : jvm栈,本地方法栈  +  程序计数器
     2. 线程共享 : 堆 + 方法区 (元数据区)
     3. 程序计数器 : jvm运行时数据区中一个很小的空间,当前线程执行的字节码的行号指示器,负责指示程序
         			下一步应该执行什么命令, 因为线程本身是没有记忆的,所以需要程序计数器
     4. 栈 : 
        1. jvm栈 : 虚拟机栈是在执行方法时生成一个栈帧放到栈中,每一个栈帧中包含 局部变量, 操作数栈, 动态链接, 方法出口
                		每一个方法的执行就是一个栈帧的入栈和出栈
        2. 本地方法栈 : 存放执行由native修饰的方法的栈帧,专门为虚拟机执行native方法服务
     5. 堆 : jvm运行时数据区中空间占比最大的区域 , 负责存储几乎所有的创建的对象实例
     6. 方法区(元数据区) : 存放常量, 静态变量, 已经被类加载器加载的类信息, 即时编译后的代码

5. 程序计数器是干什么的

     1. 行号指示器: 当前线程执行的字节码的行号指示器,字节码解释器通过改变程序计数器的值来选择下一条执行的命令
        
                             比如是顺序执行,判断,分支,循环,异常等
     2. 是线程的记忆点 : 线程本身没有记忆,他只有状态的流转,而当线程被挂起,之后又唤醒之后他需要知道接下来要干什么
        
                                     ,这时就需要程序计数器来告诉他上次执行到了那儿
     3. 为什么他是没有oom的区域 : 是因为它随着线程的创建儿存在,随着线程的销毁儿消失

6. jvm虚拟机栈是干什么的

         虚拟机栈是线程私有的区域,他的生命周期同线程的生命周期
     
         线程启动时我们创建一个栈,当线程执行方法时,我们创建栈帧入栈,方法结束之后栈帧出栈
     
         栈帧  : 栈中的基本单位
     
                   局部变量 : 存储方法中的局部变量, 包括八大基础类型变量 , return时的变量类型 , 方法中成员变量的引用地址
     
                   操作数栈 : 方法中有关操作计算的存储区域,比如 i=6*6,他会先进行计算然后存到局部变量中
     
                   动态连接 : 调用别的方法时,别的方法的链接存储的地方,如果有则会将别的方法放到我这个栈帧的下面
     
                   方法出口 : 犯法返回的东西,正常就return,异常就抛出异常
     
           注意 : 递归时会创建多个栈帧,操作不当则会引起栈内存溢出

7. jvm堆是干什么的

  • jvm堆是jvm中最大的一块内存区域,是被所有线程共享的区域,他的唯一作用就是存储对象的实例,
    包括所有的对象以及所有的数组都要在堆上分配空间
  • jvm堆也是内存回收的主要区域,所以又被称为 “GC堆” / “垃圾堆”
  • 堆区域的划分可以从内存回收和内存分配两个角度看
    内存回收 : 新生代和老年代
    内存分配 : 在线程共享的堆中划分多个线程私有的分配缓存区
  • 当堆内存无法继续为新对象分配内存的时候,抛出OOM异常

8. 方法区是干什么的

  • jvm方法区用来存储 常量,静态变量,即使编译后的代码,已经被类加载器加载的类信息

  • 它也被称为 “非堆”,也是线程共享的区域,当方法区无法满足内存分配时也会抛出OOM异常

9. JVM字节码执行引擎

  • jvm通过字节码执行引擎将.class文件编译为机器码,他是jvm的核心组件

10.jvm的垃圾回收系统是什么

  • 在运行时数据区内,堆内存空间中存在的大量创建后的对象,而其中有很多对象没有了引用,对于计算机来说这一类对象相当于"死亡"
    所以在java中会有垃圾回收器自动收集并清除这些"死亡"的对象,以确保程序的正常运行

11.jvm堆和栈的区别

对比JVM堆JVM栈
物理地址堆的物理地址分配对对象是不连续的。因此性能慢些。在GC的时候也要考虑到不连续的分配,所以有各种算法。比如,标记-消除,复制,标记-压缩,分代(即新生代使用复制算法,老年代使用标记——压缩)栈使用的是数据结构中的栈,先进后出的原则,物理地址分配是连续的。所以性能快。
内存分别堆因为是不连续的,所以分配的内存是在运行期确认的,因此大小不固定。一般堆大小远远大于栈。栈是连续的,所以分配的内存大小要在编译期就确认,大小是固定的。
存放的内容堆存放的是对象的实例和数组。因此该区更关注的是数据的存储栈存放:局部变量,操作数栈,返回结果。该区更关注的是程序方法的执行。
程序的可见度堆对于整个应用程序都是共享、可见的。栈只对于线程是可见的。所以也是线程私有。他的生命周期和线程相同。
  • 注意: 静态变量放在方法区,但是静态的对象还是存放在堆中

12.浅拷贝和深拷贝

  • 浅拷贝: 复制一个引用指向原来的对象地址
  • 深拷贝: 复制一个引用指向一个在堆中开辟的新地址
  • 赋值:和浅拷贝非常容易混淆的概念 , 对于基本类型来说是开辟新的空间,对于对象来说是指向原有对象

参考 : 深拷贝和浅拷贝的讨论

二: 垃圾回收

1:简述一下java的垃圾回收机制

  • 在java开发中,程序员本身无序关心内存空间的释放问题, jvm 中有一个优先级别很低的线程专门来处理内存空间的回收,
    当我们的程序空闲时,或者内存不足时,这个线程会启动,去收集那些计算机任务已经"死亡"的对象,从而将他们所占用的空间
    释放掉,这就是垃圾回收机制
  • 可通过System.gc()方法手动调用垃圾回收,但是并不保证会立即执行

2.jvm中的引用类型

  • 强引用:我们大部分手动创建的对象都是强引用,这类引用不会自己消除,只有GC判断死亡后才会消除
  • 软引用:通过 : SoftReference 引用的对象当内存不够时,在下一次回收将会被消除
  • 弱引用:通过 : WeakReference 引用的对象,无论内存是否充足,在GC时都会被回收
  • 虚引用:通过 : 无法通过虚引用来获取对象 , 可以通过PlantomReference实现,虚引用的一个很大的作用就是当发生GC时会返回一个通知

参考 : jvm的引用类型

3.如何判断对象是否可回收

  • 判断对象是否可回收,主要是判断该对象是否还存在有用的引用,判断方法有 引用计数法和可达性分析法
  • 引用计数法 : 在创建对象时,在对象中分配一小块内存,添加一个引用计数器,当对象每增加一个引用,计数器+1, 每当减少一个引用,
    计数器-1,当计数器为0时,此对象可被判断为可回收
    需要注意的是 : 当出现循环引用时 A=B , B=A 这时两个对象的计数器都为1,无法被回收,导致内存空间浪费
  • 可达性分析法 : 利用java中的一些特定的对象作为 GC Root , 让对象们指向这些GC Root,从对象到GC Root的过程称为引用链,
    当一个对象的引用链没有指向任何一个GC Root时,则此对象会被认定为可回收对象
  • 可以当做 GC Root 的对象有那些 :
    – jvm虚拟机栈中的局部变量引用的对象
    – 本地方法栈中,native方法引用的对象
    – 方法区中的静态对象引用的对象
    – 方法区中常量引用的对象

但是注意: 这里对象并不会立即被销毁,此时GC会去判断对象是否有finalize()方法,没有或者已经执行过finalize()方法则直接回收 , 如果有并且还没有执行,则对象获得一次复活的机会,他被放到一个F-Queue队列中,等待低级别的线程来执行他 ,这里finalize()方法既不稳定也会消耗内存,建议不要频繁使用.

4.jvm的垃圾回收算法有那些

  • 标记清除
    概念 : 垃圾回收器将已经判定死亡的对象进行标记,然后对标记对象进行清理
    标记清楚算法
    优点 : 采用引用计数法,避免了可达性分析的循环依赖问题
    缺点 :
    1.性能问题:两次操作都需要遍历所有对象,并且需要stw,停止所有程序,对于响应要求高的系统不友好
    2.空间问题:标记清除法会产生大量的不连续空间,不利于后续大对象的创建

  • 标记复制算法
    概念: 回收器将内存分为两个大小相同的空间,将存活的对象复制到另一个空间中,之后清除原来的空间中所有对象
    标记复制算法
    优点 : 解决了标记清除算法产生的大量空间碎片的问题,同时如果对于每次存活对象占比小的系统,这种算法是比较高效的
    缺点 : 显而易见,放弃了一半的内存,空间浪费严重,并且对于存活对象占比大的系统(就像老年代),这种算法性能是比较差的

  • 标记整理
    概念: 标记整理的标记过程和标记清除算法相同,但是在后续的清除阶段,标记整理算法会将存活的对象压缩到内存的一端,解决了大量空间碎片的问题
    标记整理算法
    优点: 解决了空间碎片问题,也不会像复制算法那样空闲大量内存空间
    缺点: 可能的性能问题
    说明 : 使用标记清除算法和标记整理算法的区别在于是否移动存活对象,前者是非移动,后者移动,而移动存活对象这一动作其实是一把双刃剑
    如果移动 : 对于老年代这种存在大量存活对象的空间,对象的移动和更新这些对象的引用,是一个非常负重的过程,并且在执行时需要stw,这对于响应要求高的系统不太友好
    如果不移动 : 大量的空间碎片无法处理,只能依赖更深的内存分配器和内存访问器来操作,而内存是我们程序访问最频繁的地方,这种负重的依赖,会影响我们程序的吞吐量
    折中 : 在大多数情况下采用不移动的标记清除算法,暂时容忍空间碎片,提高吞吐量 , 在内存不够用时采用标记整理算法,保证程序的正常运行,CMS收集器就是采用的折中算法

  • 分代收集算法
    概念: 分代收集是根据对象的存活周期,将内存分为几个不同的区域,一般java将内存分为新生代和老年代,在新生代中的对象存活周期短,所以采用标记复制算法处理,只需要将少数存活的对象复制出来,其余死亡对象清除即可,对于老年代来说,对象存活周期长,使用标记清除或者标记整理算法处理即可

5.jvm的分代收集算法

概念: jvm分代收集是将java堆内存分为年轻代(Young)和老年代(Old)两个内存区域,年轻代中分为Eden区和两个大小相同的Survivor区,Survivor区中一个称为From,一个称为To,通常 Eden : Survivor = 8:2 , 根据年轻代和老年代两个区域中对象存活周期采用不通的回收算法,
年轻代一般采用标记复制算法,老年代一般采用标记清除和标记整理算法

年轻代区域划分
回收流程:

  1. 新创建的对象被分配到Eden区中,在GC触发时,对Eden区和Survivor区中的From/To中的一个区进行标记复制算法,将存货的对象移动到另一个Survivor区中,不同的处理是从Survivor区中存活下来的对象 “年龄” 会+1 , 如果这个"年龄"没有超过阈值(可配置,默认15)则他还在Young区中,否则将此对象放入Old区中
  2. 在下一次回收时(一般是空间不足时),将Eden和上次存放存活对象的Survivor区进行标记复制算法,存活的对象移动到另一个Survivor区,重复之前的操作
  3. 以此类推,反复循环

6. 垃圾回收器

参照 : 常见的垃圾回收器
常见的垃圾回收器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值