面试题-JVM(二)

参考文章:

狂神说——JVM笔记_小小酒馆的掌柜的博客-CSDN博客_狂神说jvm

(Java实习生)每日10道面试题打卡——JVM篇 (二)_兴趣使然的草帽路飞的博客-CSDN博客

狂神说笔记——JVM入门07 - subeiLY - 博客园 (cnblogs.com)

1、请你说一说java 中的 五种引用?Java 中都有哪些引用类型?

① 强引用

  • 只要沿着 GC Root 的引用链能够找到该对象,就不会被垃圾回收;只有当 GC Root 都不引用该对象时,才会回收强引用对象。
  • 换句话说就是,只要强引用存在,JVM 垃圾回收器就永远都不会回收被引用的对象,即使内存不足,JVM 会抛出 OutOfMemoryError

比如,new 一个对象 Student ,将对象 Student 通过=(赋值运算符),赋值给变量 stu,则变量 stu 就强引用了对象 Student

// 只要 stu 指向 Student 对象,那它就是强引用,永远都不会被 JVM 回收
Student stu = new Student();
// 如果将 stu 置为 null,可以切断 GC Root 引用链,这样 stu 就会被 JVM 回收
stu = null;

② 软引用(SoftReference)

  • 如果仅有软引用引用某个对象时,在垃圾回收后,内存仍不足时会再次触发垃圾回收,回收软引用对象。即,在内存足够时,JVM 不会回收软引用对象,但当内存不足时,软引用对象就会被回收,所以软引用对象通常用来描述一些非必要但仍有用的对象。
// 不直接通过 list 引用 byte[]
// list -----> SoftReference -----> byte[] 添加了一层软引用:
List<SoftReference<byte[]>> list = new ArrayList<>();

③ 弱引用(WeakReference)

  • 弱引用是较软引用更低一级的引用,如果仅有弱引用引用某个对象,在垃圾回收时,无论内存是否充足,都会回收弱引用所引用的对象。
List<WeakReference<byte[]>> list = new ArrayList<>();

④ 虚引用(必须配合引用队列)

  • 虚引用必须配合引用队列使用,主要配合 ByteBuffer 使用,引用对象被回收时,会将虚引用入队,然后调用虚引用相关方法(Unsafe.freeMemory())释放直接内存。

⑤ 终结器引用(必须配合引用队列)

  • 所有的类都继承自Object 类,Object 类有一个finalize()方法。当某个对象不再被其他的对象所引用时,会先将终结器引用对象放入引用队列中(被引用对象暂时没有被回收),然后根据终结器引用对象找到它所引用的对象,然后调用该对象的finalize()方法。调用以后,该对象就可以被垃圾回收了。

引用队列

在回收软引用、弱引用所指向的对象时,软引用本身不会被清理。如果想要清理引用,需要使借助引用队列

ReferenceQueue,当一个引用(软引用、弱引用)关联到了一个引用队列后,当这个引用所引用的对象要被垃圾回收时,就会将它加入到所关联的引用队列中,所以判断一个引用对象是否已经被回收的一个现象就是,这个对象的引用是否被加入到了它所关联的引用队列。

ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);

说到底,引用队列就是一个对引用的回收机制,当软引用或弱引用所包装的对象为 null 或被回收时,这个引用也就不在具有价值,引用队列就是清除掉这部分引用的一种回收机制。

  • 软引用和弱引用可以配合引用队列(也可以不配合):在弱引用虚引用所引用的对象被回收以后,会将这些引用放入引用队列中,方便一起回收这些软/弱引用对象。
  • 虚引用和终结器引用必须配合引用队列虚引用终结器引用在使用时会关联一个引用队列。

2、JVM 是如何判断对象可以被回收的?判断对象是否可以回收的两种算法?

① 引用计数法(java不采用这种方法)

  • 如果一个对象被其他变量所引用,则让该对象的引用计数+1,如果该对象被引用 2 次则其引用计数为 2,依次类推。

  • 某个变量不再引用该对象,则让该对象的引用计数-1,当该对象的引用计数变为0 时,则表示该对象没用被其他变量所引用,这时候该对象就可以被作为垃圾进行回收。

引用计数法弊端:循环引用时,两个对象的引用计数都为 1 ,导致两个对象都无法被释放回收。最终就会造成内存泄漏!

在这里插入图片描述

② 可达性分析算法

可达性分析算法就是JVM中判断对象是否是垃圾的算法:该算法首先要确定 GC Root (根对象,就是肯定不会被当成垃圾回收的对象)。

在垃圾回收之前,JVM会先对堆中的所有对象进行扫描,判断每一个对象是否能被 GC Root直接或者间接的引用,如果能被根对象直接或间接引用则表示该对象不能被垃圾回收,反之则表示该对象可以被回收

  • JVM中的垃圾回收器通过可达性分析来探索所有存活的对象。

  • 扫描堆中的对象,看能否沿着 GC Root 为起点的引用链找到该对象,如果找不到,则表示可以回收,否则就可以回收。

  • 可以作为GC Root的对象

    • 虚拟机栈(栈帧中的本地变量表)中引用的对象。
    • 方法区中类静态属性引用的对象。
    • 方法区中常量引用的对象。
    • 本地方法栈中 Native 方法引用的对象。

垃圾回收算法GC

在这里插入图片描述

JVM在进行GC时,并不是对这三个区域==(jdk1.8以后不存在永久区,改名元空间,不在JVM,是在本地内存中的)==统一回收。 大部分时候,回收都是新生代~

●新生代**(伊甸园区)**
●幸存区**(form区,to区)**
●老年区

3、JVM 垃圾回收算法有哪些?

① 标记-清除算法

在这里插入图片描述

  • 标记清除算法:顾名思义,是指在虚拟机执行垃圾回收的过程中,先采用标记算法确定可回收对象,然后垃圾收集器根据标识,清除相应的内容,给堆内存腾出相应的空间。

    • 这里的腾出内存空间并不是将内存空间的字节清 0,而是记录下这段内存的起始结束地址,下次分配内存的时候,会直接覆盖这段内存。
  • 它的主要不足有两个:

  • 一个是效率问题,标记和清除两个过程的效率都不高;

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

② 标记-整理算法

在这里插入图片描述

  • 标记-整理算法:会将不被 GC Root 引用的对象回收,清除其占用的内存空间。然后整理剩余的对象内存空间,可以有效避免因内存碎片而导致的问题,但是牵扯到对象的整理移动,需要消耗一定的时间,所以效率较低。

③ 复制算法

  • 复制算法:将内存分为等大小的两个区域,FROMTO(TO中永远为空)。先将被 GC Root 引用的对象从 FROM 放入 TO 中,再回收不被 GC Root 引用的对象。然后交换 FROMTO

这样也可以避免内存碎片的问题,但是会占用双倍的内存空间 (用空间来换时间)。流程如下:

在这里插入图片描述

  • 当需要回收对象时,先将 GC Root 直接引用的的对象(不需要回收的对象)从 FROM 放入 TO 中:

在这里插入图片描述

在这里插入图片描述

  • 然后清除FROM中的需要回收的对象:

在这里插入图片描述

  • 最后交换 FROMTO 的位置:(FROM 换成 TO,TO 换成 FROM )

在这里插入图片描述

④ 分代收集算法

  • 分代收集算法 :这种算法是把 Java 堆分为新生代老年代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。根据不同年代的特点采用最适当的收集算法。
  • 新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
    • 新生代里有 3 个分区:伊甸园、To 幸存区、From 幸存区,它们的默认占比是 8:1:1
  • 老年代中,因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清理或者标记—整理算法来进行回收。

下面来逐步介绍一下分代收集算法的流程:

  • 长时间使用的对象放在老年代中(长时间回收一次,回收花费时间久),用完即可丢弃的对象放在新生代中(频繁需要回收,回收速度相对较快):

在这里插入图片描述

  • 新创建的对象都被放在了新生代的伊甸园中:

在这里插入图片描述

在这里插入图片描述

  • 当伊甸园中的内存不足时,就会进行一次垃圾回收,这时的回收叫做 Minor GC (Young GC 轻GC):

Minor GC 会将伊甸园和幸存区FROM仍需要存活的对象复制到 幸存区 TO中, 并让其寿命加1,再交换FROM和TO

在这里插入图片描述

  • 伊甸园中不需要存活的对象将其清除:
    在这里插入图片描述

  • 交换FROM和TO

在这里插入图片描述

  • 同理,继续向伊甸园新增对象,如果满了,则进行第二次 Minor GC

流程相同,仍需要存活的对象寿命+1:(下图中 FROM 中寿命为1的对象是新从伊甸园复制过来的,而不是原来幸存区 FROM 中的寿命为1的对象,这里只是静态图片不好展示,只能用文字描述了)

在这里插入图片描述

再次创建对象,若新生代的伊甸园又满了,则会再次触发 Minor GC(会触发 Stop The World, 暂停其他用户线程,只让垃圾回收线程工作),这时不仅会回收伊甸园中的垃圾,还会回收幸存区中的垃圾,再将活跃对象复制到幸存区TO中。回收以后会交换两个幸存区,并让幸存区中的对象寿命加1

  • 如果幸存区中的对象的寿命超过某个阈值最大为 154 bit),就会被放入老年代中:

在这里插入图片描述

  • 如果新生代老年代中的内存都满了,就会先触发 Minor Gc,再触发 Full GC,扫描新生代和老年代中所有不再使用的对象并回收:

在这里插入图片描述

分代收集算法流程小结:

  • 新创建的对象首先会被分配在伊甸园区域。
  • 新生代空间不足时,触发 Minor GC,伊甸园和 FROM 幸存区需要存活的对象会被 COPY 到 TO 幸存区中,存活的对象寿命+1,并且交换 FROMTO
  • Young GC 会引发 Stop The World:暂停其他用户的线程,等待垃圾回收结束后,用户线程才可以恢复执行。
  • 当对象寿命超过阈值15时,会晋升至老年代。
  • 如果新生代、老年代中的内存都满了,就会先触发 Minor GC,再触发 Full GC,扫描新生代和老年代中所有不再使用的对象并回收。

4、JVM 垃圾回收机制有哪些?

在触发 GC 的时候,具体如下,这里只说常见的 Young GC (Minor GC) 和 Full GC

① Young GC (Minor GC)轻GC

  • 当新生代中的 Eden(伊甸园) 区没有足够空间进行分配时会触发 Young GC
  • Young GC 其实就是一次 复制垃圾回收算法伊甸园和幸存区FROM仍需要存活的对象复制到 幸存区 TO中, 并让其寿命加1,再交换 FROM和 TO。这时候伊甸园中不需要存活的对象就将其清除。

② Full GC重GC

  • 如果新生代老年代中的内存都满了,就会先触发 Young GC,再触发 Full GC,扫描新生代和老年代中所有不再使用的对象并回收。

  • System.gc() 默认也是触发 Full GC

  • Full FC 使用 标记—清理 或者 标记—整理 算法来进行回收。(一般为多次清理再进行一次整理,也叫标记-清理-整理算法)

Full GC为什么那么慢?

  • 因为 Full GC 要回收新生代、老年代、元数据区/永久代(方法区)。

  • 元数据区的回收算法效率低,虚拟机规范 Class 回收条件比较苛刻

  • 新生代复制算法比较快,老年代使用的标记—清理 或者 标记—整理 算法回收速度慢,且老年代本身内存大小就大于新生代。

  • 在一些垃圾回收器中,Full GC 回收之前,会先触发一次 Young GC,然后再进行 Full GC

5、说一下JVM 有哪些垃圾回收器?

相关概念:

  • 并行收集多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态

  • 并发收集用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。

  • 吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间)),也就是。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%

垃圾回收器的分类:

  • 串行:单线程垃圾收集。
  • 吞吐量优先:多线程垃圾收集,单位时间内,让STW(stop the world,停掉其他所有工作线程)时间最短。
  • 响应时间优先:多线程垃圾收集,尽可能单次STW时间变短(尽量不影响其他线程运行)。

7种垃圾回收器:

如图:(图片参考自:https://thinkwon.blog.csdn.net/article/details/104390752

在这里插入图片描述

主要来了解一下下面两个回收器(7个回收器,就算背也难记住,先掌握2个再说!)

  • CMS 收集器老年代并行收集器

  • 以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

  • CMS 收集器基于标记-清除算法实现,会产生内存碎片。

  • G1 收集器Java堆并行收集器

  • G1收集器是 JDK1.7 提供的一个新收集器,G1 收集器基于标记-整理算法实现,也就是说不会产生内存碎片。

  • 此外,G1 收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而其他六种收集器回收的范围仅限于新生代或老年代。

CMS 和 G1 都是属于响应优先的垃圾回收器:尽可能让**单次 **STW 时间变短(尽量不影响其他线程运行)。

:STW ,即 Stop The World, 暂停其他用户线程,只让垃圾回收线程工作。

6、请你说一下 GC Root 有哪些?

在Java语言中,可作为GC Roots的对象包括下面 4 种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 Native 方法引用的对象

7、HotSpot为什么要分为新生代和老年代?

HotSpot根据对象存活周期的不同将内存划分为几块,一般是把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。

  • 在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
  • 而在老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。

其中新生代又分为1个伊甸园区(Eden)和2个幸存区(Survivor),通常称为From Survivor和To Survivor区。

8、说一下 JVM 的几个主要组成部分?

如图:(图片参考自https://blog.csdn.net/weixin_43591980/article/details/116903332)

在这里插入图片描述

主要由 4 个部分组成:

  • 运行时数据区域:就是我们常说的JVM的内存。
  • 执行引擎:执行 class 字节码文件中的指令。
  • 类加载系统:根据给定的全限定名类名(如:java.lang.Object)来装载 .class 文件到运行时数据区中的方法区中。
  • 本地接口:与本地方法库交互,是其它编程语言交互的接口。

10、Java会存在内存泄漏吗?请简单描述

答案:Java中会存在内存泄漏。

Java中虽然存在 GC垃圾回收机制,及时回收不再被使用的对象。但是依然存在内存泄露的情况!

Java导致内存泄露的原因很明确:长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露的发生场景。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三横同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值