垃圾回收
参考文章:[文章1],[文章2],强烈建议自己看看,文章很多参考内容来自这,自己再做一个总结和补充
前言
Java的垃圾回收器(Garbage Collector, GC)是Java虚拟机(JVM)中自动管理内存的机制。Java语言使用垃圾回收器进行自动内存管理,因此程序员可以不必手动分配和释放内存
垃圾回收器的主要功能是回收无用的Java对象占用的内存空间,将这些空间合理地利用起来。Java的垃圾回收器可归为四类:
Serial收集器(串行 GC ):单线程,适合小型应用,简单、高效,停顿时间短,采用标记清除算法。
Parallel收集器(并行 GC):多线程,适合中型应用,采用标记清除算法,并行垃圾收集。
CMS收集器(并发低暂停收集器):多线程,适合大型应用,使用标记-清除算法,分为四个阶段,可达到低停顿时间。
G1收集器:多线程,适合大型应用,使用标记-整理算法,将堆划分为多个区域(Region),在需要垃圾回收的区域进行回收,可以支持大内存应用。
不同的垃圾回收器针对不同大小、类型、使用方式和性能需求的应用场景,选择合适的垃圾回收器有助于提高应用的性能和稳定性。
正文
垃圾回收器有个很重要的词,**Stop The World**:因为程序在执行GC期间,系统中的所有活动都被阻塞,所以GC又被称为“stop-the-world”场景
> 这就像你在扫地的时候,会命令其他人不要动,不然你一边扫其他人一边乱动,制造新的垃圾,或者不小心把你扫成堆的垃圾踢散,垃圾就很难清扫干净
> 垃圾回收器,到目前为止,还没有出现一个适用于所有场景的完美方案。这是一个三角形关系,由内存占用、吞吐量、停顿时间三者组成,互相拉扯。想内存占用少,吞吐量就低。想吞吐量高,内存占用也得提高。但内存占用大,标记和清理的停顿时间又会变长,这又会影响吞吐量......
> 所以,了解JVM GC的原理,可以了解各种GC回收器非常优秀的设计思路,也可以让我们以后在技术选型上,挑选符合自身需求的回收器
回收哪个区域?
JVM的垃圾回收器主要回收堆内存中的对象。堆内存分为新生代和老年代两个区域。新生代包括Eden区和两个Survivor区。当对象被创建时,它们首先被分配到Eden区。当Eden区满时,会触发一次Minor GC,将存活的对象移到Survivor区。当Survivor区满时,存活的对象会被移动到另一个Survivor区,同时年龄加一。当对象经过多次Minor GC后,如果仍然存活,就会被晋升到老年代。老年代主要存放长时间存活的对象。当老年代满时,会触发一次Major GC,对整个堆内存进行回收(Major GC和Full GC是垃圾回收的两个过程。Major GC只清理老年代的对象,而Full GC则清理整个堆(包括年轻代和老年代)的对象)
除了堆内存的垃圾回收,JVM还会对方法区进行垃圾回收,方法区主要存放类的元数据信息、常量池等
怎么判断对象可以被回收了?
在Java中,判断对象是否可以被回收主要依赖于垃圾回收器的算法和策略。一般来说,以下几种情况下的对象可以被回收:
1. 引用计数法:对象的引用计数为0,表示没有任何引用指向该对象,可以被回收。但是Java中一般不使用引用计数法,因为它无法解决循环引用的问题。
2. 可达性分析算法:从GC Roots(根节点)开始,通过一系列的引用链,判断对象是否可达。如果对象不可达(即无法通过引用链访问到),则可以被回收。GC Roots包括以下几种情况:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(Java Native Interface)引用的对象。
3. 弱引用、软引用、虚引用:这些特殊的引用类型可以用来指示对象的可达性。当一个对象只被弱引用、软引用、虚引用所引用时,即使内存不足,也不会阻止垃圾回收器回收该对象。
需要注意的是,垃圾回收器并不保证一定会回收对象,只是在合适的时机进行回收。具体的回收策略和算法会根据垃圾回收器的实现不同而有所差异
回收算法
Java虚拟机(JVM)的垃圾回收算法决定了对象何时被回收以及如何回收。JVM使用了多种垃圾回收算法来满足不同的需求,主要包括标记-清除算法、复制算法、标记-整理算法和分代回收算法。
(ps:什么是内存碎片?它会带来什么问题?在垃圾回收完成后,会产生很多不连续的内存空间,这就是内存碎片。Java虚拟机对于大对象(例如很长的字符串、byte数组等等),都必须存储在连续的内存空间里,当一个大对象需要进入某个内存空间时,由于内存碎片过多,虽然剩余内存是远大于这个对象所需空间的,但就是找不到一块连续的内存空间来存储它,这会导致提前触发一次Full GC)
1. 标记-清除算法(Mark and Sweep):
- 标记阶段:从根节点开始,遍历对象图,标记所有可达对象。
- 清除阶段:遍历整个堆,清除未被标记的对象。
- 缺点:会产生内存碎片,导致分配大对象时无法找到连续的内存空间。
2. 复制算法(Copying):
- 将堆分为两个区域,一半是活动对象,一半是空闲空间。
- 活动对象存活下来,复制到空闲空间,然后清除原空间中的所有对象。
- 优点:不会产生内存碎片,分配速度快。
- 缺点:只能使用堆空间的一半,适用于存活对象较少的场景。
3. 标记-整理算法(Mark and Compact):
- 标记阶段:从根节点开始,遍历对象图,标记所有可达对象。
- 整理阶段:将所有存活对象向一端移动,然后清理剩余的内存空间。
- 优点:不会产生内存碎片,适用于存活对象较多的场景。
- 缺点:需要移动对象,可能会影响性能。
4. 分代回收算法(Generational Collection):
- 根据对象的存活时间将堆分为多个代(年轻代、老年代等)。
- 年轻代使用复制算法,存活对象复制到老年代。
- 老年代使用标记-整理算法或标记-清除算法。
- 优点:根据对象的特性进行不同的回收策略,提高回收效率。
- 缺点:需要维护多个代,增加了管理开销。
常用的垃圾回收器
Java虚拟机(JVM)提供了多种垃圾回收器,每个版本的JDK可能会引入新的回收器或对现有回收器进行改进。下面是一些常用的垃圾回收器及其在不同JDK版本中的使用情况的详细介绍:
1. Serial收集器(Serial Collector):这是一种单线程的垃圾回收器,使用标记-复制算法。它主要用于小型或简单的应用程序,不适用于多核处理器。在JDK 1到JDK 8版本中,默认的新生代回收器是Serial收集器。
2. Parallel收集器(Parallel Collector):这是一种多线程的垃圾回收器,使用标记-复制算法。它适用于多核处理器,可以并行地进行垃圾回收。在JDK 1到JDK 8版本中,默认的老年代回收器是Parallel Old收集器。
3. CMS收集器(Concurrent Mark Sweep Collector):这是一种并发的垃圾回收器,使用标记-清除算法。它的特点是在垃圾回收过程中与应用程序线程并发执行,减少了停顿时间。在JDK 5到JDK 8版本中,CMS收集器是默认的老年代回收器。
4. G1收集器(Garbage First Collector):这是一种并发的垃圾回收器,使用标记-整理算法。它将堆划分为多个区域,每个区域可以是Eden、Survivor或Old区域。G1收集器的优点是可以在有限的停顿时间内高效地回收大堆内存。从JDK 9版本开始,G1收集器成为默认的垃圾回收器。
5. ZGC收集器(Z Garbage Collector):这是一种低延迟的垃圾回收器,使用标记-整理算法。它的特点是具有非常短的停顿时间,适用于大堆内存和对低延迟有要求的应用程序。ZGC收集器在JDK 11版本中作为实验性特性引入,并在JDK 15版本中成为稳定的垃圾回收器。
6. Shenandoah收集器:这是一种低延迟的垃圾回收器,使用标记-整理算法。它的特点是具有非常短的停顿时间,并且对堆内存的大小没有限制。Shenandoah收集器在JDK 12版本中作为实验性特性引入,并在JDK 15版本中继续作为实验性特性。
需要注意的是,不同版本的JDK可能会有不同的默认垃圾回收器。在JDK 1到JDK 8版本中,默认的新生代回收器是Serial收集器,而默认的老年代回收器是Parallel Old收集器。从JDK 9版本开始,默认的垃圾回收器是G1收集器。
总结来说,Java常用的垃圾回收器包括Serial收集器、Parallel收集器、CMS收集器、G1收集器、ZGC收集器和Shenandoah收集器。从JDK 1到JDK 8版本所使用的默认回收器是Serial收集器和Parallel Old收集器,而从JDK 9版本开始,默认的垃圾回收器是G1收集器。
要设置不同的垃圾回收器,可以使用Java虚拟机(JVM)的启动参数来指定。以下是一些常见的设置方法:
1. 设置新生代回收器:可以使用`-XX:+UseSerialGC`启用Serial收集器,使用`-XX:+UseParallelGC`启用Parallel收集器,使用`-XX:+UseG1GC`启用G1收集器,使用`-XX:+UseZGC`启用ZGC收集器。
例如,要使用Parallel收集器,可以在启动命令中添加`-XX:+UseParallelGC`参数。
2. 设置老年代回收器:可以使用`-XX:+UseParallelOldGC`启用Parallel Old收集器,使用`-XX:+UseConcMarkSweepGC`启用CMS收集器。
例如,要使用CMS收集器,可以在启动命令中添加`-XX:+UseConcMarkSweepGC`参数。
3. 设置默认的垃圾回收器:可以使用`-XX:+UseSerialGC`设置新生代和老年代都使用Serial收集器,使用`-XX:+UseParallelGC`设置新生代和老年代都使用Parallel收集器,使用`-XX:+UseG1GC`设置新生代和老年代使用G1收集器。
例如,要设置默认的垃圾回收器为G1收集器,可以在启动命令中添加`-XX:+UseG1GC`参数。