什么是垃圾回收
垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,而此时存在一个很大的矛盾,C++ 等语言创建对象要不断的去开辟空间,不用的时候又需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的 allocated,然后不停的析构。于是,有人就提出,能不能写一段程序实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?
1960 年,基于 MIT 的 Lisp 首先提出了垃圾回收的概念,而这时 Java 还没有出世呢!所以实际上 GC 并不是 Java 的专利,GC 的历史远远大于 Java 的历史!
垃圾回收算法
https://blog.csdn.net/weixin_42615068/article/details/102813947
https://www.infoq.cn/article/zoyqri4c-bfkmubmzmkn
- 引用计数算法
- 可达性分析算法
内存
JVM 年轻代和老年代内存默认配置比例?
新生代-Eden 区
IBM 公司的专业研究表明,有将近 98%的对象是朝生夕死,所以针对这一现状,大多数情况下,对象会在新生代 Eden 区中进行分配,当 Eden 区没有足够空间进行分配时,虚拟机会发起一次 Minor GC,Minor GC 相比 Major GC 更频繁,回收速度也更快。
通过 Minor GC 之后,Eden 会被清空,Eden 区中绝大部分对象会被回收,而那些无需回收的存活对象,将会进到 Survivor 的 From 区(若 From 区不够,则直接进入 Old 区)。
在Java虚拟机(JVM)中,Full GC和Major GC是两种不同的垃圾回收(GC)事件,尽管它们有时被混用,但它们在垃圾回收的范围内有所区别:
Major GC:
定义:Major GC通常指的是针对老年代(Old Generation)的垃圾回收。它专注于清理老年代中的不再被引用的对象。
触发条件:Major GC通常在老年代空间不足时触发。在进行Major GC之前,通常会先进行一次Minor GC(年轻代垃圾回收)。
范围:它只清理老年代,不涉及年轻代(Young Generation)和方法区(如永久代或元空间)。
速度:Major GC的速度通常比Minor GC慢,因为它涉及到更多的对象和更复杂的回收算法。
Full GC:
定义:Full GC是指对整个堆内存(包括年轻代、老年代和方法区)的垃圾回收。
触发条件:Full GC可以在多种情况下触发,包括老年代空间不足、方法区空间不足、通过Minor GC后大对象进入老年代且没有足够空间、显式调用System.gc()等。
范围:Full GC清理整个堆内存,包括年轻代、老年代和方法区(如果存在)。
速度:由于Full GC需要处理整个堆内存,它的速度通常是所有GC事件中最慢的,并且可能会导致应用程序的暂停时间显著增加。
区别总结:
回收范围:Major GC只清理老年代,而Full GC清理整个堆内存。
触发条件:Major GC通常由老年代空间不足触发,而Full GC可能由多种原因触发,包括老年代空间不足、方法区空间不足等。
影响:Full GC的影响通常比Major GC更大,因为它需要处理更多的数据,可能会导致更长的暂停时间。
需要注意的是,不同的JVM实现和垃圾回收器可能会有不同的行为,因此“Major GC”和“Full GC”的定义在某些情况下可能会有所不同。此外,随着JVM的发展,一些现代垃圾回收器(如G1)可能不会严格区分这两种类型的GC,因为它们可能会同时回收多个堆区域。
新生代-Survivor 区
Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交通灯中的黄灯。Survivor 又分为 2 个区,一个是 From 区,一个是 To 区。每次执行 Minor GC,会将 Eden 区和 From 存活的对象放到 Survivor 的 To 区(如果 To 区不够,则直接进入 Old 区)。
老年代区
垃圾回收算法
https://cloud.tencent.com/developer/article/2153851
垃圾回收有两种类型:Minor GC 和 Full GC。
-
Minor GC:小量垃圾回收
对新生代进行回收,不会影响到年老代。因为新生代的 Java 对象大多死亡频繁,所以 Minor GC 非常频繁,一般在这里使用速度快、效率高的算法,使垃圾回收能尽快完成。 -
MajorGC:大量垃圾回收
它主要回收老年代的内存空间。老年代是堆空间中存储寿命较长的对象的部分,通常只有在年轻代空间不足时才会触发 MajorGC。 MajorGC 的暂停时间通常比 MinorGC 更长,因为它需要遍历整个老年代空间来查找和回收不再使用的对象。 -
Full GC:全量垃圾回收
也叫 Major GC,对整个堆进行回收,包括新生代和老年代。由于Full GC需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数,导致Full GC的原因包括:老年代被写满、永久代(Perm)被写满和System.gc()被显式调用等。
在Java虚拟机(JVM)中,老年代(Old Generation)和永久代(Permanent Generation)是堆内存中的两个不同区域,它们各自有不同的用途和垃圾回收策略。
老年代(Old Generation):
1. 老年代主要用于存放生命周期较长的对象。这些对象在多次垃圾回收(Minor GC)后仍然存活,从年轻代(Young Generation)晋升到老年代。
2. 老年代的空间通常比年轻代大,因为长期存活的对象通常比短期存活的对象多。
3. 垃圾回收在老年代发生的频率比年轻代低,但每次回收的时间更长,因为它涉及到更多的对象和更复杂的算法(如标记-清除或标记-整理)。
4. 老年代垃圾回收(Major GC)可能会引发全堆垃圾回收(Full GC),对系统性能有一定影响。
永久代(Permanent Generation):
1. 永久代是Java虚拟机在Java 8之前用于存储类的元数据、常量以及方法信息的区域。它属于堆内存的一部分,但它的管理方式和垃圾回收策略与普通对象堆有所不同。
2. 永久代的大小在启动时固定,如果元数据过多,可能导致内存溢出错误(OutOfMemoryError)。
3. 在Java 8及以后的版本中,永久代的概念被元空间(Metaspace)所取代。元空间使用本地内存,而不是虚拟机堆内存,可以动态地调整大小,减少了内存溢出的风险。
区别总结:
- **用途**:老年代用于存放长时间存活的对象,而永久代(或元空间)用于存储类的元数据。
- **垃圾回收**:老年代进行的是Major GC,而永久代(元空间)的垃圾回收通常与类加载器的生命周期相关。
- **内存管理**:老年代是堆内存的一部分,其大小可以通过JVM参数调整;而永久代在Java 8之前有固定的大小限制,Java 8之后被元空间取代,使用本地内存,并且可以动态扩展。
了解这些区别有助于更好地调优JVM参数,优化应用程序的性能。
minorGC 和 Full GC区别
- 新生代 GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC 非常频繁,回收速度一般也比较快。
- 老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC 经常会伴随至少一次的 Minor GC(并非绝对),Major GC 的速度一般会比 Minor GC 的慢 10 倍以上。
老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。一般,老年代用的算法是标记-压缩算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;**如果不允许,则仍然进行Full GC(**这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。
老年代的空间将会被清除和压缩(标记-清除或者标记整理)。
从上面的过程可以看出,Eden区是连续的空间,且Survivor总有一个为空。经过一次GC和复制,一个Survivor中保存着当前还活着的对象,而Eden区和另一个Survivor区的内容都不再需要了,可以直接清空,到下一次GC时,两个Survivor的角色再互换。因此,这种方式分配内存和清理内存的效率都极高,这种垃圾回收的方式就是著名的**“停止-复制(Stop-and-copy)”清理法**(将Eden区和一个Survivor中仍然存活的对象拷贝到另一个Survivor中),这不代表着停止复制清理法很高效,其实,它也只在这种情况下(基于大部分对象存活周期很短的事实)高效,如果在老年代采用停止复制,则是非常不合适的。
老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。一般,老年代用的算法是标记-压缩算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。
https://github.com/guobinhit/cg-blog/blob/master/articles/others/jvm-garbage-collection-mechanism.md
复制算法
复制(Copying Collector)算法的提出是为了克服句柄的开销和解决堆碎片的垃圾回收。它将内存按容量分为大小相等的两块,每次只使用其中的一块(对象面),当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面(空闲面),然后再把已使用过的内存空间一次清理掉。
复制算法比较适合于新生代(短生存期的对象),在老年代(长生存期的对象)中,对象存活率比较高,如果执行较多的复制操作,效率将会变低,所以老年代一般会选用其他算法,如“标记-整理”算法。一种典型的基于复制算法的垃圾回收是stop-and-copy算法,它将堆分成对象区和空闲区,在对象区与空闲区的切换过程中,程序暂停执行。
- 优点:标记阶段和复制阶段可以同时进行;每次只对一块内存进行回收,运行高效;只需移动栈顶指针,按顺序分配内存即可,实现简单;内存回收时不用考虑内存碎片的出现。
- 缺点:需要一块能容纳下所有存活对象的额外的内存空间。因此,可一次性分配的最大内存缩小了一半。
FullGC 为什么会线程暂停?