JVM之GC详解

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/l18637220680/article/details/69332949

GC的概念

Grabage Collection:在系统运行过程中占据空间的无用对象在一定时间范围内被及时清理来保证整个系统有足够的内存空间来运行。java中GC的对象是堆和永久区。

常用的GC算法

引用计数法(reference counting)

概念:对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。
出现的问题:①.引用和去引用伴随加法和减法,影响性能。②.很难处理垃圾对象的循环引用。下面一段代码说明循环引用问题:

package jvm;
/**
 * GC引用计数法Demo
 * @author Administrator
 *
 */
public class JvmGcDemo {
    public static void main(String[] args) {
        GcObject g1 = new GcObject();//栈中创建变量g1指向(引用)new出对象GcObject1(堆中),此时GcObject1的引用计数为1
        GcObject g2 = new GcObject();//栈中创建变量g2指向(引用)new出对象GcObject2(堆中),此时GcObject2的引用计数为1
        g1.o = g2;//GcObject1的引用计数再加1,引用计数=2
        g2.o = g1;//GcObject2的引用计数再加1,引用计数=2
        g1 = null;//GcObject1的引用计数减1,引用计数=1
        g2 = null;//GcObject2的引用计数减1,引用计数=1
    }
}
class GcObject{
    public Object o;
}

看上面代码流程发现GcObject1和GcObject2循环引用,引用计数都为1,最后引用计数法无法回收这两个无用的对象。
在eclipse中初始化配置jvm参数,最大使用内存20M,最小使用内存5M:-Xmx20m -Xms5m;修改上段代码,用System.gc()进行GC,即下面代码:

package jvm;
/**
 * GC引用计数法Demo
 * @author Administrator
 *
 */
public class JvmGcDemo {
    public static void main(String[] args) {
        printMemory(1);//打印内存 free  memory:4.719520568847656M,约5M
        GcObject g1 = new GcObject();//栈中创建变量g1指向(引用)new出对象GcObject1(占用1M堆内存),此时GcObject1的引用计数为1
        GcObject g2 = new GcObject();//栈中创建变量g2指向(引用)new出对象GcObject2(占用1M堆内存),此时GcObject2的引用计数为1
        printMemory(2);//打印内存发现free  memory:2.7194900512695312M = 4.719520568847656M - 2M
        g1.o = g2;//GcObject1的引用计数再加1,引用计数=2
        g2.o = g1;//GcObject2的引用计数再加1,引用计数=2
        g1 = null;//GcObject1的引用计数减1,引用计数=1
        g2 = null;//GcObject2的引用计数减1,引用计数=1
        System.gc();//进行GC
        printMemory(3);//free  memory3:4.922454833984375M 说明GcObject1和GcObject2被回收
    }
    /**
     * 打印内存
     */
    public static void printMemory(int i){
        //打印空闲堆内存
        System.out.println("free memory"+i+":"+Runtime.getRuntime().freeMemory()/1024.0/1024+"M");
        System.out.println("---------------------------->");
    }
}
class GcObject{
    public Object o;
    byte[] by = new byte[1024*1024];//占用1M内存
}

控制台输出:
free memory1:4.75958251953125M
—————————->
free memory2:2.7194137573242188M
—————————->
free memory3:4.922454833984375M
—————————->
发现GC后free memory3比free memory2增加了2M多一点(多出的部分是回收的其他垃圾内存),free memory3与free memory1无明显差别,说明对象GcObject1和GcObject2被回收,这是因为JVM没有采用引用计数法进行回收垃圾。

标记-清除算法(mark-and-sweep)

概念:标记-清除算法是现代垃圾回收算法的思想基础。是指在可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,进行标记-清除算法将垃圾回收,过程分为两个阶段:标记阶段和清除阶段。在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
出现的问题:①.标记和清除过程效率不高 ,而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲。②.标记清除之后会产生大量不连续的内存碎片。失效对象都是随即的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。
这里写图片描述

标记-压缩算法(mark-compact)

概念:标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记。但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端,之后,清理边界外所有的空间。
出现的问题:标记-压缩算法的缺点就是效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。
这里写图片描述

复制算法(copying)

概念:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收.
出现的问题:用空间换取时间,空间浪费,不适用于存活对象较多的场合,如老年代等。
这里写图片描述

分代收集算法(generational collecting)

概念:把对象分为年轻代、年老代、持久代,对不同的生命周期使用不同的算法进行回收,一般新生代对象和小对象适合复制算法,老年代对象和大对象存活适合标记-清理或者标记-压缩算法。
这里写图片描述
分代收集过程

对象的可触及性

可触及的

从根节点可以触及到这个对象。根一般为栈中引用的对象、方法区中静态成员或者常量引用的对象(全局对象)、JNI方法栈中引用对象。

可复活的

一旦所有引用被释放,就是可复活状态,因为在finalize()中可能复活该对象。

不可触及的

在finalize()之后,可能会进入不可触及状态,不可触及的对象不可能复活,可以回收。

GC参数

串行收集器

最古老、最稳定、效率高的收集器,缺点是可能会产生较长时间的停顿,她只使用一个线程去回收,没有办法发挥多核计算机的性能。
-XX:+UseSerialGC
新声代和老年代使用串行回收,新生代使用复制算法、老年代使用标记-压缩算法。
这里写图片描述

并行收集器

ParNew

-XX:UserParNewGC
新生代使用并行回收,老年代使用串行回收。
新生代的的并行回收采用复制算法,多线程回收(多核支持),可以使用XX:ParallelGCThreads限制线程的数量。当然,多线程并不一定快。

Parallel

类似于ParNew,新声代采用复制算法、老年代使用标记-压缩算法,更加关注吞吐量。
-XX:+UseParallelGC 使用Parallel收集器和老年代串行。
-XX:+UserParalleOldGC 使用Paralle收集器老年代并行。
-XX:MaxGCPauseMills 最大停顿时间,单位毫秒,GC尽力保证收回时间不超过设定的值,但不是肯定。
-XX:GCTimeRatio 在0-100中取值,代表垃圾收集时间占总时间的比,默认99,代表最大允许1%的时间做GC。
这里写图片描述

CMS收集器

Concurrent Mark Sweep 并发(与用户线程一起执行)标记清除,这是一个老年代的收集器(新声代使用的是ParNew),GC中应用线程停顿时间比较短,所以并发阶段会降低吞吐量。
-XX:+UseConcMarkSweepGC

CMS收集器GC过程

  1. 初始标记
    根可以直接关联到的对象,速度快
  2. 并发标记(和用户线程一起)
    主要标记过程,标记全部对象
  3. 重新标记
    由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正
  4. 并发清除(和用户线程一起)
    基于标记结果,直接清理对象
    这里写图片描述

CMS收集器特点

  1. 尽可能降低停顿
  2. 会影响系统整体吞吐量和性能。比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半。
  3. 清理不彻底。因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理。
  4. 不能在空间快满时再清理,因为和用户线程一起运行。-XX:CMSInitiatingOccupancyFraction设置触发GC的阈值,如果不幸内存预留空间不够,就会引起concurrent mode failure。

CMS收集器常用参数

-XX:+UseCMSCompactAtFullCollection Full GC,进行一次整理,整理的过程是独占的,停顿时间较长。
-XX:+CMSFullGCsBeforeCompaction 设置几次 Full GC后进行一次一次碎片整理。
-XX:ParallelCMSThreads 设定CMS的线程数量,一般约等于可用CPU的数量,不宜设置太大。

GC的参数整理

-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:SurvivorRatio:设置eden区大小和survivior区大小的比例
-XX:NewRatio:新生代和老年代的比
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParallelGC :在新生代使用并行回收收集器
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:ParallelGCThreads:设置用于垃圾回收的线程数
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:ParallelCMSThreads:设定CMS的线程数量
-XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发
-XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
-XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩
-XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收
-XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收
-XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收

展开阅读全文

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