面试 JVM 八股文十问十答第七期
相信看了本文后,对你的面试是有一定帮助的!关注专栏后就能收到持续更新!
⭐点赞⭐收藏⭐不迷路!⭐
1)逃逸分析(Escape Analysis)
逃逸分析是一种编译器优化技术,用于分析并确定在程序运行时,对象的作用域是否会超出创建它的方法或线程。如果一个对象不会逃逸到方法或线程之外,那么其他线程可能无法访问到这个对象,因此可以对这个对象进行一些特定的优化,例如:
- 栈上分配:将对象直接分配在栈上,而不是堆上,这样可以减少垃圾回收的压力,因为栈上的对象随着方法的退出而自动销毁。
- 同步消除:如果确定一个对象不会逃逸,那么对这个对象的同步操作就可以被安全地消除,因为其他线程无法访问到这个对象。
- 标量替换:如果一个对象不会逃逸,那么可以将这个对象拆分为若干个基本类型的变量来存储,这样可以减少对象的创建和销毁开销。
逃逸分析在Java虚拟机(JVM)中是一个可选的优化,不是所有的JVM都实现了这个特性,而且它的效果也受到具体实现和运行环境的影响。
2)指令重排(Instruction Reordering)
指令重排是指编译器或处理器为了优化程序性能,在不改变程序语义的前提下,改变程序中指令的执行顺序。指令重排可以减少流水线停顿,提高处理器的并行度。然而,指令重排可能会影响多线程程序的正确性,因为在多线程环境下,指令的执行顺序可能会影响线程间的同步和数据依赖。
为了保证多线程程序的正确性,Java内存模型(JMM)定义了happens-before原则,确保在不同线程中对共享变量的读写操作具有一定的顺序性。此外,Java提供了volatile
关键字和synchronized
关键字来限制指令重排,保证特定代码块的执行顺序。
3)为什么Java要分老年代和新生代?
Java中的堆内存分为新生代(Young Generation)和老年代(Old Generation),这种划分是为了提高垃圾回收(GC)的效率。
- 新生代:新生代是堆内存中用于存放新创建的对象的区域。由于大多数对象都是短生命周期的,新生代采用复制算法(如标记-清除-整理算法)进行垃圾回收,这种算法在对象存活率较低时效率很高。新生代又分为Eden空间和两个Survivor空间(通常称为From和To)。
- 老年代:老年代用于存放生命周期较长的对象,这些对象在经过多次新生代的垃圾回收后仍然存活。老年代的垃圾回收通常采用标记-清除或标记-整理算法,这些算法在对象存活率较高时效率更高。
将堆内存分为新生代和老年代的好处包括:
- 提高垃圾回收效率:新生代的垃圾回收频率高于老年代,但由于新生代中的对象大多数是短生命周期的,因此新生代的垃圾回收速度快,效率高。
- 减少全堆垃圾回收的次数:通过将堆内存分为两个区域,可以减少全堆垃圾回收的次数,因为全堆垃圾回收通常比局部垃圾回收耗时更长。
- 优化内存使用:新生代和老年代的垃圾回收算法不同,可以根据对象的生命周期特性选择最合适的垃圾回收策略,从而优化内存的使用。
总之,新生代和老年代的划分是Java虚拟机为了提高垃圾回收效率和优化内存使用而采取的一种策略。
4)为什么Java8移除了永久代,加了元空间?
在Java 8之前,Java虚拟机(JVM)使用永久代(Permanent Generation,简称PermGen)来存储类的元数据、静态变量、常量池等。永久代的大小在JVM启动时就已经固定,而且它的大小很难调整,这导致了一些问题:
- 内存溢出问题:如果应用程序加载了大量的类,可能会导致永久代内存溢出(OutOfMemoryError)。
- 垃圾回收效率低:永久代的垃圾回收效率不高,因为它通常包含大量不会被回收的类和常量。
- 难以预测的内存使用:由于永久代的大小固定,很难预测应用程序在运行时对永久代内存的需求。
为了解决这些问题,Java 8中移除了永久代,引入了元空间(Metaspace)。元空间使用本地内存(Native Memory)来存储类的元数据,这意味着:
- 动态调整大小:元空间的大小不再固定,可以根据需要动态增长,减少了内存溢出的风险。
- 提高垃圾回收效率:元空间中的垃圾回收效率更高,因为它只包含真正需要回收的类和常量。
- 更好的内存管理:元空间使用本地内存,可以更有效地管理内存,避免了永久代的一些限制。
5)为什么新生代又要分S1、S2和Eden?
新生代分为Eden空间和两个Survivor空间(通常称为S1和S2),这种划分是为了优化新生代的垃圾回收效率。新生代的垃圾回收通常采用复制算法,这种算法的基本思想是将可用内存分为两个半区,每次只使用其中一个半区。当进行垃圾回收时,将存活的对象复制到另一个半区,然后清空当前半区的所有对象。
Eden空间用于存放新创建的对象,而S1和S2则用于存放从Eden空间复制过来的存活对象。每次垃圾回收时,Eden空间和其中一个Survivor空间(假设是S1)的对象会被检查,存活的对象会被复制到另一个Survivor空间(S2)。这样,Eden空间和S1空间就可以被清空,而S2空间则存放了所有存活的对象。
这种划分的好处包括:
- 减少垃圾回收时的内存移动:由于每次只复制存活的对象,而不是所有对象,因此减少了垃圾回收时的内存移动量。
- 提高垃圾回收效率:新生代中的对象大多数是短生命周期的,因此存活的对象比例通常很低,复制算法的效率在这种情况下非常高。
- 避免内存碎片:复制算法可以有效地避免内存碎片问题,因为每次垃圾回收都会整理内存。
6)你知道有哪些垃圾回收算法?
垃圾回收算法是用于自动管理内存的算法,它们用于识别和回收程序不再使用的内存对象。以下是一些常见的垃圾回收算法:
- 标记-清除(Mark-Sweep)算法:这是最基本的垃圾回收算法之一。它分为两个阶段:标记阶段,遍历所有可达对象并标记它们;清除阶段,回收未被标记的对象所占用的内存空间。这种算法的缺点是会产生内存碎片。
- 复制(Copying)算法:这种算法将可用内存分为两个半区,每次只使用其中一个半区。当进行垃圾回收时,将存活的对象复制到另一个半区,然后清空当前半区的所有对象。这种算法避免了内存碎片,但会浪费一半的内存空间。
- 标记-整理(Mark-Compact)算法:这种算法结合了标记-清除和复制算法的优点。在标记阶段,它遍历所有可达对象并标记它们;在整理阶段,它将存活的对象移动到内存的一端,然后清除边界以外的所有内存。这种算法避免了内存碎片,并且不会浪费内存空间。
- 分代收集(Generational Collection)算法:这种算法基于对象生命周期的不同,将堆内存分为新生代和老年代。新生代采用复制算法,老年代采用标记-清除或标记-整理算法。这种算法利用了不同生命周期对象的特性,提高了垃圾回收的效率。
- 增量收集(Incremental Collection)算法:这种算法将垃圾回收过程分为多个小步骤,在每一步之间允许应用程序继续执行。这样可以减少垃圾回收对应用程序的影响,但会增加算法的复杂性。
- 并发收集(Concurrent Collection)算法:这种算法允许垃圾回收线程和应用程序线程并发执行,从而减少垃圾回收对应用程序的影响。并发收集算法通常用于老年代的垃圾回收。
- 并行收集(Parallel Collection)算法:这种算法使用多个线程并行执行垃圾回收,以加快垃圾回收的速度。并行收集算法通常用于新生代的垃圾回收。
7)三色标记有听过吗?
三色标记(Tri-color Marking)是一种用于并发垃圾回收的标记算法。它通过将对象标记为三种颜色来跟踪对象的可达性,这三种颜色分别是:
- 白色:表示对象尚未被垃圾回收器发现,即未被标记为可达。
- 灰色:表示对象已经被垃圾回收器发现,但其引用尚未被完全处理。
- 黑色:表示对象及其所有引用都已经被垃圾回收器处理完毕。
三色标记算法的基本过程如下:
- 初始时,所有对象都是白色的。
- 垃圾回收器从根对象(如全局变量、活动线程的栈帧等)开始,将它们标记为灰色,并开始遍历它们的引用。
- 当处理完一个灰色对象的所有引用后,将其标记为黑色,并将它引用的对象标记为灰色。
- 重复上述过程,直到没有灰色对象为止。此时,所有白色对象都是不可达的,可以被回收。
三色标记算法的关键在于,它允许垃圾回收器和应用程序线程并发执行,只要保证没有新的白色对象被黑色对象引用,就可以保证算法的正确性。为了防止这种"错失"(missed)情况,通常需要使用写屏障(write barrier)技术来跟踪对象引用的变化。
8)young gc、old gc、full gc、mixed gc 的区别?
在Java虚拟机(JVM)中,垃圾回收(GC)根据堆内存的不同区域和垃圾回收的范围,可以分为以下几种类型:
- Young GC(新生代垃圾回收):也称为Minor GC,它只针对新生代进行垃圾回收。当新生代的Eden空间满时,会触发Young GC。在Young GC中,存活的对象会被从Eden空间和Survivor空间(From)复制到另一个Survivor空间(To),如果对象的年龄达到一定阈值或者Survivor空间不足,对象会被晋升到老年代。
- Old GC(老年代垃圾回收):也称为Major GC,它只针对老年代进行垃圾回收。老年代的垃圾回收通常采用标记-清除或标记-整理算法。Old GC的触发条件通常是老年代空间不足或者达到了垃圾回收的阈值。
- Full GC(全局垃圾回收):Full GC是对整个堆内存(包括新生代、老年代和永久代/元空间)进行垃圾回收。Full GC的触发条件包括老年代空间不足、永久代/元空间空间不足、System.gc()调用等。Full GC通常比Young GC和Old GC更耗时,因为它需要处理更多的内存区域。
- Mixed GC(混合垃圾回收):这是G1垃圾回收器特有的垃圾回收类型。Mixed GC不仅回收新生代,还会回收一部分老年代的区域。G1垃圾回收器将堆内存划分为多个区域(Region),Mixed GC会根据垃圾回收的效率和目标停顿时间,选择一部分老年代区域进行回收。
每种垃圾回收类型都有其特定的触发条件和目标,它们共同协作以保持应用程序的内存使用效率和性能。
9)Young GC 触发条件是什么?
Young GC(新生代垃圾回收)的触发条件主要是新生代的Eden空间满了。当新创建的对象分配内存时,如果Eden空间没有足够的连续内存来满足分配请求,就会触发Young GC。在Young GC过程中,垃圾回收器会执行以下操作:
- 标记Eden空间和Survivor空间(From)中的存活对象。
- 将存活的对象复制到另一个Survivor空间(To)。
- 清空Eden空间和Survivor空间(From)中的所有对象。
- 如果Survivor空间不足以存放所有存活对象,或者某些对象的年龄达到了晋升阈值,这些对象会被晋升到老年代。
Young GC通常执行得比较频繁,因为新生代中的对象生命周期较短,很多对象在经过一次或几次Young GC后就会变成垃圾。
10)Full GC 触发条件有哪些?
Full GC(全局垃圾回收)是对整个堆内存(包括新生代、老年代和永久代/元空间)进行垃圾回收的过程。Full GC的触发条件通常包括以下几种情况:
- 老年代空间不足:当晋升到老年代的对象大小超过了老年代的可用内存时,会触发Full GC。
- 永久代/元空间空间不足:在Java 8之前,永久代空间不足会触发Full GC;在Java 8及以后,元空间空间不足也会触发Full GC。
- System.gc()调用 :虽然不推荐,但应用程序可以通过调用
System.gc()
来建议JVM执行Full GC。 - 大对象直接分配到老年代:如果创建了大对象(大小超过-XX:PretenureSizeThreshold设置的阈值),并且直接分配到老年代,可能会导致老年代空间不足,从而触发Full GC。
- Young GC后老年代空间不足:在Young GC后,如果晋升到老年代的对象大小超过了老年代的可用内存,会触发Full GC。
- CMS GC失败:在使用CMS(Concurrent Mark-Sweep)垃圾回收器时,如果并发收集失败(concurrent mode failure)或者晋升失败(promotion failed),会触发Full GC。
- JVM参数设置 :某些JVM参数设置也可能导致Full GC,例如设置
-XX:+UseParallelOldGC
(使用并行老年代垃圾回收器)时,可能会导致更频繁的Full GC。
Full GC通常比Young GC和Old GC更耗时,因为它需要处理更多的内存区域,因此在设计和调优应用程序时,应尽量避免频繁的Full GC。
开源项目地址:https://gitee.com/falle22222n-leaves/vue_-book-manage-system
前后端总计已经 1300+ Star,2W+ 访问!
⭐点赞⭐收藏⭐不迷路!⭐