上一篇:JVM(三)—堆、栈、方法区
前言:
在了解GC回收算法之前先明确一个问题,
1.如何确定一个对象是否需要被回收?
答:如果这个对象不会再被引用(其他对象不再持有该对象的引用)了,那么就可以被回收了
这里涉及到两个算法:
1.可达性分析算法
先确定肯定不会被回收的对象,然后以该对象为根对该对象持有的引用进行遍历,这些遍历到的对象就认为是可达的(不需要回收的),
遍历不到的就认为是不可达的(需要被回收的)。
2.引用计数算法
简单理解:被创建的每一个对象都有一个与之相对应的计数器,计数器记录了该对象被使用的次数。如果该对象被其他对象引用一次,
则计数器加1,如果删除对该对象的引用,则计数器减1,当计数器计数为0的时候,那么就会对该对象进行回收。
eg: String s = "uuu" 此时"uuu"这个字符串对象的计数器加1, s = null ''uuu''的计数器就会减1
回到正题,下面说说GC四种垃圾回收算法:
第一种:标记清除算法
这种算法的思路简单粗暴,直观的说就是遍历内存中所有的对象,对那些需要被回收的对象做个标记,然后再将这些做了标记的
对象进行清除。(就好比早上在做第八套广播体操的时候,全班的同学都排好队形了,但是过程中班主任老师眼睛挨个扫描了一
下班上同学,有几个伙计操儿八蛋的不干正事站着不动弹,被老师请出了队伍,那么这些伙计原来站着的位置就空出来了,这个
时候队伍就显得不那么整齐了)。此算法有如下有缺点:
优点:
算法思路简单清晰,是后面几种算法的基础
缺点
1.效率低下,
首先你需要遍历一次内存中所有的对象,标记出所有需要被回收的对象,然后再遍历一次将所有被标记的对象进行清除回收。
设想现在有一块内存,里面对象非常多,但是需要被清除的对象非常少,只有几个,那使用这种算法就毫无游戏体验了。
2.需要维护空间碎片
从上面早操的例子中我们提到了这些对象被清除了之后会产生空位置(内存碎片),如果这些内存碎片不加以维护调整,那么如果以后分配
一个大对象的时候就没有位置放了。
第二种:复制算法
这种算法就是将内存分为容量大小相等的两个,分别是From区和To区,新创建的对象都放在From区,然后当From区的内存不够
存储下一个对象的时候,就将From区中存活的对象复制到To区,然后再将From区一次性全部清空回收,此时From区和To区的职
责发生变化,原来的From区变为To区,原来的To区变为From区。
优点:
这种算法可以有效解决内存碎片的问题,因为当对象从From区被复制到To区的时候都是连续分配内存空间。
缺点:
1.当对象的存活率较高的时候使用这种算法的效率就很低了,因为你需要复制大量的存活对象从From区到To区。而实际上复制对象也是一个
比较复杂的过程。
2.内存空间资源的浪费:原本你有100元钱可以使用,但是你总是只能使用其中的50元,那么100元人民币的资源是不是就是资源浪费?
第三种:标记压缩算法
这种算法简单理解就是将被标记的需要被回收的对象集中移动到内存的一侧,然后清除回收掉未被标记对象边界线以外的
内存空间,这样做就避免了内存碎片的问题。
优点:
不用再浪费一半的内存空间资源了,避免了内存碎片的产生。
缺点:
对于内存中存活对象较多的时候,使用这种算法需要做大量的移动工作,效率性能低下。
算法对比
内存整齐度:复制算法=标记压缩算法>标记清除算法
内存效率(时间复杂度):复制算法>标记清除算法>标记压缩算法
内存利用率:标记压缩算法=标记清除算法>复制算法
第四种:分代算法(重点)
此种算法有一种基本假设:所有的对象的生命周期都很短,存活时间并不长。
它是目前虚拟机使用的一种算法,它根据对象存活时间分为年轻代,年老代,持久代(持久代主要存放java类信息,与要收集的java对象的关系并不大,GC主要发生在年轻代和年老代)
1.年轻代:
所有新生的对象都放在年轻代中,该代中对象的存活时间较短,GC频次较高,使用复制算。年轻代分为3个区:一个Eden区和
两个Survivor区。新生成的对象主要是在Eden区,当Eden区满了之后,将存活的对象复制到任意一个Survivor区,当其中一个
Servicor区满了之后,将这个区中存活的对象复制到另外一个Survivor区中,当这个Survivor区也满了之后,从Survivor区A中复
制过来的对象如果还存活着就将他复制到年老代中(需要注意的是Survivor区既存在从Eden区过来的对象,也存在从另外一个
Survivor区复制过来的对象,总有一个Survivor区是空的,Survivor区是可以配置多个的(多于2个),这样做的目的是为了让
对象尽可能的存在于年轻代,也就是延长对象在年轻代的存在时间)。
2.年老代
年老代的对象存活的时间都是比较长的,需要被回收的对象很少,所以适用标记压缩算法,年老代的内存空间比年轻代大得多,大致的比例为
1:2,当年老代满了之后会触发完全GC(Full GC),年老代的GC频次较低。
3.持久代
永久代主要用于存放静态文件,如Java类、方法等。永久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,
例如使用反射、动态代理、CGLib等bytecode框架时,在这种时候需要设置一个比较大的永久代空间来存放这些运行过程中新增的类。
下一篇:内存溢出(OOM)故障排查—JProfiler