搞定面试官:咱们从头到尾再说一次 Java 垃圾回收

简介: 搞定面试官:咱们从头到尾再说一次 Java 垃圾回收

三、GC垃圾回收


1、GC是什么?为什么要GC


GC:垃圾收集,GC能帮助我们释放jvm内存,可以一定程度避免OOM问题,但是也无法完全避免。Java的GC是自动工作的,不像C++需要主动调用。


当new对象的时候,GC就开始监控这个对象的地址大小和使用情况了,通过可达性分析算法寻找不可达的对象然后进行标记看看是否需要GC回收掉释放内存。


2、你能保证GC执行吗?


不能,我只能通过手动执行System.gc()方法通知GC执行,但是他是否执行的未知的。


3、对象的引用类型有哪几种,分别介绍下


强引用


发生GC的时候不会回收强引用所关联的对象。比如new就是强引用。


软引用


有用但非必须的对象,在OOM之前会把这些对象列进回收范围之中进行第二次回收,若第二次回收还没有足够的内存,则会抛出OOM。也就是第一次快要发生OOM的时候不会立马抛出OOM,而是会回收掉这些软引用,然后再看内存是否足够,若还不够才会抛出OOM。


弱引用


有用但非必须的对象,比软引用更弱一些,只要开始GC,不管你内存够不够,都会将 弱引用所关联的对象给回收掉。


虚引用


也叫幽灵引用/幻影引用,无法通过虚引用获得对象,他的意义在于能在这个对象被GC掉时收到一个系统通知,仅此而已。


4、垃圾收集算法有哪些


标记清除


分为两步:标记和清除。


首先需要标记出所有需要回收的对象,然后进行清除回收变为可用内存。


缺点:效率低,会产生垃圾碎片 。


image.png


复制算法


将可用堆内存按照容量分为大小相等的两块,每次只用一块,当这块内存快用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存一次清理掉。


年轻代from/to(s1/s2)采取的就是此种算法。老年代一般不会采取此种算法,因为老年代都是大对象且存活的久的,空间压缩一半代价略高。


优点:效率较高、不会产生碎片。


缺点:将内存缩小为原来的一半,代价略高。


image.png

image.png


标记整理


分为两步:标记和整理。


整理其实也是两步:整理+清除。


整理让所有存活的对象都移动到一端,然后清理掉边界以外的内存。


优点:不会产生碎片问题,适合年老代的大对象存储,不像复制算法那样浪费空间。


缺点:效率赶不上复制算法。


image.png


image.png


分代算法


并不是新算法,而是根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。


5、为什么要分代


因为在不进行对象存活时间区分的情况下,每次垃圾回收都是对整个堆空间进行回收,花费时间会相对较长,也有很多对象完全没必要遍历,比如大对象存活的时间更长,遍历下来发现不需要回收,这样更浪费时间。所以才有了分代,分治的思想,进行区域划分,把不同生命周期的对象放在不同的区域,不同的区域采取最适合他的垃圾回收方式进行回收。


6、分代垃圾回收是怎么工作的


分代回收基于这样一个理念:不同的对象的生命周期是不一样的,因此根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。这样来提高回收效率。

新生代执行流程:


  • 把 Eden + From Survivor(S1) 存活的对象放入 To Survivor(S2) 区;
  • 清空 Eden 和S1 区;
  • S1 和 S2 区交换,S1 变 S2,S2变S1。


每次在S1到S2移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老年代。大对象也会直接进入老年代。


老年代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。


7、垃圾回收器有哪


image.png


Serial


采取复制算法,用于新生代,单线程收集器,所以在他工作时会产生StopTheWorld。单线程情况下效率更高,比如用于GUI小程序


ParNew


采取复制算法,用于新生代,是Serial的多线程版本,多个GC线程同时工作,但是也会产生StopTheWorld,因为不能和工作线程并行。


Parallel Scavenge


采取复制算法,用于新生代,和ParNew一样,所以也会产生STW,多线程收集器,他是吞吐量优先的收集器,提供了很多参数来调节吞吐量。


Serial Old


采取标记整理算法,用于老年代,单线程收集器,所以在他工作时会产生StopTheWorld。单线程情况下效率更高,比如用于GUI小程序


Parallel Old


采取标记整理算法,用于老年代,Parallel Scavenge收集器的老年代版本,吞吐量优先。


CMS


采取标记清除算法,老年代并行收集器,号称以最短STW时间为目标的收集器,并发高、停顿低、STW时间短的优点。主流垃圾收集器之一。


G1


采取标记整理算法,并行收集器。对比CMS的好处之一就是不会产生内存碎片,此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。而且他的STW停顿时间是可以手动控制一个长度为M毫秒的时间片段(可以用JVM参数 -XX:MaxGCPauseMillis指定),设置完后垃圾收集的时长不得超过这个(近实时)。


8、详细介绍一下 CMS 垃圾回收器?


采取标记清除算法,老年代并行收集器,号称以最短STW时间为目标的收集器,并发高、停顿低、STW时间短的优点。主流垃圾收集器之一。


主要分为四阶段:


  • 初始标记:只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。所以此阶段会STW,但时间很短。


  • 并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。不会STW。


  • 重新标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。STW时间会比第一阶段稍微长点,但是远比并发标记短,效率也很高。


  • 并发清除:清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。


image.png



所以CMS的优点是:


  • 并发高
  • 停顿低
  • STW时间短。


缺点:


  • 对cpu资源非常敏感(并发阶段虽然不会影响用户线程,但是会一起占用CPU资源,竞争激烈的话会导致程序变慢)。


  • 无法处理浮动垃圾,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,失败后而导致另一次Full GC的产生,由于CMS并发清除阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾产生,这一部分垃圾是出现在标记过程之后的,CMS无法在本次去处理他们,所以只好留在下一次GC时候将其清理掉。


  • 内存碎片问题(因为是标记清除算法)。当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。


9、详细介绍一下 G1 垃圾回收器?


采取标记整理算法,并行收集器。


特点:


  • 并行与并发执行:利用多CPU的优势来缩短STW时间,在GC工作的时候,用户线程可以并行执行。
  • 分代收集:无需其他收集器配合,自己G1会进行分代收集。
  • 空间整合:不会像CMS那样产生内存碎片。
  • 可预测的停顿:可以手动控制一个长度为M毫秒的时间片段(可以用JVM参数 -XX:MaxGCPauseMillis指定),设置完后垃圾收集的时长不得超过这个(近实时)。


原理:


G1并不是简单的把堆内存分为新生代和老年代两部分,而是把整个堆划分为多个大小相等的独立区域(Region),新生代和老年代也是一部分不需要连续Region的集合。G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。


补充:


Region不是孤立的,也就是说一个对象分配在某个Region中,他并非只能被本Region中的其他对象引用,而是整个堆中任意的对象都可以相互引用,那么在【可达性分析法】来判断对象是否存活的时候也无需扫描整个堆,Region之间的对象引用以及其他手机其中新生代和老年代之间的对象引用虚拟机都是使用Remembered Set来避免全堆扫描的。


步骤:


  • 初始标记:仅仅标记GCRoots能直接关联到的对象,且修改TAMS的值让下一阶段用户程序并发运行时能正确可用的Region中创建的新对象。速度很快,会STW。


  • 并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。不会STW。


  • 最终标记:为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。STW时间会比第一阶段稍微长点,但是远比并发标记短,效率也很高。


  • 筛选回收:首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。


image.png


10、GC日志分析


image.png



11、Minor GC与Full GC分别在什么时候发生


新生代内存(Eden区)不够用时候发生Minor GC也叫YGC。


Full GC发生情况:


  • 老年代被写满
  • 持久代被写满
  • System.gc()被显示调用(只是会告诉需要GC,什么时候发生并不知道)


12、新生代垃圾回收器和老年代垃圾回收器都有哪些?有什么区别?


  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1


新生代垃圾回收器一般采用的是复制算法,复制算法的优点是效率高,缺点是内存利用率低;老年代回收器一般采用的是标记-整理的算法进行垃圾回收。标记整理很适合大对象,不会产生空间碎片。


13、栈上分配是什么意思


JVM允许将线程私有的对象分配在栈上,而不是分配在堆上。分配在栈上的好处是栈上分配不需要考虑垃圾回收,因为出栈的时候对象就顺带着一起出去了,没了,而不需要垃圾回收器的介入,从而提高系统性能。


补充1:对象逃逸。


逃逸的目的是判断对象的作用域是否有可能逃出函数体。例如下面的代码就显示了一个逃逸的对象:


private User user;
private void hello(){
   user = new User();
}


对象实例 user 是类的成员变量,可以被任何线程访问,因此它属于逃逸对象。但如果我们将代码稍微改动一下,该对象就可以线程非逃逸的了。


private void hello(){
   User user = new User();
}


可以看到 user 实例作用域只在 hello 函数中,不会被其他线程访问到,也不会访问。所以该 user 实例对象的作用域只在该函数中,因此它并未发生逃逸。对于这样的情况,虚拟机就有可能将其分配在栈上,而不在堆上。


补充2:TLAB,自行Google。


简单点说,就是将本来应该分配在堆中的对象,让其分配在线程私有的栈上。通过这种方式,减少垃圾回收的压力,提高虚拟机的运行效率。


14、简述下对象的分配规则


  • 对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次YGC。并将还活着的对象放到from/to区,若本次YGC后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。


  • 大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。


  • 长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次YGC那么对象会进入Survivor区,之后每经过一次YGC那么对象的年龄加1,直到达到阀值对象进入老年区。默认阈值是15。可以通过-XX:MaxTenuringThreshold参数来设置。


  • 动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。无需等到-XX:MaxTenuringThreshold参数要求的年龄。


动态年龄判断是有歧义的,要想面试加分,必看这个


https://www.jianshu.com/p/989d3b06a49d


  • 空间分配担保。每次进行YGC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行YGC,如果false则进行Full GC。


image.png

相关文章
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
25天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
32 0
|
27天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
67 14
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
24天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
28天前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
43 1
|
1月前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
42 5
|
1月前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
36 6
|
29天前
|
算法 Java 开发者
Java内存管理与垃圾回收机制深度剖析####
本文深入探讨了Java虚拟机(JVM)的内存管理机制,特别是其垃圾回收机制的工作原理、算法及实践优化策略。不同于传统的摘要概述,本文将以一个虚拟的“城市环卫系统”为比喻,生动形象地揭示Java内存管理的奥秘,旨在帮助开发者更好地理解并调优Java应用的性能。 ####
|
1月前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用