共计 2246 个字符,预计需要花费 6 分钟才能阅读完成。
JVM GC(垃圾回收机制)
在学习 Java GC 之前,我们需要记住一个单词:stop-the-world。它会出现在任何一种 GC 算法中。stop-the-world 意味着 JVM 因为需要执行 GC 而停止了应用程序的执行。当 stop-the-world 发生时,除 GC 所需的线程外,所有的线程都进入等待的状态,直到 GC 任务完成。
GC 优化很多时候就是减少 stop-the-world 的发生。
JVM GC 回收哪个区域的垃圾?
需要注意的是,JVM GC 只回收堆区和方法区内的对象。而栈区的数据,在超出作用域后会被 JVM 自动释放掉,所以其不在 JVM GC 的管理范围内。
JVM GC 怎么判断对象可以被回收?
- 对象没有引用
- 作用域发生未捕获异常
- 程序在作用域正常执行完毕
- 程序执行了 System.exit()
- 程序发生意外终止(被杀线程等)
在 Java 程序中不能显示的分配和注销缓存,因为这些事情 JVM 都帮我们做了,那就是 GC。
有时候我们可以将相关的对象设置成 null 来试图显式的清除缓存,但是并不是设置成 null 就会一定被标记成可回收,有可能发生逃逸。
将对象设置成 null 至少没有什么坏处,但是使用 System.gc() 便不可取了,使用 System.gc() 时并不是马上执行 GC 操作,而是会等待一段时间,甚至不执行,而且 System.gc() 如果被执行,会触发 Full GC,这非常影响性能。
JVM GC 什么时候执行?
eden 区空间不够存放新对象的时候,执行 Minro GC。升到老年代的对象大于老年代剩余空间的时候执行 Full GC,小于 HandlePromotionFailure 参数时被强制 Full GC。调优主要是减少 Full GC 的触发次数,可以通过 NewRatio 控制新生代转老年代的比例,通过 Max Tenuring Threshold 设置对象进入老年代的年龄阀值。
按代的垃圾回收机制
新生代(Young generation): 绝大多数最新被创建的对象都会被分配到这里,由于大部分在创建后很快变得不可达,很多对象被创建在新生代,然后“消失”。对象从这个区域“消失”的过程我们称为:Minor GC。
老年代(Old generation): 对象没有变得不可达,并且从新生代周期中存活下来,会被拷贝到这里。其区域分配的空间要比新生代多。也正因为其较大的空间,发生在老年代的 GC 次数要比新生代少的多。对象从老年代消失的过程,称之为:Major GC 或者 Full GC。
持久层(Permanent generation)也称为方法区(Method area):用来保存类常量以及字符串常量。注意,这个区域不是用来存储那些从老年代存活下来的对象,这个区域也可能发生 GC。发生在这个区域的 GC 时间也被算为 Major GC。只不过这个区域发生 GC 的条件非常苛刻,必须符合以下三种条件才会被回收:
所有实例被回收
加载该类的 ClassLoader 被回收
CLass 对象无法通过任何途径访问(包括访问)
如果老年代的对象需要引用新生代的对象,会发生什么?
为了解决这个问题,老年代中存在一个 card table,他是一个 512byte 大小的块。所有老年代的对象指向新生代对象的引用都会被记录在这个表中。当针对新生代执行 GC 的时候,只需要查询 card table 来执行是否可以被回收,而不用查询整个老年代。这个 card table 由一个 write barrier 来管理。write barrier 给 GC 带来了很大的性能提升,虽然由此可能带来一些开销,但完全是值得的。
默认的新生代(Young generation)、老年代(Old generation)所占空间比例为 1:2
新生代空间的构成与逻辑
为了更好的理解 GC,我们来学习新生代的构成,它用来保存那些第一次被创建的对象,它被分成三个空间
- 一个伊甸园(Eden)
- 两个幸存者空间(From Survivor、To Survivor)
默认新生代空间分配:Eden:From:To = 8:1:1
每个空间的执行顺序如下:
绝大多数刚刚被创建的对象会存放在伊甸园空间(Eden)
在伊甸园空间执行第一次 GC(Minor GC)之后,存活的对象被移动到其中一个幸存者空间(Survivor)
此后,每次伊甸园空间执行 GC 后,存活的对象会被堆积在同一个幸存者空间。
当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。然后会清空已经饱和的那个幸存者空间。
在以上步骤中重复 N 次(N = MaxTenuringThreshold(年龄阀值设定,默认 15))依然存活的对象,就会被移动到老年代。
从上面的步骤可以发现,两个幸存者空间,必须有一个是保持空的。如果两个两个幸存者空间都有数据,或两个空间都是空的,那一定是你的系统出现了某种错误。
我们需要重点记住的是,对象在刚刚被创建之后,是保存在伊甸园空间的(Eden)。那些长期存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)。
也有例外出现,对于一些比较大的对象(需要分配一块比较大的连续内存空间)则直接进入到老年代。一般在 Survivor 空间不足的情况下发生。
并且 Survivor 并不是永远地要求对象的年龄必须达到了 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 空间中相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。