JVM核心知识-GC知识

Java开发相对于C语言最方便的点,就是代码上不需要主动去管理内存的回收,而由JVM负责分配回收。

GC算法

标记清除算法(Mark-Sweep)

标记出所有存活的内存对象,当垃圾回收时只清除未标记的对象。
缺点:回收的空间不连续,可能导致创建对象时,虽然未使用空间足够,但连续的空间不足无法创建。

复制算法(Copying)

将空间分为A、B两块。对象在A块创建。当A空间满了,垃圾回收,将A中存活的对象复制到B中,清空A空间。之后在B空间中尽享相同的逻辑。
优点:合理规划了空间的连续性,每次回收都是整块内存的回收。
缺点:将内存分为了两块,每次只能用一半的内存。

标记整理算法(Mark-Compact)

结合标记清除和标记复制的缺点,为了优化算法。提出了标记整理算法。对象的回收依然使用标记方式,在垃圾回收时,把存活的对象整理到内存一端,将其他内存回收。这样做的的好处时腾出了一整块连续的空间。
优点:创造了连续的空间环境
缺点:整理耗时,效率有所损失

分代收集算法(Generational Collection)

根据对象的存活特性,将内存分为几个区域,分别对不同区域进行不同的回收算法。目前大部分的JVM都采用分代收集算法。常见的将堆分为年轻代、老年代,以及堆外的永久代。老年代存放的对象一般回收较少,年轻代回收的对象相对更多。一般对象多数是”朝生夕死“,即创建完很快就会被回收。针对这些区域的对象特性采用不同的收集算法,提高内存利用率。
例如:以hotspot为例,堆(heap)区域内存划分为年轻代、老年代、永久代,年轻代又分为Eden、From Survivor、To Survivor。


JVM核心知识-GC知识_第1张图片
image.png

年轻代采用标记复制算法,对象的创建在Eden区进行,当Eden区进行垃圾回收时,将Eden存活对象复制到From Survivor区,清空Eden。当From Survivor也满了,将Eden区和From Survivor存活的对象复制到To Survivor区,清空Eden和From Survivor。(之后From Survivor与To Survivor身份交换)。老年代采用标记整理算法,将存活的对象整理到一端,腾出连续的空间,所以FullGC更耗时,需要避免GC频繁。

GC收集器

Serial/Serial Old收集器

Serial收集器是单线程收集器,是分代收集器。年轻代使用单线程复制收集算法(Serial收集器),老年代使用单线程标记整理算法(Serial Old收集器)。
进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束(Stop TheWorld)。
参数:-XX:+UseSerialGC
特点:单线程没有多线程切换的开销。

ParNew 收集器

相当于Serial收集器的多线程版本。年轻代使用并行复制收集算法(ParNew 收集器),老年代使用单线程标记整理算法(Serial Old收集器)。
参数:
-XX:+UseConcMarkSweepGC":指定使用CMS后,会默认使用ParNew作为新生代收集器;
-XX:+UseParNewGC":强制指定使用ParNew;
-XX:ParallelGCThreads":指定垃圾收集的线程数量,ParNew默认开启的收集线程与CPU的数量相同;
特点:ParNew收集器在单CPU环境中不比Serial效果好,甚至可能更差,两个CPU也不一定跑的过,但随着CPU数量的增加,性能会逐步增加。

Parallel Scavenge/Parallel Old收集器

JDK8默认收集器。
同样是并发收集器,Parallel Scavenge收集器的关注点与其他收集器不同, Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量(Throughput)(吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间))
Parallel Old是Parallel老年代收集器,使用标记整理算法。
jdk1.6之前老年代只能使用Serial Old收集器。
参数:

  • -XX:MaxGCPauseMillis:控制最大垃圾收集停顿时间
  • -XX:GCTimeRatio:设置吞吐量大小
  • -XX:+UseAdaptiveSizePolicy:GC自适应的调节策略(GC Ergonomics),当这个参数打开之后,无需手动指定年轻代空间大小,也不需要设置Survivor对象年龄,虚拟机会根据运行情况,动态调整这些参数值。
    特点:能够控制GC的吞吐量,自适应调整。

CMS 收集器(ConcurrentMarkSweep)

CMS是老年代垃圾收集器,在收集过程中可以与用户线程并发操作。它可以与Serial收集器和Parallel New收集器搭配使用。CMS牺牲了系统的吞吐量来追求收集速度,适合追求垃圾收集速度的服务器上。
参数:-XX:+UseConcMarkSweepGC来开启CMS。
特点:并发收集、降低停顿时间

CMS收集器GC的过程

  • 初始标记:标记GCRoot关联对象。会触发Stop The World
  • 并发标记:进行GC Roots Tracing的过程。
  • 重新标记:处理因为初始标记过程中用户线程创建对象而没有处理的对象标记。
  • 并发清除:标记清除算法,清除垃圾。吃阶段可能产生浮动垃圾。

G1收集器

G1收集器的设计将整个堆内存分为多个大小相等的块(Region)。任然沿用年轻代、老年代的概念,但相对之前的空间连续,换成了多个Region的集合。
G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的来由)。
G1收集器GC的过程

  • 初始标记(Initial Marking) 仅仅只是标记一下GC Roots 能直接关联到的对象,并且修改TAMS(Nest Top Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建对象,此阶段需要停顿线程,但耗时很短。
  • 并发标记(Concurrent Marking) 从GC Root 开始对堆中对象进行可达性分析,找到存活对象,此阶段耗时较长,但可与用户程序并发执行。
  • 最终标记(Final Marking) 为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中,这阶段需要停顿线程,但是可并行执行。
  • 筛选回收(Live Data Counting and Evacuation) 首先对各个Region中的回收价值和成本进行排序,根据用户所期望的GC 停顿时间来制定回收计划。
  • 官方建议使用G1,应用内存最少分配6G

ZGC

JDK11提供的新GC收集器,暂时了解不多,号称gc耗时能在10ms内。

GC时机

  • 年轻代的GC被称为Young GC或者Minor GC
  • 对老年代的GC称为Major GC
  • 整堆包括新生代和老年代的垃圾回收称为Full GC(HotSpot VM里,除了CMS之外,其它能收集老年代的GC都会同时收集整个GC堆,包括新生代)。

分代回收:

  • 新对象在Eden Space创建内存分配。当Eden Space满了,这时新对象的创建由于空间不足,会触发Young GC。将Eden+From Survivor进行垃圾回收,将存活的对象复制到To Space空间,记录对象的GC次数,清理Eden和From Survivor,这时From Survivor和To Survivor交换身份,这样保证一直有一个Survivor空间为空。
  • 当Young GC时,发现To Survivor空间不足以对象的复制,则将To Survivor和Eden存活的对象复制到老年代。
  • 当对象经过多次Young GC后任然存活,将对象复制到老年代。
  • 当老年代也满了,则进行Full GC。
  • System.gc()
  • heap dump,默认触发 Full GC.

什么对象属于垃圾

引用计数法

即对象被其他对象引用则计数器计数加一,以计数器为0的对象视为可回收对象。目前主流的JVM已经不使用计数法。原因:需要维护对象引用数数数据,影响性能。另外存在相互引用情况无法回收对象。

GCRoot可达性分析

将不再被GCRoots引用的对象进行回收,即不在GCRoots引用路径上的对象。
而JVM中可以被当做GCRoot的对象包括

  • java虚拟机栈(栈帧中的本地变量表 - Local Variables)中的引用的对象。(也可认为是线程)
  • 方法区中的类静态属性引用的对象。
  • 方法区中的常量引用的对象。
  • 本地方法栈中JNI本地方法的引用对象。

你可能感兴趣的:(JVM核心知识-GC知识)