JVM GC策略

静态内存
  在Java中静态内存分配是指在Java被编译时就已经能够确定需要的内存空间,当程序被加载时系统把内存一次性分配给它。这些内存不会在程序执行时发生变化,直到程序执行结束时内存才会被回收。在Java的类和方法中的局部变量包括原生数据类型和对象的引用都是静态分配内存的。静态内存空间实在Java栈上分配的,当方法运行结束时,对应的栈帧也就撤销了,所以分配的静态内存空间也就被回收了。

// 形参 arg 为基础数据类型int  4个字节
public void test(int arg) {
    // 对象引用  4个字节(对象引用一律为4个字节表示内存地址)
    String s = "helloword";
    // 基础数据类型 long 8个字节
    long l1 = 1;
    // 对象引用 4个字节
    Long l2 = 1L;
}

动态内存
  除了Java基础数据类型,其他都是对象类型,它们存储在Java堆中,可以被共享,不一定随着方法执行结束而消失。对于对象类型,在Java栈中会分配一个4字节的地址指针空间(引用),这个地址指针指向该对象在堆中的地址。
  对象的内存空间是动态分配的,即只有在程序执行时才知道要分配的存储空间大小,这个对象什么时候被回收也是不确定的,只有等到这个对象不再使用时才会被回收。动态内存的分配和回收是一个值得讨论的问题,下面就看看垃圾收集器(下面简称GC)是怎样来解决堆内存回收的。

检测垃圾
  GC的第一个使命就是正确地检测出垃圾对象,这也是关键点。先给出垃圾对象的定义:不能够被一个根对象集合到达的对象。根对象集合和JVM的具体实现有关系,但大都包含如下元素:
1) 在方法中局部变量区的对象引用
2)在Java操作栈中的对象引用
3)在常量池中的对象引用
4)在本地方法中持有的对象引用:有些对象被传入本地方法中,但是这些对象还没有被释放
5)类的Class对象:每个类被JVM加载时都会创建一个代表这个类的唯一数据类型的Class对象,而中国Class对象也同样存放在堆中,当这个类不再被使用时,在方法区中类数据和这个Class对象同样需要被回收
  对于引用,补充说明一点。上面说的引用指的是强引用,在Java中有四种引用类型,如下:
1)强引用:是指创建一个对象并把这个对象赋给一个引用变量, 强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
2)软引用:如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存,比如网页缓存、图片缓存等。使用软引用能防止内存泄露,增强程序的健壮性。
3)弱引用:弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
4)虚引用:虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。在java中用java.lang.ref.PhantomReference类表示。

基于分代的垃圾收集算法
  目前使用最广泛垃圾收集算法是hotspot VM(sun JDK和open JDK使用的虚拟机)中使用的基于分代的垃圾收集算法。该算法思路是这样的:把对象按照寿命长短来分组,分为年轻代和老年代,新创建的对象被分在年轻带中,如果对象经过几次回收后仍然存活,就把这个对象划分到老年代,老年代的收集操作不像年轻代那么频繁,这样就减少了每次垃圾收集时所要扫描的对象的数量,从而提高了垃圾回收效率。


这里写图片描述

如上图所示,JVM将整个堆划分为Young区、Old区和Perm区,分别存放不同年龄的对象,这三个区存放的对象有如下区别:

  • Young:分为Eden区和两个Survivor区,其中所有新创建的对象都在Eden区,当Eden区满后会出发minor GC将Eden区仍然存好的对象复制到其中一个Survivor区中,另外一个Survivor区中的存活对象也复制到这个Survivor中,以保证始终有一个Survivor区是空的。一般情况下,Young区的大小为整个堆区的1/4,Survivor区为整个Young区的1/8
  • Old区:存放Young区的Survivor满后出发minor GC后仍然存活的对象,当Eden区满后会将对象存放到Survivor区中,如果Survivor区仍然存不下这些对象,GC收集器会将这些对象直接存放到Old区。如果在Survivor区中的对象足够老,也直接存放到Old区。如果Old区也满了,将会触发Full GC,回收整个堆内存
  • Perm区:存放的主要是类的Class对象,如果一个类被频繁的加载,也可能会导致Perm区满,Perm区的垃圾回收也是由Full GC触发的

下面看Hotspot提供的三类具体的垃圾收集算法
Serial Collector
  JVM在client模式下默认的GC方式。可以通过-XX:+UseSerialGC来指定使用该收集算法,该算法是串行的,GC动作是单线程完成,因此GC时其他应用程序会全部停止(所谓的“stop-the-world”)。
  当Minor GC时,除了将Eden区的非活动对象回收以外,还会把一些老对象也复制到Old区中。这个老对象的定义是通过配置参数MaxTenuringThreshold来控制的,如-XX:MaxTenuringThreshold=5,则如果这个对象已经被Minor GC回收过5次后仍然存活,那么这个对象在这次Minor GC后会直接被放入Old区。还有一种情况,当这次Minor GC时Survivor区中的To Space放不下这些对象时,这些对象也将直接放入Old区。如果Old区或者Perm区空间不足,将会触发Full GC,Full GC会检查堆中的所有对象,清除所有垃圾对象,如果是Perm区,会清除已经被卸载的classloader中加载的类的信息。

Parallel Collector
根据Minor GC 和 Full GC的不同分为三种。
1)ParNewGC
  回收策略和Serial Collector类似,只是回收操作是多线程并行回收,但同样会导致“stop-the-world”。通过-XX:+UseParNewGC来指定,通过UseAdaptiveSizePolicy来配置Eden、From Space和To Space的TenuringThreshold大小。
2)ParallelGC
  在Server下默认的GC方式,通过-XX:+UseParallelGC参数来指定,并行回收的线程数由-XX:ParallelGCThreads指定(如果CPU的核数小于8,线程数可以和核数一样,如果大于8,值为3 + core * 5 / 8)。Eden、From Space和To Space的大小通过SurvivorRatio来控制(-XX:SurvivorRatio=8表示Eden区和FromSpace的大小为8:1,默认情况就是这个值)。
  当在Eden区中申请内存空间时,如果Eden区不够,那么看当前申请的空间是否大于等于Eden的一半,如果大于则这次申请的空间直接在Old中分配,如果小于则触发Minor GC。在触发GC之前首先会检查每次晋升到Old区的平均大小是否大于Old区的剩余空间,如大于则再触发Full GC。这次触发GC后会按照这个规则再重新检查一次,也就是Full GC会执行两次。Full GC时清空堆中所有的垃圾对象和Perm区中已经被卸载的类信息,并进行压缩。这里有几个配置项了解一下
AlwaysTenure:默认为flase,表示只要Minor GC时存活就晋升到Old区
NeverTenure:默认为false,表示永远晋升到Old区
UseAdaptiveSizePolicy:上面的两个都没设置时,使用该配置项,在启动时以InitialTenuringThreshold值作为存活次数的阈值,每次GC后会动态调整。-XX: - UseAdaptiveSizePolicy表示不使用该配置
3)ParallelOldGC
  通过-XX:+UseParallelOldGC参数来指定,并行回收的线程数由-XX:ParallelOldGCThreads指定(如果CPU的核数小于8,线程数可以和核数一样,如果大于8,值为3 + core * 5 / 8)。和ParallelGC只有一点不同,Full GC时清空堆中部分的垃圾对象,进行部分的空间压缩,并不是所有垃圾对象。

CMS Collector
  通过-XX:+UseConcMarkSweepGC参数来指定,并发回收的线程数由-XX:ParallelCMSThreads指定(默认值为4)。该收集算法因为是并发的,所以不会导致“stop-the-world”,这一点在很多场景下是很有用的,例如WebServer,不会导致用户无法访问页面。触发规则为检查Old区或者Perm区的使用率,当达到一定比例时(CMSInitialOccupancyFraction指定)触发CMS GC,触发时会回收Old区中的内存空间。触发Full GC的两种情况:
1)Eden分配失败,Minor GC后分配到To Space,ToSpace不够再分配到Old区,Old区不够再出发Full GC
2)当CMS GC正在进行时向Old申请内存失败则会直接触发Full GC。

三种GC对比:

GC 有点 缺点
Serial(串行) 适合内存有限的情况下 回收慢
Parallel(并行) 效率高 当Heap过大时,应用程序暂停时间较长
CMS(并发) Old区回收暂停时间短 产生内存碎片、整个GC耗时长、比较耗CPU
阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012484172/article/details/79973435
文章标签: JVM GC
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭