大家好,我是码哥,可以叫我靓仔。
最近 vivo 校招薪资开奖了,想必互联网公司给的不算多,有的同学达到 vivo offer 后直接拒了。
vivo 的面试难度如何?
下面就分享一位校招同学在 vivo Java 后端岗位面试中就有问到 JVM 垃圾回收算法以及这些算法分别用在哪些垃圾收集器?
vivo 一面
JVM 垃圾回收时自动管理内存的一种机制,用于释放不再使用的对象所占用的内存空间。 一般是通过两个步骤实现:
下面我们先来看下垃圾的标记是如何实现的。
通常,标记垃圾有两种方式:引用计数和可达性分析。
通过维护每个对象的引用计数来判断对象是否可以被回收。当有一个指针引用它,那么引用计数+1,当引用计数为 0 时,表示没有被对象引用,可以被回收。
但是引用计数会存在一个问题,就是对象互相引用会导致-循环引用,形成一个环状,这样在这个循环引用的环内所有对象的引用次数至少都为 1,那么这些对象永远无法被回收了。
Java 采用的也是这一种,可达性分析算法表示从 GC Roots 为起点,开始查找存活对象,在查找过的路径称为引用链,所有能访问到对象标记为“可达”,无法访问到的对象就是不可达,也就是可以被回收的垃圾。
哪些可以作为 GC ROOTS 对象呢?
那么在找到垃圾后,如何进行回收垃圾呢?
主要分为“标记”和“清除”两个阶段,标记存在引用的对象,回收未被标记的对象空间。
存在问题:
主要是将内存分为大小相同的两部分,每次只使用其中一个,当其中一个的内存使用完时,把存活的对象复制到另一边去,然后把剩下的空间清理掉。
这样可以提高一定效率,但缺点是内存空间使用不高。
标记过程和“标记-清除”算法一致,但在回收阶段,它是让所有存活的对象移动至一端,然后清理掉边界以外的对象。
分代收集算法主要是将 Java 堆分为年轻代和老年代两个区域。
垃圾收集器是 JVM 中对垃圾回收算法的具体实现。
Serial(串行)收集器是最基本的垃圾收集器了,它是一个单线程收集器,进行垃圾回收时,只会用一个线程去完成垃圾回收工作,同时会让其他所有的工作线程停止(Stop The World),等待它执行完成。
Parallel Scavengel 收集器其实就是 Serial 的多线程版本,使用多线程进行垃圾回收,而它的系统吞吐量自然也是比 Serial 的要高。
ParNew 收集器和 Parallel Scavengel 收集器十分类似,通常会和 CMS 结合使用,新生代采用它完成垃圾回收。
CMS,Concurrent Mark Sweep,是一款追求以最短回收时间为目标的垃圾收集器,注重提升用户体验,它是第一次实现了同时让垃圾回收线程和用户线程一起工作。
CMS 是基于“标记-清除”算法实现的,它的运作过程有以下几个步骤:
从上面过程就可看出,CMS 的主要特点是:并发收集,延迟低。但也存在几个缺点:
CMS 有哪些常用的参数呢?
-XX:+UseConcMarkSweepGC
,启用 CMS 收集器,注意 JDK8 默认使用的是 Parallel GC,JDK9 以后使用 G1 GC。
-XX:ConcGCThreads
,CMS 并发过程运行的线程数。
-XX:+UseCMSCompactAtFullCollection
,FullGC 完成后再做压缩整理,针对 CMS 容易产生内存碎片做的优化。
-XX:CMSFullGCsBeforeCompaction
,配合上面使用,多少次 FullGC 完成后进行压缩,,默认是 0,即每次都会压缩。
-XX:CMSInitiatingOccupancyFraction
,老年代使用达到的某个比例时会触发 FullGC,默认是 92。
-XX:+CMSParallellnitialMarkEnabled
,表示在初始标记阶段采用多线程执行,减少 STW 时间。
-XX:+CMSParallelRemarkEnabled
,表示在重新标记阶段采用多线程执行,减少 STW 时间。
G1 是一面向服务器的垃圾收集器,主要针对多处理器和大内存的机器,在极高概率满足 GC 停顿时间要求的同时,具备高吞吐量特性。
G1 将 Java 堆划分为多个大小相等的独立区域(Region),JVM 最多可以有 2048 个 Region。一般 Region 大小等于堆大小除以 2048,比如堆大小为 4096M,则 Region 大小为 2M。
G1 保留了年轻代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region 的集合。默认年轻代对堆内存的占比是 5%,如果堆大小为 4096M,那么年轻代占据 200MB 左右的内存,对应大概是 100 个 Region。
G1 回收步骤: