1. Java中的强引用、软引用、弱引用和虚引用分别是什么
- 强引用
- 最常见的引用类型,在Java中,默认情况下,任何普通的对象引用都是强引用
- 只要一个对象有强引用指向他,垃圾回收器永远不会回收该对象,即使系统内存紧张。
- 软引用
- 当系统内存不足时,垃圾回收器会回收软引用指向的对象,避免内存溢出。在内存充足时,这些对象不会被回收。
- 软引用通常用于实现缓存机制,允许程序在不影响性能的前提下利用多余内存。
- 弱引用
- 只要垃圾回收器发现只有弱引用指向某个对象,该对象会立即被回收,无论系统内存是否充足
- 弱引用常用于防止内存泄漏,典型应用场景WeakHashMap。
- 虚引用
- 虚引用的作用是跟踪对象的垃圾回收状态。在对象被回收时,虚引用会被放入一个ReferenceQueue,我们可以通过这个队列来执行一些清理或者其他后续操作。
2. Java中常用的垃圾收集器有哪些
- 新生代垃圾收集器
- Serial收集器
- 单线程收集器,适合小型应用和单处理器环境
- 触发STW操作,所有应用线程在GC时暂停
- 使用场景:适用于单线程应用
- ParNew收集器
- 是Serial收集器的多线程版本,能够并行进行垃圾收集。
- 与CMS收集器配合使用时,通常会选择ParNew收集器作为新生代收集器。
- 使用场景:适用于多处理器环境,通常配合CMS收集器使用
- Parallel Scavenge收集器(吞吐量优先)
- 也称为"吞吐量收集器",追求最大化CPU时间的利用率
- 并行处理新生代垃圾回收,适合大规模后台任务处理,注重吞吐量而非延迟
- 老年代垃圾收集器
- Serial Old收集器
- Serial收集器的老年代版本,使用标记-整理算法进行垃圾回收。
- Parallel Old收集器
- Parallel Scavenge收集器的老年代版本,使用多线程并行标记-整理算法
- CMS收集器
- 并发标记-清除收集器,追求低延迟,减少GC停顿时间
- 使用并发标记和清除算法,适合对响应时间有较高要求的应用
- 缺点:可能产生内存碎片,并且在并发阶段可能会发生Concurrent Mode Failure,导致full gc。
- G1收集器
- 设计用于取代CMS的低延迟垃圾收集器,能够提供可预测的停顿时间
- 通过分区来管理内存,并在垃圾收集时优先处理最有价值的区域,避免了CMS的内存碎片问题
3. Java中如何判断对象是否是垃圾?不同垃圾回收方法有何区别
- 引用计数法
- 每个对象维护一个引用计数器,引用计数增加时,计数器加1。减少时,计数器减1,当引用计数器为0时,说明该对象不在被引用,可以被回收。
- 优点:实现简单,实时性好
- 缺点:无法处理循环引用的问题,两个对象互相引用时,引用计数器永远不会为0.
- 可达性分析算法
- Java中垃圾回收主要 采用可达性分析算法,通过从一组称为"GC Roots"的对象出发,遍历所有可达的对象,凡是无法通过GC Roots到达的对象,均被视为垃圾。
- 优点:能够解决循环引用问题
- 缺点:需要消耗一定的资源进行标记。
GC Roots的来源
- 线程栈中的引用:每个线程栈中的局部变量、参数等。
- 类的静态变量:被类加载器加载后的类会存储在方法区,类的静态变量可以作为GC Roots。
- JNI全局引用:通过JNI创建的全局引用可以作为GC Roots。
4. 为什么Java的垃圾收集器将堆分为老年代和新生代
主要是为了提高垃圾回收效率,依据对象的生命周期特点来进行优化。
对象生命周期特点:
- 大多数对象存活时间短:大部分对象会很快变成垃圾,不再被使用,这些短生命周期的对象会分配在新生代。
- 少部分对象存活时间长:一些长期存活的对象不会很快被回收,分配在新生代的对象经过多次垃圾回收仍存活的,将晋升到老年代。
所以按照存活时间分区管理更加高效,因为不同分区的生命周期不同,所以可以采用不同的清除算法来优化处理。
不同的回收算法
- 新生代的回收:新生代采用
复制算法
。因为新生代中大部分对象生命周期短,大部分会在一次GC中被回收,复制算法只需要在内存中保留少量存活对象,并将它们复制到Survivor空间,回收剩余区域。这种算法效率很高,适合新生代对象频繁创建和回收的特点。 - 老年代的回收:老年代中对象存活时间长,回收效率低。使用
标记-整理算法
或者标记-清除算法
,更加适合老年代对象的特性。
分区后,可以减少GC暂停的时间。总而言之,分区是为了更高效的管理不同生命周期的对象
。
1.堆的分代机制
Java堆内存根据生命周期被划分为三部分
- 新生代:存放新创建的对象
- 老年代:存放存活时间较长的对象,通常是从新生代晋升过来的对象。
- 永久代:(JDK8以前为永久代,JDK8以后为元空间),存放类的元数据信息,包括类的静态变量、方法等。
2.新生代结构
新生代进一步划分为三个区域
- Eden区:所有新创建的对象首先分配到Eden区。
- Survivor区:Eden区中存活的对象会被复制到Survivor区(一般分为两个区域S0,S1),经过多次GC存活的对象会逐渐晋升到老年代。
新生代中采用复制算法
,每次垃圾回收时,将Eden和Survivor存活对象复制到另一个Survivor空间,效率高且避免内存碎片。
3. 老年代的作用
老年代用于存放生命周期较长的对象,通常是从新生代晋升而来的。老年代使用的回收算法不同于新生代,常用
标记-清除算法
或者标记-整理算法
,适合回收长生命周期的对象。
5. 为什么Java8移除了永久代并引入了元空间
Java8移除了永久代并引入元空间,主要是为了解决PermGen固定大小、容易导致内存溢出GC效率低的问题。元空间使用本地内存,具备更灵活的内存分配能力,提升了垃圾收集和内存管理的效率。
6. 为什么Java新生代被划分为S0、S1和Eden区
主要是为了提高新生代内存利用率。
因为新生代对象朝生夕死的特性,适合复制算法,按照正常思路将新生代一分为二,
划两块区域。每次只使用其中一个,GC后将存活的复制到另一个区域,然后清理老区域非存活对象,这样替换使用两块区域可以避免内存碎片的存在。
但如果一分为二的话,空间利用率只有一半,浪费空间。基于这点,定义了三个区域,Eden区和两个Survivor区,Eden区+1个Survivor可以比二分之一大,提升利用率。默认Eden占80%,一个Survivor占10%。
如果单个Survivor放不下GC存活的对象怎么办
老年代兜底
也就是说Survivor放不下存活的对象,那么超出的对象直接晋升到老年代。如果老年代仍然放不下,则会触发GC。
7. 什么是三色标记算法
三色标记算法是现代垃圾回收器中常用的一种增量标记算法。可以与应用线程并发执行,用于标记哪些对象需要被回收,哪些需要被保留,减少一次性停顿带来的性能影响。非常适合 低延迟和实时垃圾回收的场景。
他通过将对象分为三种颜色来进行标记和追踪。
三色标记基本概念
- 白色对象:表示还没有被垃圾回收器访问到的对象,这些对象有可能是垃圾。
- 灰色对象:表示已经被访问到,但其引用的其他对象还没有被处理完。
- 黑色对象:表示已经被访问到且其引用的所有对象也都已经标记完毕,这些对象不会被回收。
标记过程
- 初始状态:所有对象都是白色。
- 标记阶段:从根对象(GC Roots)开始,把根对象变为灰色,然后递归扫描所有灰色对象,将其引用的对象变为灰色。标记为"已访问",当灰色对象的所有引用都处理完毕时,灰色对象会变成黑色。
- 最终阶段:经过扫描,所有存活的对象最中都会变为黑色,未被访问到的白色对象即为垃圾,会被清除。
8. Java中的young GC、old GC、full GC、mixed GC的区别是什么
- Young GC(Minor GC)
- 作用范围:仅针对新生代(Eden和S0/S1)
- 触发条件:当新生代内存被填满时触发
- 执行方式:只回收新生代中的对象,老年代不受影响
- 特点:回收频率高,回收时间短,因为新生代中的对象的大多数是短命对象,容易被回收。
- old GC (Major GC)
- 作用范围:仅针对老年代
- 触发条件:当老年代空间不足时触发,通常当新生代晋升到老年代的对象过多,或者老年代存活的对象数量达到一定阈值时。
- 执行方式:只回收老年代的对象,新生代不受影响。
- 特点:执行时间比young gc长,因为老年代中的对象存活时间更长,且数量更多。
- full GC
- 作用范围:对整个堆内存进行回收
- 触发条件:当老年代空间不足且无法通过Old GC释放足够空间,或者系统调用如System.gc()。
- 执行方式:回收新生代、老年代中的对象,并且可能会伴随着元空间的回收。
- 特点:回收之间最长,会触发整个JVM停顿(STW),对性能有较大影响,通常不希望频繁发生。
- mixed GC (仅适用于G1 GC)
- 作用范围:同时回收新生代和部分老年代区域
- 触发条件:当G1垃圾回收器发现老年代区域的垃圾过多时触发。
- 执行方式:混合回收新生代和部分老年代区域,主要目的是减少老年代中的垃圾积压。
- 特点:结合了YGC的快速回收和OGC的深度回收,尽量减少停顿时间,适用于大内存应用。
9. 什么条件下触发Java的young GC
- Eden空间不足
- 新生代被划分为三个区域:Eden区、S0、S1,大部分新创建的对象会分配到Eden区。
- 当Eden区的对象填满,无法再为新的对象分配空间时,young GC会被触发,回收新生代中不在使用的对象。
2.Eden区+Survivor区都装满
- 如果Eden区和Survivor区空间都不足以存放新分配的对象时,触发。清理空间,并将幸存的对象转移到Survivor区或者老年代。
- 部分垃圾回收器在full gc前
- 如Parallel Scavenge收集器的回收是在full gc前执行young gc。
10. 什么条件下触发full gc
- 老年代空间不足
- 永久代或者元空间(元空间设置了阈值)不足
- 调用system.gc()或者jmap -dump。
- 空间分配担保:当新生代的to区放不下从Eden区和from区拷贝过来的对象或者新生代对象晋升到老年代时,如果老年代没有足够的空间来容纳这些对象,会触发full gc。
- 新生代到老年代的晋升失败:年轻代中的大对象或长期存活的对象会晋升到老年代。如果此时老年代空间不足,也会引发full gc。
- 年轻代平均晋升大小计算:在要进行young gc的时候,根据之前统计数据发现平均晋升大小比现在老年代剩余空间大,就会触发full gc。
如何减少full gc的触发
- 调整堆内存大小:通过调整堆内存大小(-Xms和-Xmx)来减少老年代空间不足的情况。
- 增大新生代大小:增加新生代的大小,减少对象晋升到老年代的频率。
- 优化对象分配和生命周期:通过分析对象的生命周期,减少长时间存在的大对象,优化应用的内存使用模式。
- 合理设置元空间大小:避免元空间过小导致频繁的full gc。可以使用 -XX:MetaspaceSize和-XX:MaxMetaspaceSize来设置。