JVM冷门装x知识(一问一个不知道)

69 阅读8分钟

1. 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?

JDK1.8以前,方法区是由永久代实现的;JDK1.8之后,方法区是由元空间实现的;
永久代是放在JVM内存空间内,在JVM启动时永久代的内存大小就被固定了,无法动态调整;再加上方法区垃圾回收频率较低,很容易导致永久代内存溢出。
元空间是位于主机的直接内存中,内存大小是动态调整的,上限为主机内存上限,因此不容易发生内存溢出。

2. 为什么大对象或者长数组要直接放入老年代呢?多大的对象会直接放入,JVM怎么去判断?

大对象或长数组直接放入老年代的主要原因是为了减少垃圾回收的复杂性和提高性能。将大对象或长数组直接放入老年代可以减少在新生代的复制(新生代采取复制算法)和老年代的移动(老年代采取标记整理算法),从而减少了垃圾收集器的压力,提高了垃圾回收的效率。

可以使用JVM参数 -XX:PretenureSizeThreshold 来指定一个阈值,当对象的大小超过这个阈值时,JVM会选择直接将其分配到老年代。-XX:PretenureSizeThreshold的默认值为0,意味着所有的大对象都会在新生代分配空间而不会直接进入老年代。

3. 老年代分配担保机制是什么?

老年代空间分配担保(Promotion Failure Handling)是Java虚拟机(JVM)在进行内存管理时的一项策略,主要用于确保在年轻代(Young Generation)进行Minor GC(新生代垃圾回收)之后,存活下来的对象能够成功晋升到老年代(Old Generation)而不会触发过多的Full GC。
当年轻代内存不足触发Minor GC时,在在进行Minor GC前,JVM会评估老年代剩余的连续可用空间是否足够存放年轻代中即将晋升的对象。根据是否开启老年代分配担保机制,会有不同的执行策略。

  • 如果开启了老年代分配担保机制,那么JVM会检查过去晋升老年代的对象数量及大小等统计信息,尝试猜测本次Minor GC后可能有多少对象会晋升。1.若预测的结果显示可能存在风险,即使当前老年代空间足够,JVM也可能选择提前触发一次Full GC,以试图回收更多老年代空间,从而尽可能避免Minor GC过程中因老年代空间不足而导致的晋升失败。2.若预测的结果不存在风险,JVM将继续执行Minor GC。
  • 如果没有开启老年代分配担保机制,那么JVM将继续执行Minor GC。如果实际晋升的对象大小超过了老年代剩余空间,这时只能被迫中断Minor GC过程,转而执行一次Full GC来释放老年代空间。

4. 不可达对象一定会被回收吗?

在Java虚拟机(JVM)中,一个不可达对象并不一定会立即被回收。不可达是垃圾回收的一个必要条件,JVM的垃圾回收器会认为不可达对象是潜在的可回收对象。但是,对象是否会被回收还要经过一定的垃圾回收流程:

  1. 可达性分析:首先,JVM使用可达性分析算法(如根搜索算法)来确定对象的可达状态。如果对象不可达,则进入下一个步骤。
  2. 对象 finalize() 方法:如果不可达对象实现了 finalize() 方法,并且尚未被执行过,JVM会将此类对象放入一个名为F-Queue的队列中,由一个低优先级的Finalizer线程去执行这些对象的 finalize() 方法。在 finalize() 方法中,对象有机会重新建立可达性,比如通过把自己赋值给一个全局变量或者其他可达对象的字段,从而达到自我拯救的目的。但这种方式不推荐作为常规手段,因为其执行时机不确定,且只会执行一次。
  3. 二次标记:在一些垃圾回收器(如CMS和G1)中,会进行二次标记的过程。如果 finalize() 执行后对象仍然不可达,则在第二次标记阶段结束时,该对象会被确认为可回收。
  4. 垃圾回收:最终,确认为可回收的对象会在下次垃圾回收时被回收器回收其所占用的内存空间。

需要注意的是,从程序的角度看,垃圾回收的时间点是不确定的,且JVM可能会出于性能优化等原因延迟回收。此外,现代JVM在逐步弱化甚至取消对 finalize() 方法的支持,因为它经常带来不确定性并且可能导致性能问题。例如:有些版本的JDK不支持finalize() 方法;JDK鼓励使用 java.lang.AutoCloseable 接口和 try-with-resources 语句块来执行资源清理操。

补充:try-with-resources语句的语法形式如下:

// resource为实现了AutoCloseable接口或子接口的资源对象
try (resource) { 
    // 使用resource 
} catch (SomeException e) { 
    // 处理异常 
}

try 块结束后,无论是否抛出异常,resource 会在适当的时候自动关闭,释放资源。

5. 如何判断一个常量是无用常量?如何判断一个类是无用类?

废弃常量(无用常量)的判断: 在JDK1.8后,JVM堆中存放字符串常量池,JVM方法区中存放其他的常量池。当一个常量不再有任何强引用指向它时,即认为它是无用的。具体来说,当且仅当以下情况发生时,一个常量可能被视为废弃并可被垃圾回收器回收:

  1. 当常量池中的某个常量不再被任何正在运行的线程所引用,包括静态变量、字符串字面量或其他对它的直接或间接引用。
  2. 如果类加载器已经卸载,那么由该类加载器加载的类中的所有常量都会被认为是废弃的。

无用类(不可触及类)的判断: Java虚拟机在进行类的卸载时,会采用更为严格的条件来判断一个类是否是无用的,通常满足以下所有条件时,JVM可以决定一个类是无用的,并对其进行回收:

  1. 该类的所有实例均已被垃圾回收,即Java堆中不存在该类的任何实例对象
  2. 加载该类的类加载器已经被回收
  3. 该类对应的 java.lang.Class 对象没有在任何地方被引用,既不能通过普通的对象引用访问到,也无法通过反射访问到该类的方法。

在实际应用中,JVM并不会频繁地执行无用类的回收操作,因为确定一个类是否真正无用以及回收过程本身都是有开销的。因此,除非必要或者内存压力较大时,JVM才可能会触发无用类的回收工作。

6. 引用的分类有哪些?区别是什么?

引用的分类可以根据其特性和用途进行不同的划分,常见的引用分类包括以下几种:

  1. 强引用(Strong Reference): 强引用是最常见的引用类型,也是默认的引用类型。当一个对象被一个强引用变量所引用时,即使系统内存不足,垃圾收集器也不会回收该对象。只有当没有任何强引用指向一个对象时,该对象才会被判定为垃圾,可以被垃圾收集器回收。
  2. 软引用(Soft Reference): 软引用是一种比强引用弱化的引用类型。当系统内存充足时,软引用的对象不会被回收。但是当系统内存不足时,垃圾收集器可能会回收软引用的对象,以释放内存供其他对象使用。软引用通常用于实现内存敏感的高速缓存,允许在内存不足时自动释放缓存。
  3. 弱引用(Weak Reference): 弱引用比软引用更弱化。即使系统内存充足,弱引用的对象也可能被垃圾收集器回收。弱引用通常用于实现辅助数据结构,如哈希表的键或者缓存中的值。一般来说,当垃圾收集器执行垃圾回收时,只要发现有弱引用指向的对象,无论系统内存是否充足,都会立即回收弱引用的对象。
  4. 虚引用(Phantom Reference): 虚引用是最弱化的引用类型。虚引用的对象在任何时候都可能被回收,甚至在垃圾收集器回收它之前,虚引用对象的引用队列中都不会收到通知。虚引用通常用于跟踪对象是否已经被垃圾回收,以执行一些必要的清理操作。