秋招面试我去了拼多多,直接被问JVM&GC底层原理和算法,我吊打面试官(1)

最后

学习视频:

大厂面试真题:

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  1. 混合模式:依旧采用解释模式执行代码,但是对于一些“热点”代码采用编译模式执行,JVM 一般采用混合模式执行代码;
  • 优点:相比解释模式,执行会快,相比编译模式,启动会快;

针对混合模式,JVM 有对应的技术去实现,比如 JIT,也就是即时编辑技术。

JVM 内存分配与回收


  • jvm 内存区域图

avatar

  1. 堆内内存:堆内内存 = 年轻代 + 老年代 + 持久代『jdk1.8 之后没有持久代』
  • 优点:

  • 缺点:

  1. 堆外内存:把内存对象分配在 Java 虚拟机的堆以外的内存「比如:java.nio.DirectByteBuffer」
  • 优点:
  1. 减少了垃圾回收机制(GC 会暂停其他的工作);
  1. 加快了复制的速度「堆内在flush到远程时, 会先复制到直接内存(非堆内存), 然后再发送,而堆外内存(本身就是物理机内存)几乎省略了该步骤」。
  • 缺点:
  1. 内存难以控制「使用了堆外内存就间接失去了JVM管理内存的可行性,改由自己来管理,当发生内存溢出时排查起来非常困难」。
年轻代
  • 伊甸区:survivor from 区:survivor to 区 = 8:1:1
伊甸区
  • 大部分对象都在这里诞生

  • 当Eden区满时, 依然存活的对象将被复制到Survivor区, 当一个Survivor 区满时, 此区的存活对象将被复制到另外一个Survivor

survivor 区
  • survivor from

  • survivor to

老年代

方法区/元空间

  • 在 jdk1.8 之后取消了方法区,命名为元空间

线程栈

什么场景下对象会进入老年代

  1. 即将存储的大对象在eden 区域是发现存储不下「就算 Minor gc后还是存储不下」;

  2. 长期存活下来的对象;

  3. Minor gc 后存活的对象Survivor区放不下;

什么是老年代空间分配担保机制

​ 年轻代每次 minor gc 之前 JVM 都会计算下老年代剩余可用空间如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象)就会看一个 “-XX:-HandlePromotionFailure”(jdk1.8 默认就设置了)的参数是否设置了,如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次 minor gc 后进入老年代的对象的平均大小。

如果上一步结果是小于或者之前说的参数没有设置,那么就会触发一次 Full gc,对老年代和年轻代一起回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生 OOM,当然,如果 minor gc 之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发 full gc,full gc完之后如果还是没用空间放 minor gc 之后的存活对象,则也会发生 “OOM”。


触发 full gc 的时机

  1. 调用System.gc();
  1. 老生代内存不足的时候;
  1. 即将要放进老年代的对象过大,需要进行老年代回收;「和第二种类似」
  1. 老年代空间分配担保机制中有可能触发;「也就是老年代空间分配担保失败」
  1. 执行 jmap -histo:live 或者 jmap -dump:live 的时候;

如何判断对象可以被回收


1. 引用计数法

给对象添加一个引用计数器,没增加一个地方引用它,计数器就加一,减少一个,计数器就减一,但是解决不了循环引用的问题「会导致内存泄露」,主流的虚拟机都没有使用这个。

2. 可达性分析

通过一系列的称为 GC Roots 的对象作为起点,从这些节点开始向下搜索,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象。

GC Roots 根节点:线程栈的本地变量、静态变量、本地方法栈的变量等等。

具体操作:从gc root根往下搜索,然后三色标记,黑灰白,刚开始是白色,如果搜索到A节点,A节点的子节点还没被搜索,则A节点是灰色,A节点包括子节点全部搜索完毕标记为黑色,到最后白色的就回收了

3. 依据引用类型

java的引用类型一般分为四种:强引用、软引用、弱引用、虚引用。

  • 强引用

普通的变量引用

public static Person person = new Person();

  • 软引用

将对象用 SoftReference 软引用类型的对象包裹,正常情况不会被回收,但是GC做完后发现释放不出空间存放新的对象,则会把这些软引用的对象回收掉。软引用可用来实现内存敏感的高速缓存。

public static SoftReference person = new SoftReference(new Person());

  • 弱引用

将对象用 WeakReference 软引用类型的对象包裹,弱引用跟没引用差不多,GC 会直接回收掉,很少用。

public static WeakReference person = new WeakReference(new Person());

  • 虚引用

虚引用也称为幽灵引用或者幻影引用,是最弱的一种引用关系,几乎不用。

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用活着弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。


jdk中直接内存的回收就用到虚引用,由于jvm自动内存管理的范围是堆内存,而直接内存是在堆内存之外(其实是内存映射文件,自行去理解虚拟内存空间的相关概念),所以直接内存的分配和回收都是有Unsafe类去操作,java在申请一块直接内存之后,会在堆内存分配一个对象保存这个堆外内存的引用,这个对象被垃圾收集器管理,一旦这个对象被回收,相应的用户线程会收到通知并对直接内存进行清理工作。

4. 通过 finalize() 方法最终判定对象是否存活

即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。标记的前提是对象在进行可达性分析后发现没有与 GC Roots 相连接的引用链。

  • 第一次标记并进行一次筛选。

筛选的条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize 方法,对象将直接被回收。

  • 第二次标记

如果这个对象覆盖了 finalize 方法,finalize 方法是对象脱逃死亡命运的最后一次机会,如果对象要在 finalize() 中成功拯救自己,只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。

垃圾回收算法


[

](https://shimo.im/docs/9GTP6XrJg9J88cJD/)

1、标记-清除算法

分为两个阶段,即标记和清除,首先会标记所有需要被回收的对象,在标记完成后统一对已经标记的对象进行回收,是最基础的收集算法。

  • 优点
  1. 实现简单
  • 缺点
  1. 内存碎片化

  2. 效率不高

  • 使用场景:主流虚拟机不使用

2、标记-整理算法「也叫标记-压缩算法」

针对老年代进行回收的一种算法,标记的过程和『标记-清除算法』一样,只是在清除完成后,会将还存活的对象朝着一个方向移动,然后固定的清理靠近边界的对象。

  • 优点
  1. 解决了碎片化
  • 缺点
  1. 效率不高

  2. 移动了对象地址,需要更新对象的引用

  • 使用场景:用于老年代垃圾回收

3、复制算法「比标记清理和标记整理快 10 倍以上」

能解决「标记-清理算法」带来碎片化问题,复制算法首先将内存分为大小相同的两块,每次只使用其中的一块,但这一块被使用完后「或者是没法提供所需的连续长度的内存」,就会将这一块的内存复制到另一块去,然后再一次性将这块的内存空间全部清理掉。

  • 优点
  1. 解决了碎片化

  2. 效率高

  • 缺点
  1. 内存使用率不高
  • 使用场景:用于年轻代垃圾回收

4、分代回收算法

​ 这种算法不是新鲜的算法,而是针对不同的内存分区,采用不同的回收算法,比如在新生代中,每次收集都会有大量对象(近 99%)死去,所以可以选择复制算法,只需要付出少量对象的复制成本就可以完成每次垃圾收集。而老年代的对象存活几率是比较高的,而且没有额外的空间对它进行分配担保「老年代多是大对象,很可能是需要连续内存地址的对象」,所以我们必须选择「标记清除算法」或「标记整理算法」进行垃圾收集。

垃圾收集器「回收算法的具体实现」


image.png

1、Serial 收集器「-XX:+UseSerialGC -XX:+UseSerialOldGC」

新生代采用复制算法,老年代采用标记-整理算法

Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程「也就是应用程序线程」,直到它收集结束。

Serial 收集器执行过程

avatar

  • 优点
  1. 没有多线程交互,单线程实现简单;
  1. 相比其他单线程收集器,效率最高「当然是比不上多线程收集器」;
  • 缺点:
  1. STW 时间长,用户体验不好
  • 使用场景:

​ 一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。

2、ParNew 收集器「-XX:+UseParNewGC」

新生代采用复制算法,老年代采用标记-整理算法

ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。默认的收集线程数跟 CPU 核数相同,当然也可以用参数(-XX:ParallelGCThreads)指定收集线程数,但是一般不推荐修改。

ParNew 收集器执行过程

[

avatar](https://shimo.im/docs/9GTP6XrJg9J88cJD/)

  • 优点:
  1. 相比 Serial 效率高
  • 缺点:
  1. 实现稍复杂
  • 使用场景:

3、Parallel 收集器「-XX:+UseParallelGC(年轻代) -XX:+UseParallelOldGC(老年代)」

新生代采用复制算法,老年代采用标记-整理算法

Parallel Scavenge 收集器关注点是吞吐量(高效率的利用CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。 Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解的话,可以选择把内存管理优化交给虚拟机去完成也是一个不错的选择。

avatar

  • 优点:

最后

毕竟工作也这么久了 ,除了途虎一轮,也七七八八面试了不少大厂,像阿里、饿了么、美团、滴滴这些面试过程就不一一写在这篇文章上了。我会整理一份详细的面试过程及大家想知道的一些问题细节

美团面试经验

美团面试
字节面试经验
字节面试
菜鸟面试经验
菜鸟面试
蚂蚁金服面试经验
蚂蚁金服
唯品会面试经验
唯品会

因篇幅有限,图文无法详细发出

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

些面试过程就不一一写在这篇文章上了。我会整理一份详细的面试过程及大家想知道的一些问题细节

美团面试经验

[外链图片转存中…(img-ojfJXZBf-1715697828657)]
字节面试经验
[外链图片转存中…(img-7zpyYpKS-1715697828658)]
菜鸟面试经验
[外链图片转存中…(img-qTV76IY9-1715697828658)]
蚂蚁金服面试经验
[外链图片转存中…(img-93gd5b9x-1715697828658)]
唯品会面试经验
[外链图片转存中…(img-r9wJnH1b-1715697828659)]

因篇幅有限,图文无法详细发出

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值