jvm垃圾收集策略和算法

来源:互联网 发布:淘宝店铺负责人更改 编辑:程序博客网 时间:2024/06/11 14:56
首先我们思考一下为什么jvm需要垃圾回收,那些地方需要垃圾回收,什么是垃圾
1、为什么jvm需要垃圾回收:因为java是实现自动内存管理,程序在申请使用内存时不像C++那样手动申请和释放内存,所以java需要回收已使用的内存
2、垃圾回收区域:jvm运行时内存结构:程序计数器、java虚拟机栈、本地方法栈、方法区、堆, 我们知道,方法区主要存放类与类之间关系的数据,而这部分数据被加载到内存之后,基本上是不会发生变更的,

Java堆中的数据基本上是朝生夕死的,我们用完之后要马上回收的,而Java栈和本地方法栈中的数据,因为有后进先出的原则,当我取下面的数据之前,必须要把栈顶的元素出栈,因此回收率可认为是100%;而程序计数器我们前面也已经提到,主要用户记录线程执行的行号等一些信息,这块区域也是被认为是唯一一块不会内存溢出的区域。在SunHostSpot的虚拟机中,对于程序计数器是不回收的,而方法区的数据因为回收率非常小,而成本又比较高,一般认为是“性价比”非常差的,所以Sun自己的虚拟机HotSpot中是不回收的!但是在现在高性能分布式J2EE的系统中,我们大量用到了反射、动态代理、CGLIBJSPOSGI等,这些类频繁的调用自定义类加载器,都需要动态的加载和卸载了,以保证永久带不会溢出,他们通过自定义的类加载器进行了各项操作,因此在实际的应用开发中,类也是被经常加载和卸载的,方法区也是会被回收的!但是方法区的回收条件非常苛刻,只有同时满足以下三个条件才会被回收!

    1)、所有实例被回收

    2)、加载该类的ClassLoader被回收

    3)Class对象无法通过任何途径访问(包括反射)

3、什么是垃圾:如何确定对象是回收垃圾,也就是如何确定一个对象是否已经存活,有以下几个算法:
      3.1、引用计数法:使用引用计算器记录对象引用使用情况,被引用计数器加1,否则减1(缺点是会有相互引用的问题)
      3.2、可达性分析算法:找一个根root对象,从这个根对象搜索引用的对象,称为引用链,如何一个对象到GC root是不可达的,则说明这个对象不可用,对象可以被回收了。
      那么那些对象可以作为GC ROOT 呢?我们知道,java运行开始是在栈上的,那么很显然GC Root是保存在栈上的对象引用,方法区上的对象引用。
那jvm是如何回收垃圾对象的?接下来上垃圾回收算法和常用垃圾回收器
在了解垃圾回收算法的过程中请记住评价算法的优劣的指标:空间和时间复杂度!!!
1、标记-清除(Mark-Sweep)
第一步标记出要清除的对象,第二步执行清除内存动作,优点:实现简单,缺点:标记过程需要扫描整个内存,效率低,清除垃圾对象后会产生大量的碎片,不利于大对象分配和对象访问。
2、复制算法(分代堆存储虚拟机中新生代就是使用这个回收算法)

将可用内存按容量分成大小相等的两块,每次只使用其中一块,当这块内存使用完了,就将还存活的对象复制到另一块内存上去,然后把使用过的内存空间一 次清理掉。这样使得每次都是对其中一块内存进行回收,内存分配时不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可,优点:实现简单,运行高效。缺点:可使用的内存降为原来一半。

3、标记-整理算法:

标记-整理算法在标记-清除算法基础上做了改进,标记阶段是相同的标记出所有需要回收的对象,在标记完成之后不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,在移动过程中清理掉可回收的对象,这个过程叫做整理。

标记-整理算法相比标记-清除算法的优点是内存被整理以后不会产生大量不连续内存碎片问题。

复制算法在对象存活率高的情况下就要执行较多的复制操作,效率将会变低,而在对象存活率高的情况下使用标记-整理算法效率会大大提高。

4、分代收集算法:

根据内存中对象的存活周期不同,将内存划分为几块,java的虚拟机中一般把内存划分为新生代和年老代,当新创建对象时一般在新生代中分配内存空 间,当新生代垃圾收集器回收几次之后仍然存活的对象会被移动到年老代内存中,当大对象在新生代中无法找到足够的连续内存时也直接在年老代中创建。

现代的Java虚拟机就联合使用了分代复制、标记-清除和标记-整理算法,java虚拟机垃圾收集器关注的内存结构如下:

如上图所示

1、新生代:

新生代使用复制和标记-清除垃圾收集算法,研究表明,新生代中98%的对象是朝生夕死的短生命周期对象,所以不需要将新生代划分为容量大小相等的两 部分内存,而是将新生代分为Eden区,Survivor from和Survivor to三部分,其占新生代内存容量默认比例分别为8:1:1,其中Survivor from和Survivor to总有一个区域是空白,只有Eden和其中一个Survivor总共90%的新生代容量用于为新创建的对象分配内存,只有10%的Survivor内存 浪费,当新生代内存空间不足需要进行垃圾回收时,仍然存活的对象被复制到空白的Survivor内存区域中,Eden和非空白的Survivor进行标记 -清理回收,两个Survivor区域是轮换的。

新生代中98%情况下空白Survivor都可以存放垃圾回收时仍然存活的对象,2%的极端情况下,如果空白Survivor空间无法存放下仍然存 活的对象时,使用内存分配担保机制,直接将新生代依然存活的对象复制到年老代内存中,同时对于创建大对象时,如果新生代中无足够的连续内存时,也直接在年 老代中分配内存空间。

Java虚拟机对新生代的垃圾回收称为Minor GC,次数比较频繁,每次回收时间也比较短。

使用java虚拟机-Xmn参数可以指定新生代内存大小。

2、年老代:

年老代中的对象一般都是长生命周期对象,对象的存活率比较高,因此在年老代中使用标记-整理垃圾回收算法。

Java虚拟机对年老代的垃圾回收称为MajorGC/Full GC,次数相对比较少,每次回收的时间也比较长。

当新生代中无足够空间为对象创建分配内存,年老代中内存回收也无法回收到足够的内存空间,并且新生代和年老代空间无法在扩展时,堆就会产生OutOfMemoryError异常。

java虚拟机-Xms参数可以指定最小内存大小,-Xmx参数可以指定最大内存大小,这两个参数分别减去Xmn参数指定的新生代内存大小,可以计算出年老代最小和最大内存容量。

3、永久代:

java虚拟机内存中的方法区在Sun HotSpot虚拟机中被称为永久代,是被各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。永久代 垃圾回收比较少,效率也比较低,但是也必须进行垃圾回收,否则会永久代内存不够用时仍然会抛出OutOfMemoryError异常。

永久代也使用标记-整理算法进行垃圾回收,java虚拟机参数-XX:PermSize和-XX:MaxPermSize可以设置永久代的初始大小和最大容量。

关于垃圾回收算法和策略部分先总结到这里,请思考以下问题:
1、垃圾算法是如何实现的?
2、为什么需要对象需要分代?

1 0
原创粉丝点击