zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java GC

JAVA GC
2023-09-11 14:20:29 时间

标记-清除算法

  JVM的GC我了解尚浅。我就写下我知道的吧,首先java并没有采用引用计数的算法,并不是因为引用计数算法无法解决循环引用的问题,通过特殊的算法或弱引用可以用引用计数的算法去解决循环引用的问题,但是JVM并没有采用这种方案。
  JVM采用的是可达性分析的算法。通过四种对象作为GC root,然后从GC root出发看是否能达到某个对象来判断对象是否可回收。这四种GC root分别是:

  1. 本地变量(局部变量)
  2. 活动线程
  3. 静态字段
  4. JNI引用

  这种算法叫做标记-清除Mark and Sweep算法。当然清除并不是真的去擦除内存里的数据,只是标记这块内存是可分配的。

内存分区

  标记清除算法会带来一个问题,被清除的内存区域形成内存碎片。内存碎片零散地分布在整个内存区域,使得大对象不能分配,必须整理为连续的内存区域。但是内存的数据拷贝移动非常耗费性能,为此需要设计一个比较好的算法。
  在长期的实践中,工程师们提出了弱分代假设Weak Generational Hypothesis,弱分代假设包含两个内容:

  1. 大多数对象都会很快被回收,这种对象称为年轻代young generation
  2. 少数对象生存周期特别长,这种称为tenured generation

  这其实是一种唯物辩证法的思想,属于矛盾论里的具体问题具体分析,根据不同的场景选择不同的算法。对于上述两种对象,就必须采用不同的内存碎片整理算法。而Java则采用了这个方案,将堆内存分为以下三个区域:

  1. Young
  2. Tenured
  3. PermGen

  而Young又分为以下三个区域:

  1. Eden
  2. Survivor1
  3. Survivor2

  Eden又分为以下两种区域:

  1. TLAB(Thread Local Allocation Buffer)
  2. 公共区域

标记-复制

  对于Eden区域来说,通过可达性分析存活的对象会标记为存活,然后复制到其中一个Survivor区域,剩余的就不管了,这时候整个Eden都被认为是空闲内存了。对于上层的young里的这三个区域是这样的,那两个survivor区域有时候也叫s0和s1,有时候也叫from和to。不管叫什么吧,to是保持空闲的,垃圾回收时,from里不超过15岁的,全部复制到to,eden里存活的也复制到to。然后from和to角色转换,eden和to变成了空闲。
  15岁是XX:+MaxTenuringThreshold的默认参数。

另外两个区域

  Tenured和PermGen这两个区域垃圾回收特别复杂。此外,JAVA8移除了PermGen区域,取而代之的是元空间Metaspace。元空间没有做限制,物理内存大小就是它的上限,但是也可以加上限制,用-XX:MaxMetaspaceSize=256m。

GC事件

  三种GC事件指的是Minor GC, Major GC和Full GC。Minor GC发生在Young空间的GC事件被成为Minor GC。没有足够空间分配新对象是,Minor GC一定会发生。Minor GC会引发stop-the-world,会挂起线程。Major GC清理Tenured空间。而Full GC清理整个堆。

各种垃圾收集器

  一篇文章里写完各种是不太可能的,那样文章太长了,我只能罗列出来,以后慢慢补充。我计划学完这几种:

  • PS Scavenge
  • ParNew
  • PS MarkSweep
  • ConcurrentMarkSweep
  • G1

  SerialGC过于简单啊,就是一个单线程收集器,只适用于单核CPU,现在大部分计算机是多核CPU,所以这种收集器用的是非常少了。
  Serial收集器分年轻代和老年代两部分,对年轻代使用标记-复制算法,对老年代使用标记-清除-整理算法。激活Serial收集器使用-XX:UseSerialGC选项就可以了。所以我就不为SerialGC单独写笔记了。