大厂面试必备:JVM- 垃圾回收机制—垃圾回收中的概念
public class SystemGCTest {public static void main(String[] args) {new SystemGCTest();System.gc();//提醒 jvm 的垃圾回收器执行 gc,但是不确定是否马上执行 gc//与 Runtime.getRuntime().gc();的作用一样。System.runFinalization();//强制调用 失去引用的对象的 finalize()方法}
@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("SystemGCTest 重写了 finalize()");}}
如果注掉 System.runFinalization(); 那么控制台不保证一定打印,
从而证明了 System.gc()无法保证 GC 一定执行.
二、JVM 内存溢出与内存泄漏
1.内存溢出与内存泄漏
内存溢出相对于内存泄漏来说,尽管更容易被理解,但是同样的,内存溢出也是引发程序崩溃的罪魁祸首之一。
由于 GC 一直在发展,所有一般情况下,除非应用程序占用的内存增长速度非常快,造成垃圾回收已经跟不上内存消耗的速度,否则不太容易出现 OOM 的情况。
大多数情况下,GC 会进行各种年龄段的垃圾回收,实在不行了就放大招,来一次独占式的 Full GC 操作,这时候会回收大量的内存,供应用程序继续使用。
javadoc 中对 OutOfMemoryError 的解释是,没有空闲内存,并且垃圾收集器也无法提供更多内存(意思是即使 GC 之后,也无法提供空闲的内存)。
2.内存溢出
首先说没有空闲内存的情况:说明 Java 虚拟机的堆内存不够。原因有二:
Java 虚拟机的堆内存设置不够。比如:可能存在内存泄漏问题;也很有可能就是堆的大小不合理,比如我们要处理比较可观的数据量,但是没有显式指定 JVM 堆大小或者指定数值偏小。我们可以通过参数一 Xms、一 Xmx 来调整。
代码中创建了大量大对象,并且长时间不能被垃圾收集器收集(存在被引用)对于老版本的 Oracle JDK,因为永久代的大小是有限的,并且 JVM 对永久代垃圾回收(如,常量池回收、卸载不再需要的类型)非常不积极,所以当我们不断添加新类型的时候,永久代出现 OutOfMemoryError 也非常多见,尤其是在运行时存在大量动态类型生成的场合;类似 intern 字符串缓存占用太多空间,也会导致 0OM 问题。对应的异常信息,会标记出来和永久代相关: "java. lang. OutOfMemoryError: PermGen space"。随着元数据区的引入,方法区内存已经不再那么窘迫,所以相应的 00M 有所改观,出现 00M,异常信息则变成了:“java. lang. OutOfMemoryError: Metaspace"。 直接内存不足,也会导致 0OM。
这里面隐含着一层意思是,在抛出 0utOfMemoryError 之 前,通常垃圾收集器会被触发,尽其所能去清理出空间。
例如:在引用机制分析中,涉及到 JVM 会去尝试回收软引用指向的对象等。
在 java.nio.BIts.reserveMemory()方法中,我们能清楚的看到,System.gc()会被调用,以清理空间。
当然,也不是在任何情况下垃圾收集器都会被触发的
比如,我们去分配一个超大对象,类似一个超大数组超过堆的最大值,JVM 可以判断出垃圾收集并不能解决这个问题,所以直接拋出 OutOfMemoryError
3.内存泄漏(Memory Leak)
也称作“存储渗漏”。严格来说,只有对象不会再被程序用到了,但是 GC 又不能回收他们的情况,才叫内存泄漏。
但实际情况很多时候一些不太好的实践(或疏忽)会导致对象的生命周期变得很长甚至导致 OOM,也可以叫做宽泛意义上的“内存泄漏。
尽管内存泄漏并不会立刻引起程序崩溃,但是一旦发生内存泄漏,程序中的可用内存就会被逐步蚕食,直至耗尽所有内存,最终出现 Out
OfMemory 异常,导致程序崩溃。
注意,这里的存储空间并不是指物理内存,而是指 Java 层面 JVM 虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。
Forgotten Reference:由于某种原因没有/忘记断开的引用,导致内存泄漏。
三、STW-stop the world
Stop 一 the 一 World,简称 STW,指的是 Gc 事件发生过程中,会产生应用程序的停顿。停顿产生时整个应用程序线程都会被暂停,没有任何响应,有点像卡死的感觉,这个停顿称为 STW。.
1.举例:
可达性分析算法中枚举根节点(GC Roots)会导致所有 Java 执行线程停顿。
2.停顿的原因
分析工作必须在一个能确保一致性的快照 中进行
一致性指整个分析期间整个执行系统看起来像被冻结在某个时间点上
如果出现分析过程中对象引用关系还在不断变化,则分析结果的准确性无法保证
3.示例代码:
被 STW 中断的应用程序线程会在完成 GC 之后恢复,频繁中断会让用户感觉像是网速不快造成电影卡带一样, 所以我们需要减少 STW 的发生。
STW 事件和采用哪款 GC 无关,所有的 GC 都有这个事件。
哪怕是 G1 也不能完全避免 Stop 一 the 一 world 情况发生,只能说垃圾回收器越来越优秀,回收效率越来越高,尽可能地缩短了暂停时间。
STW 是 JVM 在后台自动发起和自动完成的。在用户不可见的情况下,把用户正常的工作线程全部停掉。
开发中采用 System.gc();会导致 Stop 一 the 一 world 的发生。
public class StopTheWorldDemo {public static class WorkThread extends Thread {List<byte[]> list = new ArrayList<byte[]>();
public void run() {try {while (true) {for(int i = 0;i < 1000;i++){byte[] buffer = new byte[1024];list.add(buffer);}
if(list.size() > 10000){list.clear();System.gc();//会触发 full gc,进而会出现 STW 事件}}} catch (Exception ex) {ex.printStackTrace();}}}
public static class PrintThread extends Thread {public final long startTime = System.currentTimeMillis();
public void run() {try {while (true) {// 每秒打印时间信息 long t = System.currentTimeMillis() - startTime;System.out.println(t / 1000 + "." + t % 1000);Thread.sleep(1000);}} catch (Exception ex) {ex.printStackTrace();}}}
public static void main(String[] args) {WorkThread w = new WorkThread();PrintThread p = new PrintThread();w.start();p.start();}}
输出:0.00.72.83.104.115.136.14
四、垃圾回收行为的并行与并发
1.程序的并行和并发
1.程序的并发(Concurrent)
在操作系统中,是指一个时间段中有几个程序都处于己启动运行到运行完毕之间,且这几个程序都是在同一个处理器_上运行。
并发不是真正意义上的“同时进行”,只是 CPU 把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换,由于 CPU 处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行。
2.程序的并行(Parallel)
当系统有一个以上 CPU 时,当一个 CPU 执行一个进程时,另一个 CPU 可以执行另一个进程,两个进程互不抢占 CPU 资源,可以同时进行,我们称之为并行(Parallel)。
其实决定并行的因素不是 CPU 的数量,而是 CPU 的核心数量,比如一个 CPU 多个核也可以 并行。
适合科学计算,后台处理等弱交互场景
3.对比
并发,指的是多个事情,在同一时间段内同时发生了。
并行,指的是多个事情,在同一时间点上同时发生了。
并发的多个任务之间是互相抢占资源的。
并行的多个任务之间是不互相抢占资源的。
只有在多 CPU 或者一个 CPU 多核的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。
2.垃圾回收的并发与并行
1.并行(Parallel)
指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
如 ParNew、 Parallel Scavenge、 Parallel 0ld;
2.串行(Serial)
相较于并行的概念,单线程执行。
如果内存不够,则程序暂停,启动 JVM 垃圾回收器进行垃圾回收。回收完,再启动程序的线程。
3.并发(Concurrent)
指用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾回收线程在执行时不会停顿用户程序的运行。
用户程序在继续运行,而垃圾收集程序线程运行于另一个 CPU 上。
如: CMS、G1
五、垃圾回收安全点 Safe Point
1.GC 安全点(Safepoint)
程序执行时并非在所有地方都能停顿下来开始 GC,只有在特定的位置才能停顿下来开始 GC,这些位置称为“安全点(Safepoint) ”
Safe Point 的选择很重要,如果太少可能导致 GC 等待的时间太长,如果太频繁可能导致运行时的性能问题。大部分指令的执行时间都非常短暂,通常会根据“是否具有让程序长时间执行的特征”为标准。比如:选择些执行时间较长的指令作为 Safe Point, 如方法调用、循环跳转和异常跳转等。
1.如何在 GC 发生时,检查所有线程都跑到最近的安全点停顿下来呢?
抢先式中断: (目前没有虚拟机采用了) 首先中断所有线程。如果还有线程不在安全点,就恢复线程,让线程跑到安全点。
主动式中断: 设置一个中断标志,各个线程运行到 Safe Point 的时候主动轮询这个标志,如果中断标志为真,则将自己进行中断挂起。
2.安全区域(Safe Region)
Safepoint 机制保证了程序执行时,在不太长的时间内就会遇到可进入 GC 的 Safepoint
但是,程序“不执行”的时候呢?例如线程处于 Sleep 状态或 Blocked 状态,这时候线程无法响应 JVM 的中断请求,“走” 到安全点去中断挂起,JVM 也不太可能等待线程被唤醒。对于这种情况,就需要安全区域(Safe Region)来解决。
评论