JVM --- GC(Garbage Collection)

  微软将运行在公共语言运行时里的代码成为托管代码;但是从更广泛的意义上来说,只要语言提供了自动内存管理功能,我们使用其开发的代码都可以称为托管代码;自动内存管理即我们平时所说的垃圾回收器,垃圾回收器的实现是一个复杂的过程,其中涉及到很多的细节;垃圾回收器的难点并不是垃圾的回收过程,而是定位垃圾对象。当一个对象不再被引用的时候就可以被回收了

一,判断对象已死

在堆里面存放着各种各类的Java对象,垃圾收集器在对堆进行垃圾回收时,首要的是判断哪些对象还活着,哪些对象已经死去(即不被任何途径引用的对象)。

1.引用计数器法

给对象中添加一个引用计数器,每当有一个地方引用它时 ,计数器就加1;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不可能再被使用的。

优点:客观的说,引用计数器的实现简单,判断效率也高,大部分场景下是一个不错的选择

缺点:很难解决对象之间相互循环调用的情况

public class ReferenceCountDemo{

private ReferenceCountDemo instance = null;
private int _1m = 1024*1024;
private byte[] bytes = new byte[_1m*2];

public static void testGC(){
    ReferenceCountDemo objA = new ReferenceCountDemo();
    ReferenceCountDemo objB = new ReferenceCountDemo();
    objA = objB;
    objB = objA;
    System.gc();
}
}

这种A调用B,B调用A 就难以计数,会进入一个循环无法正常统计个数

2.可达性分析算法:

在主流的商用语言,都是通过可达性分析来判断对象是否存活,这个算法的思想就是通过一系列的成为“ GC Roots ” 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径成为引用链,当一个对象到“ GC Roots " 没有任何引用链,则证明此对象是不可用的

优点:

缺点:

如上: obj5 obj6 obj7 互有相连,但是到GC Roots 没有任何引用链,所以判定为需要被收回的对象

GC 会回收那些不是GC Roots 且没有被GC Roots 引用的对象

 

GC Roots 的对象类型:

 1.虚拟机栈中的引用的对象

 2.方法区中类静态属性引用的对象

 3.方法区中的常量引用的对象

 4.本地方法栈中JNI (即一般说的Native方法) 的引用的对象

3.引用的概念

    两种判断对象是否存活都跟引用有关,在JDK1.2 以前 引用被定义为当一个reference类型的数据代表的是另一块内存的起始地址,该类型的数据被称为引用,这种定义也很纯粹,但是也很狭隘,一个对象在这种定义下只有被引用和非被引用两种状态,无法描述所有状态的对象,我们希望描述对象时每当内存足够时,将它放在内存中,当内存空间进行垃圾回收后显得还是内存紧张时,可以回收这一部分对象,很多系统的缓存功能都符合这样的场景,所以JDK 1.2以后对引用重新扩充,分为强引用,软引用,弱引用,虚引用4中,这四种引用强度依次递减

    强引用:

            强引用是代码中普遍存在的,类似于Object obj = new Object(); 这种new产生的,只要这种引用一直存在,垃圾收集器就永远不会回收被引用对象

    软引用:

             软引用用来描述一些还有用但非必须的对象,对于软引用关联着的对象,当内存溢出异常发生之前,通过垃圾回收进行二次回收,如果二次回收完成之后,系统内存依然不够,才会抛出内存异常,在JDK1.2 以后用 SoftReference 类来实现软引用

    弱引用:

             弱引用也是用来描述非必须对象的,但是它的强度相比于软引用来说更弱一些,它仅仅能生存到下一次垃圾回收之前,当垃圾收集时,无论内存是否足够,弱引用的对象都要被回收,在JDK1.2以后都用 WeakReference 类来实现弱引用

    虚引用:

             虚引用是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个实例对象,为一个对象设置弱引用的唯一目的就是该对象在垃圾回收时收到一个系统通知,JDK 1.2 以后用 PhantomReference实现虚引用

二,清除算法

1.标记 - 清除算法

标记(mark)和清除(sweep)两部分

在标记阶段,collector从mutator根对象开始进行遍历,对从mutator根对象可以访问到的对象都打上一个标识,一般是在对象的header中,将其记录为可达对象。

而在清除阶段,collector对堆内存(heap memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象-通过读取对象的header信息,则就将其回收

缺点:回收后会造成内存不连续,形成很多的内存碎片,遇到大的数据就无法处理,效率低下

2. 复制法

复制算法,就是将堆空间分成From和To空间,一边一半,所有新的对象都放在From空间,当From空间满的时候,就将非垃圾的对象搬移到To空间中,然后清空From空间,同时互换From和To的名字,这样又可以继续在From空间中进行新建对象了
在这里插入图片描述

缺点:造成了大量空间浪费,需要预留50%的空间,内存利用率低下,98%的对象是朝生夕死的,仅仅2%的对象会存活比较长的时间,用50%的空间为2%的对象提供方便利用率过于低下

有点:去除了内存碎片

3. 标记 -- 整理算法

综合应用算法

可以看作三步: 标记垃圾对象 清除垃圾对象 内存碎片整理

1.首先标记除垃圾对象,标记可达性分析判断需清除的对象

2.清除垃圾对象

3.内存碎片整理

4.分代算法

当前商用的垃圾收集器都采用的是分代垃圾回收,根据对象的存活周期将内存分为几块,一般是将Java堆分为新生代和老年代,这样就可以根据各个代的对象特点选择最合适的回收算法。在新生代,每次垃圾回收都有大量的对象死去,只有少量存活,这样适合采用复制算法。只需要将对象复制成本就可以完成垃圾回收,而老年代因为存活率高,没有其他内存进行分配担保,必须使用标记-清除或标记-整理进行回收

 

新生代对象的特点是:创建出来没多久就可以被回收(例如虚拟机栈中创建的对象,方法出栈就会销毁)。也就是说,每次回收时,大部分是垃圾对象,所以新生代适用于复制算法。

老年代的特点是:经过多次GC,依然存活。也就是说,每次GC时,大部分是存活对象,所以老年代适用于标记压缩算法。

 

1.分代分为年轻代和老年代,年轻代里面分为Eden区和Survivor区,通常比例是8:1:1,每次保留10%的空间用作预留区域,然后将90%的空间可以用作新生对象

2.每一次垃圾回收后,存活的对象年龄对应+1 ,当经历15次后还依然存活的对象,我们让他直接进入老年代

   PS:为什么复制15次(15岁)后,被判定为高龄对象,晋升到老年代呢? 因为每个对象的年龄是存在对象头中的,对象头用4bit     存储了这个年龄数,而4bit最大可以表示十进制的15,所以是15岁。

3.另一种进入老年代的方式叫做内存担保机制,也就是新生代的空间不够的时候,对象直接进入到老年代

4.新生代的垃圾回收机制叫Minor GC,老年代的回收机制叫Full GC

5.在一次GC过程中首先标记Eden区的回收对象,存活的对象将标记年龄进入Survivor区,进行from区于to区的轮回,经过几次的GC 年龄就是多大,当Survivor区满时就会触发内存担保机制,存储进入老年代

 

三,垃圾收集器

1.serial 收集器

       Serial(串行)垃圾收集器是最基本、发展历史最悠久的收集器;

       JDK1.3.1前是HotSpot新生代收集的唯一选择;这个收集器是一个单线程的,他的单线程含义是 它进行垃圾收集时,其他工作线程会暂停,直到收集结束,这项工作由虚拟机在后台自动发起执行,在用户不可见的情况下将所有的工作线程全部暂停,这对很多程序来说是不可容忍的,比如运行1小时停止5分钟,就带来坏的体验,对于这种设计,虚拟机设计人员也很委屈,因为不可能边收集,这一边还要产生垃圾对象,这是清理不的。

所以JDK 1.3一直到现在,虚拟机开发团队一直为减少因垃圾回收而产生的线程停顿努力,所出现的虚拟机越来越优秀,但依然没有完全消除

实际上它依然是虚拟机Client模式下,新生代默认的垃圾收集器,它有相对于其他垃圾收集器的优势,比如由于没有线程之间切换的开销,专心做垃圾收集自然能够收获最高的线程利用率,在用户桌面背景下,一般分配个虚拟机的内存不会太大,收集几十或一两个百兆的新生代对象,停顿时间完全可以控制在很短的时间,这个是可以接受的只要不是频繁发生,因此,Serial收集器在Client模式下,对于新生代来说依然是一个很好的选择

特点

      针对新生代;采用复制算法; 单线程收集;进行垃圾收集时,必须暂停所有工作线程,直到完成;            

       即会 "Stop The World"

2.ParNew 收集器


ParNew收集器其实就是Seral收集器的多线程版本,除了使用多线程进行垃圾回收之外,其余可控参数,收集算法,停止工作线程,对象分配原则,回收策略等与Serial收集器完全致。除了多线程实现垃圾收集之外,其他没有什么太多创新之处,但是它确实Sevet模式下的新生代的首选的虚拟机收集器。其中-个重要的原因就是除了Serial收集器外, 只有它能与CMS配合使用。在JDK1.58时期, HotSpo推出了一款在强交互应用划时代的收集器CMS, 这款收集器是HotSpot第- -教真正意义上的并发收集器,第-次实现了垃圾回收与工作线程同时工作的可能性,换而言之。你可以边污染,边收集。

不过CMS作为老年代的收集器,却无法与1.4中发布的最新的新生代垃圾收集器配合使用,反之只能使用Sera或者Parnew中的一个。ParNew收集器可以使用-XX:+UseParNewGC强行指定它,或者使用-X:+UseConcMarkSweepGC选项后的默认新生代收集器。

ParNew收集器在单CPU环境下绝对不会有比Seral收集器更好的效果,甚至优于存在线程交互开销,该收集器在通过超线程技术实现的两个CPU的环境下都不能保证百分之百超越Serial收集器。当然,随着CPU数量的增加,对于GC时系统的有效资源利用还是很有好处的。在CPU非常多的情况下,可以使用-XParallelGCThreads来限制垃圾回收线程的数量

ParNew收集器和Serial收集器的差异

 

ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证能超越Serial收集器。当然,随着可以使用的CPU的数量的增加,它对于GC时系统资源的利用还是很有好处的。它默认开启的收集线程数与CPU的数量相同,在CPU非常多(譬如32个,现在CPU动辄就4核加超线程,服务器超过32个逻辑CPU的情况越来越多了)的环境下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

 

3.Parallel Scavenge 收集器

Parallel Scavenge收集器是一个新生代收集器。 采用要制算法,又是增的多线规垃圾收集器。它的关注点与其它收集器的关注点不样。CMS等收集器的关注点在于缩相垃圾回收时用户线程停止的时间,Parll Scavenge收集器则是达到一个可控制的吞吐量,所谓吞吐量就是CPU医行用户钱程的时间与CPU运行总时间的比值,

即吞叶量。用户线程工作时间) 1 (用户线程工作搁*垃圾回妆时间) .比加虚好机总共运行100分钟,垃圾收集消耗1分钟,则吞对吐量为9%。 停顿间越短越适合与用户交互的程序,朗的响应速度能提亮用户体验。但是高吞吐量则可以高效率的利用CPU89间。尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的程序。

有两个参数控制吞吐量,分别为最大垃圾收集时间: -0CMaxGCPauseMlis. 直接设置吞吐量的大小2OCCTmeao

xXC-+UscAdaptveSizePolicy

自适应策药也Paralel Scavenge收集器区别却Pamew收集器的重要-点

4.Serial Old 收集器

Serial Old收集器是Sera收集器的老年代版本,它同样是一个单线程收集器,使用标记-整理算法,这个收集器的主要目的也是在与给Client模式下使用。如果在Serverf模式下, 还有两种用途,一种是在jck5以前的版本中配合Parallel Scavenge收集器使用,另一种用途作为CMS的备用方案,在并发收集发生Concurrent Mode Failure时使用。

5.Parallel Old 收集器

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法,这个收集器在dk6中才开始使用的,在此之前arall Scavenge收集器-直处于比较皆尬的阶段,原因是,如果新生代采用了Parall Scavenge收集器,那么老年代除了SerialOla之外, 别无选择,由于老年代tSeral在服务端的拖累,使得使用了Parallel Scavenge收集器也未必能达到吞吐量最大化的效果,由于单线程的老年代无法充分利用服务器多CPU的处理能力,在老年代很大而且硬件比较高级的环境中,这种组合的吞吐量甚至不如Parallel Scavenge收集器+ CMS.

直到Parallel Old收集器出现后,“吞吐量优先效集器终于有了名副其实的组合,在注重吞叶量优先和CPU资源教的场合,可以采用Parallel Scavenge收集器+ Parallel Old收集器

6.CMS 收集器


CMS收集器是-种以获取最短停顿间为目标的收集器。从名字(Concurrent Mark Sweep)上就可以看出,采用的标记清除算法,它的过程分为4个步骤:只有初始标记和重新标记需要暂停用户线程。

1.初始标记-仅仅关联GC Roots能直接关联到的对象,速度很快

2.并发标记-进行GC Rools Tracig的过程,

3.重新标记-为了修正并发标记2期间,因用户程序运作而导致标记产生变动的那一部分对象的标记记录

4.并发清除


CMS收集器的三大缺点:

1. CMS收集器对CPUU资源非常数感

回收过程与用户线程一起井发执行的

2无法处理浮动垃圾

3因为甚于标记湖算法,用以会有大量的地

7.G1 收集器 


首先,G1的设计原则就是简单可行的性能调优

-XX:+UseG1G -Xmx329 -XX.MaxGCPauseMlls=-200

其中-XX:+UseG1GC为开启G1垃圾收集器,-Xmx32g 设计堆内存的最大内存为32G, -XX.MaxGCPauseMilis-200设IGC的最大暂停时间为200ms.如果我们需要调优,在内存大小-一定的情况下,我们只需要修改最大暂停时间即可。

1.内存分配

2. Young垃圾回收

3. Mix垃圾回收

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

§九千七§

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值