一、System.gc()方法理解
1. 实现
public final class System {public static void gc() {Runtime.getRuntime().gc();}}
2. 使用
- 该方法提醒JVM执行GC,无法保证对垃圾收集器的调用。
- 一般不用手动调用。对于特殊场景,比如正在编写一个性能基准,可以手动调用。
/*** {@link System#gc()}方法测试** @author Jinhua* @date 2021/5/1 21:18*/public class SystemGc {public static void main(String[] args) {new SystemGc();// 提醒JVM执行gc方法System.gc();// 加上该方法,一定会执行gc// System.runFinalization();}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("重写了finalize方法");}}
3. 对局部变量调用GC的理解
/*** 局部变量的gc:观察buf数组的空间回收情况<p>* VM参数:<p> * -XX:+PrintGCDetails** @author Jinhua* @date 2021/5/1 21:50*/public class LocalVarGc {public static void main(String[] args) {LocalVarGc gc = new LocalVarGc();gc.gc1();// gc.gc2();// gc.gc3();// gc.gc4();// gc.gc5();}public void gc1() {byte[] buf = new byte[10 * 1024 * 1024];// 不执行GC,在该方法栈,buf还在引用System.gc();}public void gc2() {byte[] buf = new byte[10 * 1024 * 1024];buf = null;// 执行buf空间回收,对象无引用System.gc();}public void gc3() {{byte[] buf = new byte[10 * 1024 * 1024];}// 不执行回收,buf为第二个局部变量System.gc();}public void gc4() {{byte[] buf = new byte[10 * 1024 * 1024];}int value = 10;// 执行buf空间回收。局部变量slot复用,占用索引为1的局部变量System.gc();}public void gc5() {gc1();// 执行了buf空间回收,方法栈桢弹出,作用域失效System.gc();}}
二、内存溢出与内存泄漏
1. 内存溢出
1) 必须满足的条件
- 无空闲空间;
- 垃圾收集器无法提供更多内存。
2) 相关说明
- GC一直在发展,除非占用速度大于回收速度,否则不太容易造成OOM
- 报OOM之前极大概率会执行一次独占式的Full GC(最后的挣扎)
- 在引用机制分析后,JVM会尝试回收软引用指向对象;
- java.nio.Bits.reserveMemory()方法,会调用System.gc()。
- GC不触发的例子:分配一个超过堆大小的对象,JVM可直接判断GC无法解决该问题,直接抛出OOM。
3) 可能原因
- 堆内存设置太小。
- 代码中创建大量大对象,并且长时间不能被垃圾收集器收集。
2. 内存泄漏(Memory Leak)
1) 定义
- 对象不会再被程序使用,GC却无法执行回收。
2) 相关说明
- 不是程序崩溃的直接原因。但会导致内存逐步被蚕食殆尽,最终导致OOM。
- Java的内存泄漏,存储空间不是物理内存,而是虚拟机内存,取决于磁盘交换区设定的大小。
3) 常见场景
- 单例设计模式
生命周期与应用程序一样长。
举例:Runtime类实例对象 - 一些提供close的资源未关闭
- 数据库连接
- 网络连接
- io连接
三、Stop The World——停止所有用户线程,进行垃圾回收
1. 定义
GC事件发生时候,停止用户线程,无任何响应。
2. 说明
- GC完成后恢复,频繁中断会让用户感觉是网速问题,影响体验,所以需要减少STW事件发生。
- 所有GC都有整个事件发生。垃圾回收器越优秀,则STW时间会更短。
- STW是JVM后台自动发起和自动完成的。在用户不可见的情况下挂起用户线程。
- 开发过程中不要用System.gc(),会导致Stop the world。
可达性分析算法场景
- 分析工作必须在一个保证一致性的快照中进行。
- 分析过程若引用关系还在不断变化,分析结果准确性无法保证。
四、垃圾回收的并行与并发
1. 并发(Concurrent)——同一时段内,多个应用或线程切换执行
2. 并行(Parallel)——同一时刻,多个应用都在运行
五、安全点与安全域
1. 安全点(Safe Point)
1) 概述
- 程序执行时,在特定的位置停顿下来开始GC。
- 选取很重要。
- 数量太少会导致GC等待时间过长。
- 数量太多会导致运行时性能问题。
2) 划分依据
指令是否能让程序长时间执行的特性。
- 方法调用点。
- 循环跳转。
- 异常跳转。
3) 如何到达最近安全点
- 抢先式中断(目前JVM采用了该方式)
- 中断所有线程。
- 检查每个线程是否在安全点。
- 对未在安全点的线程,恢复运行到安全点后中断。
- 主动式中断。
- 设置一个中断标志。
- 各个线程运行到安全点时主动轮询标志,
- 标志为真,则将自己进行中断挂起。
2. 安全域(Safe Region)
1) 定义
- 一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始执行GC都是安全的。
2) 问题场景
- 线程处于Sleep或Blocked状态,无法响应JVM的中断请求,不太可能被唤醒并走到安全点。
3) 实际过程
- 线程运行到Safe Region时,首先改变标识,已经入安全区域。这段时间内发生GC,JVM会忽略标识为Safe Region的线程。
- 线程离开Safe Region时,会检查JVM是否已经完成GC。
- 已完成,则继续运行。
- 否则等待,直到收到可以安全离开Safe Region的信号为止。
六、对象引用关系
希望描述这类对象:
- 内存空间足够时,能保留在内存中;
- 进行垃圾回收后,空间还是很紧张,则可以抛弃这些对象。
1. 强引用(Strong Reference)
1) 概念
普遍的代码引用赋值。
public class Main {public static void main(String[] args) {// 强引用Object obj = new Object();}}
2) 说明
- 强引用的对象是可触及的。
- 无论什么情况下,只要强引用关系还存在,垃圾收集器就不会回收。
- 是造成Java内存泄漏的主要原因之一。
2. 软引用(Soft Reference)与 弱引用(Weak Reference)
1) 概念
- 软引用:内存不够时,将这些对象纳入回收范围进行第二次回收。回收后还没有足够内存,再抛出OOM。
- 内存不足才回收。
- 弱引用:被弱引用关联的对象,只能生存到下次垃圾回收之前。
- 与内存情况无关,发现即回收。
2) 使用场景
- 实现内存敏感的缓存。
- 例:Mybatis的一些内部类。
3) 实现
- java.lang.ref.SoftReference类来实现软引用;
- java.lang.ref.WeakReference类来实现虚引用**。
/*** 软引用与弱引用测试:<p> * 1) 软引用:内存不足才回收<p> * 2) 弱引用:发现即回收<p>* -Xms10M -Xmx10M** @author Jinhua* @date 2021/5/2 15:36*/public class SoftWeakRef {public static void main(String[] args) {// 方式一:// SoftReference<User> sUser = new SoftReference<>(new User(1, "name1"));// 方式二:User user = new User(1, "name1");SoftReference<User> sUser = new SoftReference<>(user);user = null;// gc后,能获取到System.gc();System.out.println("sUser.get() = " + sUser.get());// 尝试让内存紧张try {byte[] b = new byte[7 * 1024 * 1024];} catch (Throwable t) {t.printStackTrace();} finally {// 内存紧张时候被回收,输出nullSystem.out.println(sUser.get());}WeakReference<User> wUser = new WeakReference<>(new User(2, "name2"));System.out.println("wUser.get() = " + wUser.get());System.gc();// 无论内存是否充足,执行过GC后都会回收System.out.println("After GC: ");System.out.println("wUser.get() = " + wUser.get());}@AllArgsConstructor@JsonInclude(JsonInclude.Include.NON_NULL)public static class User {private final int id;private final String name;@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}}}
3. 虚引用(Phantom Reference)
- 完全不影响对象的生存时间,也无法通过虚引用获得对象实例。
- 唯一目的是,对象回收跟踪。在对象被回收时,收到系统通知。
/*** 虚引用测试** @author Jinhua* @date 2021/5/2 16:25*/public class PhantomRefObj {/*** 当前类对象*/public static PhantomRefObj pRefObj;/*** 引用队列*/static ReferenceQueue<PhantomRefObj> pRefQueue = null;/*** 检查队列的线程*/public static class CheckQueue extends Thread {@Override@SuppressWarnings("all")public void run() {while (true) {if (pRefQueue != null) {PhantomReference<PhantomRefObj> pObj = null;try {pObj = (PhantomReference<PhantomRefObj>) pRefQueue.remove();} catch (InterruptedException ie) {ie.printStackTrace();}if (pObj != null) {System.out.println("追踪垃圾回收过程成功!");}}}}}@Overrideprotected void finalize() throws Throwable {System.out.println("finalize方法被调用");pRefObj = this;}@SneakyThrowspublic static void main(String[] args) {Thread check = new CheckQueue();// 设置守护线程,仅剩守护线程时候,守护线程也会结束check.setDaemon(true);check.start();pRefObj = new PhantomRefObj();pRefQueue = new ReferenceQueue<>();PhantomReference<PhantomRefObj> phantomRef = new PhantomReference<>(pRefObj, pRefQueue);// 不可获取虚引用中的对象System.out.println("phantomRef.get() = " + phantomRef.get());// 去除强引用pRefObj = null;// 第一次gc,对象可以复活,无法回收该对象System.gc();Thread.sleep(1_000L);String msg = pRefObj == null ? "pRefObj is null." : "pRefObj is available.";System.out.println(msg);// 去除强引用pRefObj = null;// 第二次gc,可以执行回收System.gc();Thread.sleep(1_000L);msg = pRefObj == null ? "pRefObj is null." : "pRefObj is available.";System.out.println(msg);}}
4. 终结器引用(Final Reference)
- 用于实现对象的finalize()方法
- 无需手动编码,其内部配合引用队列使用。
- GC时候,终结器引用入队。由finalizer线程通过终结器引用找到被引用对象,并且调用它的finalize()方法,第二次回收时候,才能回收被引用对象。
