一、System.gc()方法理解

1. 实现

  1. public final class System {
  2. public static void gc() {
  3. Runtime.getRuntime().gc();
  4. }
  5. }

2. 使用

  • 该方法提醒JVM执行GC,无法保证对垃圾收集器的调用。
  • 一般不用手动调用。对于特殊场景,比如正在编写一个性能基准,可以手动调用。
  1. /**
  2. * {@link System#gc()}方法测试
  3. *
  4. * @author Jinhua
  5. * @date 2021/5/1 21:18
  6. */
  7. public class SystemGc {
  8. public static void main(String[] args) {
  9. new SystemGc();
  10. // 提醒JVM执行gc方法
  11. System.gc();
  12. // 加上该方法,一定会执行gc
  13. // System.runFinalization();
  14. }
  15. @Override
  16. protected void finalize() throws Throwable {
  17. super.finalize();
  18. System.out.println("重写了finalize方法");
  19. }
  20. }

3. 对局部变量调用GC的理解

  1. /**
  2. * 局部变量的gc:观察buf数组的空间回收情况<p>
  3. * VM参数:<p>&emsp;
  4. * -XX:+PrintGCDetails
  5. *
  6. * @author Jinhua
  7. * @date 2021/5/1 21:50
  8. */
  9. public class LocalVarGc {
  10. public static void main(String[] args) {
  11. LocalVarGc gc = new LocalVarGc();
  12. gc.gc1();
  13. // gc.gc2();
  14. // gc.gc3();
  15. // gc.gc4();
  16. // gc.gc5();
  17. }
  18. public void gc1() {
  19. byte[] buf = new byte[10 * 1024 * 1024];
  20. // 不执行GC,在该方法栈,buf还在引用
  21. System.gc();
  22. }
  23. public void gc2() {
  24. byte[] buf = new byte[10 * 1024 * 1024];
  25. buf = null;
  26. // 执行buf空间回收,对象无引用
  27. System.gc();
  28. }
  29. public void gc3() {
  30. {
  31. byte[] buf = new byte[10 * 1024 * 1024];
  32. }
  33. // 不执行回收,buf为第二个局部变量
  34. System.gc();
  35. }
  36. public void gc4() {
  37. {
  38. byte[] buf = new byte[10 * 1024 * 1024];
  39. }
  40. int value = 10;
  41. // 执行buf空间回收。局部变量slot复用,占用索引为1的局部变量
  42. System.gc();
  43. }
  44. public void gc5() {
  45. gc1();
  46. // 执行了buf空间回收,方法栈桢弹出,作用域失效
  47. System.gc();
  48. }
  49. }

二、内存溢出与内存泄漏

1. 内存溢出

1) 必须满足的条件

  1. 无空闲空间;
  2. 垃圾收集器无法提供更多内存。

2) 相关说明

  • GC一直在发展,除非占用速度大于回收速度,否则不太容易造成OOM
  • 报OOM之前极大概率会执行一次独占式的Full GC(最后的挣扎)
    • 在引用机制分析后,JVM会尝试回收软引用指向对象;
    • java.nio.Bits.reserveMemory()方法,会调用System.gc()。
    • GC不触发的例子:分配一个超过堆大小的对象,JVM可直接判断GC无法解决该问题,直接抛出OOM。

3) 可能原因

  1. 堆内存设置太小
  2. 代码中创建大量大对象,并且长时间不能被垃圾收集器收集

2. 内存泄漏(Memory Leak)

1) 定义

  • 对象不会再被程序使用,GC却无法执行回收。

2) 相关说明

  • 不是程序崩溃的直接原因。但会导致内存逐步被蚕食殆尽,最终导致OOM。
  • Java的内存泄漏,存储空间不是物理内存,而是虚拟机内存,取决于磁盘交换区设定的大小。

3) 常见场景

  1. 单例设计模式
    生命周期与应用程序一样长。
    举例:Runtime类实例对象
  2. 一些提供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) 如何到达最近安全点

  1. 抢先式中断(目前JVM采用了该方式)
    • 中断所有线程。
    • 检查每个线程是否在安全点。
    • 未在安全点的线程,恢复运行到安全点后中断。
  2. 主动式中断。
    • 设置一个中断标志
    • 各个线程运行到安全点时主动轮询标志,
    • 标志为真,则将自己进行中断挂起

2. 安全域(Safe Region)

1) 定义

  • 一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始执行GC都是安全的。

2) 问题场景

  • 线程处于Sleep或Blocked状态,无法响应JVM的中断请求,不太可能被唤醒并走到安全点。

3) 实际过程

  1. 线程运行到Safe Region时,首先改变标识,已经入安全区域。这段时间内发生GC,JVM会忽略标识为Safe Region的线程
  2. 线程离开Safe Region时,会检查JVM是否已经完成GC。
    • 已完成,则继续运行。
    • 否则等待,直到收到可以安全离开Safe Region的信号为止。

六、对象引用关系

希望描述这类对象:

  • 内存空间足够时,能保留在内存中;
  • 进行垃圾回收后,空间还是很紧张,则可以抛弃这些对象。

1. 强引用(Strong Reference)

1) 概念

普遍的代码引用赋值。

  1. public class Main {
  2. public static void main(String[] args) {
  3. // 强引用
  4. Object obj = new Object();
  5. }
  6. }

2) 说明

  • 强引用的对象是可触及的
  • 无论什么情况下,只要强引用关系还存在,垃圾收集器就不会回收
  • 是造成Java内存泄漏的主要原因之一

2. 软引用(Soft Reference)与 弱引用(Weak Reference)

1) 概念

  • 软引用内存不够时,将这些对象纳入回收范围进行第二次回收。回收后还没有足够内存,再抛出OOM。
    • 内存不足才回收
  • 弱引用:被弱引用关联的对象,只能生存到下次垃圾回收之前
    • 与内存情况无关,发现即回收

2) 使用场景

  • 实现内存敏感的缓存
    • 例:Mybatis的一些内部类。

3) 实现

  • java.lang.ref.SoftReference类来实现软引用;
  • java.lang.ref.WeakReference类来实现虚引用**。
  1. /**
  2. * 软引用与弱引用测试:<p>&emsp;
  3. * 1) 软引用:内存不足才回收<p>&emsp;
  4. * 2) 弱引用:发现即回收<p>
  5. * -Xms10M -Xmx10M
  6. *
  7. * @author Jinhua
  8. * @date 2021/5/2 15:36
  9. */
  10. public class SoftWeakRef {
  11. public static void main(String[] args) {
  12. // 方式一:
  13. // SoftReference<User> sUser = new SoftReference<>(new User(1, "name1"));
  14. // 方式二:
  15. User user = new User(1, "name1");
  16. SoftReference<User> sUser = new SoftReference<>(user);
  17. user = null;
  18. // gc后,能获取到
  19. System.gc();
  20. System.out.println("sUser.get() = " + sUser.get());
  21. // 尝试让内存紧张
  22. try {
  23. byte[] b = new byte[7 * 1024 * 1024];
  24. } catch (Throwable t) {
  25. t.printStackTrace();
  26. } finally {
  27. // 内存紧张时候被回收,输出null
  28. System.out.println(sUser.get());
  29. }
  30. WeakReference<User> wUser = new WeakReference<>(new User(2, "name2"));
  31. System.out.println("wUser.get() = " + wUser.get());
  32. System.gc();
  33. // 无论内存是否充足,执行过GC后都会回收
  34. System.out.println("After GC: ");
  35. System.out.println("wUser.get() = " + wUser.get());
  36. }
  37. @AllArgsConstructor
  38. @JsonInclude(JsonInclude.Include.NON_NULL)
  39. public static class User {
  40. private final int id;
  41. private final String name;
  42. @Override
  43. public String toString() {
  44. return "User{" +
  45. "id=" + id +
  46. ", name='" + name + '\'' +
  47. '}';
  48. }
  49. }
  50. }

3. 虚引用(Phantom Reference)

  • 完全不影响对象的生存时间,也无法通过虚引用获得对象实例。
  • 唯一目的是,对象回收跟踪。在对象被回收时,收到系统通知。
  1. /**
  2. * 虚引用测试
  3. *
  4. * @author Jinhua
  5. * @date 2021/5/2 16:25
  6. */
  7. public class PhantomRefObj {
  8. /**
  9. * 当前类对象
  10. */
  11. public static PhantomRefObj pRefObj;
  12. /**
  13. * 引用队列
  14. */
  15. static ReferenceQueue<PhantomRefObj> pRefQueue = null;
  16. /**
  17. * 检查队列的线程
  18. */
  19. public static class CheckQueue extends Thread {
  20. @Override
  21. @SuppressWarnings("all")
  22. public void run() {
  23. while (true) {
  24. if (pRefQueue != null) {
  25. PhantomReference<PhantomRefObj> pObj = null;
  26. try {
  27. pObj = (PhantomReference<PhantomRefObj>) pRefQueue.remove();
  28. } catch (InterruptedException ie) {
  29. ie.printStackTrace();
  30. }
  31. if (pObj != null) {
  32. System.out.println("追踪垃圾回收过程成功!");
  33. }
  34. }
  35. }
  36. }
  37. }
  38. @Override
  39. protected void finalize() throws Throwable {
  40. System.out.println("finalize方法被调用");
  41. pRefObj = this;
  42. }
  43. @SneakyThrows
  44. public static void main(String[] args) {
  45. Thread check = new CheckQueue();
  46. // 设置守护线程,仅剩守护线程时候,守护线程也会结束
  47. check.setDaemon(true);
  48. check.start();
  49. pRefObj = new PhantomRefObj();
  50. pRefQueue = new ReferenceQueue<>();
  51. PhantomReference<PhantomRefObj> phantomRef = new PhantomReference<>(pRefObj, pRefQueue);
  52. // 不可获取虚引用中的对象
  53. System.out.println("phantomRef.get() = " + phantomRef.get());
  54. // 去除强引用
  55. pRefObj = null;
  56. // 第一次gc,对象可以复活,无法回收该对象
  57. System.gc();
  58. Thread.sleep(1_000L);
  59. String msg = pRefObj == null ? "pRefObj is null." : "pRefObj is available.";
  60. System.out.println(msg);
  61. // 去除强引用
  62. pRefObj = null;
  63. // 第二次gc,可以执行回收
  64. System.gc();
  65. Thread.sleep(1_000L);
  66. msg = pRefObj == null ? "pRefObj is null." : "pRefObj is available.";
  67. System.out.println(msg);
  68. }
  69. }

4. 终结器引用(Final Reference)

  • 用于实现对象的finalize()方法
  • 无需手动编码,其内部配合引用队列使用。
  • GC时候,终结器引用入队。由finalizer线程通过终结器引用找到被引用对象,并且调用它的finalize()方法,第二次回收时候,才能回收被引用对象。