关于JVM的gc总结

最近在网上看了一些关于Java虚拟机垃圾回收(GC,Garbage Collection)的文章,感觉收获颇丰,故写一篇博客来总结一下:

JVM结构

jvm结构示意图
上图中就是JVM的示意图,从图中我们可以看出JVM主要划分为4个区域:

  1. 类装载器:作用是在JVM启动时或某个Class要运行的时候把类装载到JVM中。
  2. 运行时数据区(内存区域):这是JVM在运行时操作的内存区域,主要可以分为5个区域,如下图所示:
    jvm运行时内存示意图
    (1) 程序计数器(PC):线程私有,用于保存当前线程执行的内存地址。
    (2) Java栈:线程私有,每运行一个方法就创建一个栈帧,用于存储局部变量表、操作栈、方法返回值、常量引用等。
    (3) 本地方法栈:线程私有,与Java栈类似,不过用于执行C/C++的native方法。
    (4) 方法区:线程共享,里面存储了类结构信息、常量池以及静态变量等,一般不进行GC,因此也被称为永久代。方法区还包含一个运行时常量池。
    (5) Java堆:线程共享,存储java实例或者对象的地方,这块是GC的主要区域。
  3. 执行引擎:负责执行class文件中包含的字节码指令
  4. 本地方法接口:主要是调用C/C++实现的本地方法

Java堆中各代分布

上文提到了Java堆是GC的主要区域,我们先来看一下Java堆的结构分布:
java堆结构示意图
Java堆主要分为3代:

  1. 年轻代:这里是所有新对象产生的地方。年轻代被分为3个部分——Enden区(伊甸园区,新对象的出生地,这部分均为连续内存,分配快速)和两个Survivor区(From和to)。当Eden区被对象填满时,就会对Eden区和Survivor From区执行Minor GC(Young GC),并把所有存活下来的对象转移到Survivor To区,然后把from区变成下次GC的to区。这样在一段时间内,总会有一个空的Survivor区。经过多次GC周期后,仍然存活下来的对象会被转移到年老代内存空间。通常这是在年轻代有资格提升到年老代前通过设定年龄阈值来完成的。需要注意,Survivor的两个区是对称的,没先后关系,from和to是相对的。
  2. 年老代:与年轻代相比,年老代里面的对象存活时间较长,大小也较大(较大的对象可能直接进入年老代)。当年老代被空间占满时,会触发Major GC(Full GC),不仅对年老代进行GC,对年轻代和永久代也进行GC,释放掉已经没有被引用的对象。
  3. 永久代:永久代即上文提及的方法区,由于方法区放的都是静态文件,故GC影响并不是很大。

JVM的GC算法

JVM GC的方法是分代分配,分代回收。

  1. 停止复制:对于生命周期较短的年轻代,JVM使用停止复制算法(Stop-and-copy),进行GC时需停止所有应用程序线程,把Eden区和Survivor From区活着的对象复制到Survivor To区(如果放不下会放进年老代),并清空Eden区和Survivor From区。
    停止复制算法示意图
  2. 标记整理:对于对象较多较大的年老代,停止复制算法并不合适,于是我们使用标记整理算法(mark-sweep-compact)来进行年老代的GC。收集器先从根开始访问所有活跃对象,标记为活跃对象。然后再遍历一次整个内存区域,把所有没有标记活跃的对象进行回收处理。清楚以后内存碎片很多,最后将其合并成较大的内存块。
    标记整理算法示意图

JVM的垃圾收集器

JVM的垃圾收集器主要有:
1.串行收集器
2.并行收集器
3.并发收集器
4.增量并发收集器
其中并行指的是暂停用户线程的多线程垃圾收集器,而并发指的是用户线程和垃圾收集线程共同工作的收集器。