垃圾回收算法

前言:

Garbage Collector 垃圾回收器

 

HotSpot VM 虚拟机,主要负责3件事:

1:执行方法所请求的指令和运算

2:定位,加载和验证新的类型(即类加载)

3:管理应用内存,包括 堆,栈,方法区

 

G1 的名字:垃圾优先(Garbage First)

 

G1内部只要有四个操作阶段:

1:年轻代回收(A Young Collection)

2:运行在后台的并行循环(A Background,Concurrent Cycle)

3:混合回收(A Mixed Collection)

4:全量回收(A Full GC)

 

-Xms 是设置内存初始化的大小

-Xmx是设置最大能够使用内存的大小(最好不要超过物理内存)

 

内存泄漏:常见的 OutOfMemory异常(简称:OOM)是内存不足引起的一场,内存泄漏也称作 “存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完后未被释放,结果导致一直占据该内存单元,一直持续到程序结束。形象的比喻是:操作系统可提供给所有进程的存储空间正在被某个进程榨干,最终结果是程序运行时间越长,占用存储空间越多,最终用尽全部内存空间,造成整个系统崩溃。这里的存储空间不是指物理内存,而是指虚拟内存大小,这个虚拟内存大小取决于磁盘交换区设定的大小。由程序申请的一块内存,如果没有任何一个指针指向它,那这块内存就泄漏了。

 

 

年轻代&年老代

1:分代的唯一理由就是 优化GC性能。如果没有分代,所有的对象都在一块,GC的时候要找到哪些对象没用,就会对 堆的所有区域进行扫描。HotSpot VM被分成了Young .Tenured,Perm三个阶段。

2:Hotspot VM 把年轻代分为了3个部,即 1个Eden区,和2个Surivor区(分别叫From和To),默认比例 8:1;

3:当GC只发生在年轻代中,回收年轻代对象的行为被称为: Minor GC;

老年代中,被称为 Major GC 或 Full GC;一般Minor GC的发生频率要比 Major GC 高很多。 即 老年代中垃圾回收的频率大大低于年轻代。

 

生命周期

1:对象提升规则:

虚拟机给每个对象定义了一个对象年龄计数器,(默认是 15岁,阈值可通过-XX:MaxTenuringThreshold来设置);如果对象在Eden出生并经过第一次 Minor Gc 后仍然存活,并且被 Survivor 容纳的话,将被移动到Survivor空间中,并将对象年龄设为1。对象在Survivor每熬过一次Minor GC,年龄就增加一岁。到达对象年龄后,会晋升到老年代。

针对不同年龄段的对象分配原则:

1:对象优先分配在Eden区,如果Eden 区没有足够的空间,虚拟机进行一次 Minor GC

2:大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)

3:长期存活的对象进入老年代。

4:动态判断对象的年龄。如果Survivor 区中相同年龄的所有对象大小的总和大于 Survivor空间的一半,年龄大于或等于该年龄的对象直接进入老年代。

5:空间分配担保。每次进行Minor GC时,JVN 会计算Survivor区移至老年区的对象的平均大小,如果这个值 大于老年区的剩余值 大小 则进行一次 Full GC,如果小于则进入检查 HandlePromotionFailure 逻辑。判断这个逻辑。如果是 True 则只进行 Minor GC,如果是 False 则进行 Full GC。

 

除了直接调用System.gc 外,触发 Full GC 执行的有四种情况:

1:老年代空间不足。只有在年轻代对象转入以及创建为大对象,大数组时 才会出现不足的现象。当执行 Full GC 后空间仍然不足,则配出如下错误:Java.lang.OutOfMemoeryError:Java heap space.

为了避免这两种情况引起的 Full GC ,调优时 尽量做到让对象在Minor GC 阶段被回收,让对象在年轻代多存活一段时间,以及尽量不要创建过大的对象及数组。

2:永久代空间满

永久代存放的是一些类的信息,当系统中要加载的类,反射的类和调用的方法 较多时,永久代可能会被占满,在未配置 为采用CMS GC 的情况下会执行 Full GC。如果经过 Full GC 仍然回收不了,那么 JVM 会抛出错误信息:Java.lang.OutOfMemoryError:PermGen space

3:CMS GC 时出现 Promotion Faild 和 Concurrent Mode Failure

4:统计得到的 Minor GC 晋升到老年代的平均大小 大于老年代的剩余空间

 

Stop the World

垃圾回收器的任务是识别和回收垃圾对象进行内存清理。为了让垃圾回收器可以正常且高效地执行,大部分情况下会要求系统进入一个停顿的状态。停顿的目的是 终止 所有应用程序的执行。只有这样,系统中才不会有新的垃圾产生,同时停顿保证了系统状态在某一瞬间的一致性,也有益于垃圾回收器更好地标记垃圾对象。

 

对象存活判断

一般有两种方式:

1:引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时 计数减1,计数为0时可以回收。此方法简单,但无法解决 对象相互循环引用的问题

2:可达性分析:从 GC Roots 开始向下搜索,搜索走过的路径 称为引用链,当一个对象到 GC Roots 没有任务引用链相连时,则证明 对象是不可用的不可达对象。

 

在Java中,GC Roots 包括以下内容:

1:虚拟机栈内引用的对象

2:方法区中类静态属性实体引用的对象

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

4:本地方法栈内 JNI 引用的对象

 

 

System.gc() 方法

默认情况下,System.gc() 会显示直接触发 Full GC ,同时对老年代和新生代进行回收。而一般情况下,垃圾回收应该自动进行,无需手动出发,如果过于频繁地触发垃圾回收,对于系统的整体性能没有好处。所以 虚拟机提供了一个选项 : DisableExplicitGC 来控制是否手工触发 GC

 

 

垃圾回收算法:

1:引用计数法:

首先需要区分出内存中哪些是存活对象 ,哪些是已经死亡的对象。只有被标记为已经死亡的对象GC才会执行垃圾回收时,释放掉所占用的内存空间。这个过程称为 垃圾标记阶段;

引用计数器,对于一个对象A,只要有任何一个对象引用了A,则A的引用计算器就加1,当引用失效时,引用计数器就减1。只要对象A 的引用计数器的值为0,则对象A 就不可能再被使用。也就是说,引用计数器的实现只需要为每个对象配置一个整形的计数器。引用计数器算法的一优势就是 不用等待内存不够的时候 才进行垃圾的回收,完全可以在赋值操作的同时检查计数器是否为0,如果是的话就立即回收。但有一个严重的问题是,无法处理循环引用的情况,如果 对象A 含有对象B的引用,对象B中含有对象A 的引用,此时 对象 A 和对象 B 的 引用计数器都不为0,但在系统中 却不存在任何第3个对象引用对象A 或 B,也就是说, A 和 B 是应该 被回收的垃圾对象,但由于垃圾对象间相互引用,从而使垃回收器无法识别,无法释放掉这种无用对象所占用的内存空间,引起内存泄漏。

 

2:根搜索算法

在根搜索算法中,不可达的对象,它们暂时处于”缓刑“阶段,到死亡 至少要经历两次标记过程。如果 对象在进行根搜索后发现没有与 GC Roots 相连接的引用链,那它会被第一次标记 并且进行一次筛选,筛选此对象是否有必要执行 finalize()方法,当对象覆盖 finalize()方法,或者finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为 ”没必要执行“,如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象将会被放置一个名为 F-Queue 的队列之中,并在稍后由一条虚拟机自动建立的,低优先级的 Finalizer 线程去执行。

 

 

 

3:标记-清除算法

标记-清楚算法 将垃圾回收分为两个阶段,标记阶段和清楚阶段。在标记的阶段,collector 从mutator 根对象开始进行遍历,对mutator 根对象可以访问到的对象都打上一个标识,一般是在对象的header 中,将其记录为可达对象。而在 清除阶段,collector 对堆内存(head memory)从头到尾进行线性的遍历,如果发现某个对象没有标记为可达对象,通过读取对象的header 信息,将其回收。一种可行的实现是:在标记阶段首先通过根节点,标记所有从根节点开始的可达对象,因此未被标记的对象就是未被引用的垃圾对象。然后,在清楚阶段,清楚所有未被标记的对象。

与引用计数算法不同的是,标记-清除算法不需要运行环境检测每一次内存分配和指针操作,而只要在 “标记”阶段中跟踪每一个指针变量的指向,用类似思路实现垃圾收集器也被统称为 跟踪收集器(Tracing Collector)

标记-清除算法最大的问题是 存在大量的空间碎片,因为回收后的空间是不连续的。在对象的堆空间分配过程中,尤其是大对象的内存分配,不连续的内存空间的工作效率要低于连续的空间。

 

4:复制算法

首先将活着的内存空间分为两块,每次只使用其中一块,在垃圾回收时 将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。

 

5:标记-压缩算法

当成功标记出内存中的垃圾对象后,算法会将所有的存活对象都移动到一个规整且连续的内存空间中,然后执行 Full GC(老年代的垃圾回收)回收无用对象所占用的内存空间,当成功执行压缩后,已用和未用的内存都各自一边,彼此之间维系着一个记录下一次分配起始点的标记指针,当为新对象分配内存时,则可以使用指针碰撞(Bump the Pointer) 技术修改指针的偏移量将新对象分配在第一个空闲内存位置上,为新对象分配内存带来的便捷。

标记压缩算法的总体执行效率高于 标记-清除算法,又不像复制算法那样需要牺牲一半的存储空间。

 

 

6:增量算法

如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行,每次,垃圾收集线程只收集一小片的内存空间,接着切换到应用程序。依次反复,直到垃圾收集完成。使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

增量收集算法通过对进程间冲突的妥善处理,允许垃圾收集进程以分阶段的方式完成标记,清理或复制工作。

 

 

7:分代收集算法

根据垃圾回收对象的特性,使用合适的算法回收,分代就是基于这种思想,它将内存区间根据额对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法以提高垃圾回收的效率。

它把对象分为 年轻代,年老代,持久代,对不同的生命周期的对象使用不同的算法;

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值