JVM内存模型与垃圾回收

时间:2022-02-06 00:00:24

一、JVM体系结构

JVM内存模型与垃圾回收

二、JVM Heap Memory

1.新生代(Young Generation)

 - Eden Space

 - Survivor FromSpace (S1)

 - Survivor ToSpace (S2)

备注:Young Generation中的98%的对象都是死朝生夕死,所以将内存分为一块较大的Eden和两块较小的Survivor1、Survivor2,JVM默认分配是8:1:1。

2.老年代(Old Generation)

 - Tenured

3.永久代(Permanent Generation)

 - 包含类、方法等细节的元信息

 - 在Java8中已被移除

JVM内存模型与垃圾回收

三、垃圾回收过程

新生代GC(Minor GC):

垃圾回收算法:Copying

1.一个新创建的对象,首先会被存储在新生代的Eden 中,Eden空间不足时,GC,将存活的对象(仍然被引用的)从 Eden 移动到 Survivor1;

2.Survivor1空间不足时,GC,将Eden和Survivor1中存活的对象,移动到Survivor2,如果Survivor2空间不足,则全部移动到老年代;

3.Survivor2空间不足时,GC,将Eden和Survivor1中存活的对象,移动到Survivor1,如果Survivor1空间不足,则全部移动到老年代;

老年代GC(Major GC / Full GC):

垃圾回收算法:Mark-Compact

触发条件:

1.System.gc(),系统建议执行Full GC,但是不必然执行;

2.老年代空间不足;

相对于 Java 垃圾回收过程,老年代是对象生命周期的最后阶段。Major GC 扫描老年代的垃圾回收过程。如果实例不再被引用,那么它们会被标记为回收,否则它们会继续留在老年代中。

 四、垃圾回收算法

  ①Mark-Sweep(标记-清除)算法

  这是最基础的垃圾回收算法,之所以说它是最基础的是因为它最容易实现,思想也是最简单的。标记-清除算法分为两个阶段:标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。具体过程如下图所示:

  JVM内存模型与垃圾回收

  从图中可以很容易看出标记-清除算法实现起来比较容易,但是有一个比较严重的问题就是容易产生内存碎片,碎片太多可能会导致后续过程中需要为大对象分配空间时无法找到足够的空间而提前触发新的一次垃圾收集动作。 

  ②Copying(复制)算法

  为了解决Mark-Sweep算法的缺陷,Copying算法就被提了出来。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。具体过程如下图所示:

  JVM内存模型与垃圾回收

  这种算法虽然实现简单,运行高效且不容易产生内存碎片,但是却对内存空间的使用做出了高昂的代价,因为能够使用的内存缩减到原来的一半。 很显然,Copying算法的效率跟存活对象的数目多少有很大的关系,如果存活对象很多,那么Copying算法的效率将会大大降低。我们的新生代GC算法采用的是这种算法

  ③Mark-Compact(标记-整理)算法

  为了解决Copying算法的缺陷,充分利用内存空间,提出了Mark-Compact算法。该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。具体过程如下图所示:

  JVM内存模型与垃圾回收

  在一般厂商JVM中老年代GC就是使用的这种算法,由于老年代的特点是每次回收都只回收少量对象。

 五、垃圾回收器

1.串行垃圾回收器(Serial Garbage Collector)

      串行垃圾回收器通过持有应用程序所有的线程进行工作。它为单线程环境设计,只使用一个单独的线程进行垃圾回收,通过冻结所有应用程序线程进行工作,所以可能不适合服务器环境。它最适合的是简单的命令行程序。

2.并行垃圾回收器(Parallel Garbage Collector)

      并行垃圾回收器也叫做 throughput collector 。它是JVM的默认垃圾回收器。与串行垃圾回收器不同,它使用多线程进行垃圾回收。当执行垃圾回收的时候,它也会冻结所有的应用程序线程。

3.并发标记扫描垃圾回收器(CMS Garbage Collector)

      并发标记垃圾回收使用多线程扫描堆内存,标记需要清理的实例并且清理被标记过的实例。并发标记垃圾回收器只会在下面两种情况持有应用程序所有线程。

      - 当标记的引用对象在tenured区域;

      - 在进行垃圾回收的时候,堆内存的数据被并发的改变。

      相比并行垃圾回收器,并发标记扫描垃圾回收器使用更多的CPU来确保程序的吞吐量。如果我们可以为了更好的程序性能分配更多的CPU,那么并发标记上扫描垃圾回收器是更好的选择相比并发垃圾回收器。

      通过JVM参数 XX:+USeParNewGC 打开并发标记扫描垃圾回收器。

4.G1垃圾回收器(G1 Garbage Collector)

      G1垃圾回收器适用于堆内存很大的情况,他将堆内存分割成不同的区域,并且并发的对其进行垃圾回收。G1也可以在回收内存之后对剩余的堆内存空间进行压缩。并发扫描标记垃圾回收器在STW情况下压缩内存。G1垃圾回收会优先选择第一块垃圾最多的区域

      通过JVM参数 –XX:+UseG1GC 使用G1垃圾回收器

Java 8 的新特性

      在使用G1垃圾回收器的时候,通过 JVM参数 -XX:+UseStringDeduplication 。 我们可以通过删除重复的字符串,只保留一个char[]来优化堆内存。这个选择在Java 8 u 20被引入。

JVM内存模型与垃圾回收

 六、垃圾回收的JVM配置

运行的垃圾回收器类型

配置 描述
-XX:+UseSerialGC 串行垃圾回收器
-XX:+UseParallelGC 并行垃圾回收器
XX:ParallelGCThreads=

并行垃圾回收器,线程数

-XX:+UseConcMarkSweepGC

并发标记扫描垃圾回收器

-XX:ParallelCMSThreads= 并发标记扫描垃圾回收器,线程数
-XX:+UseG1GC G1垃圾回收器

GC的优化配置

配置 描述
-Xms 初始化堆内存大小
-Xmx 堆内存最大值
-Xmn 新生代大小
-XX:PermSize 初始化永久代大小
-XX:MaxPermSize 永久代最大容量

 

参考文章

1.https://www.cnblogs.com/wjtaigwh/p/6635484.html

2.http://www.importnew.com/13827.html