一、分代思想
-
分代介绍
JVM将堆中对象分为了新生代和老年代,默认情况下新生代占用1/3堆大小,老年代占用2/3堆大小。 在Hotspot中方法区(元空间)又称为永久代(PermGen)。 -
新生代
新生代分为Eden区和Survivor区,其中Survivor区又分为From区和To区,Eden区:From区:To区 默认为 8:1:1。一般对象优先在Eden区进行内存分配,但是大对象有可能直接进入老年代。
新对象分配空间时,如果Eden区空间够分配那就分配到Eden,不够的话就进行Minor GC,Minor GC后存活的对象将被转移到Survivor区中From区,在Survivor区中的对象会开始计算年龄(记录在对象头中4bit大小)。
对象年龄如何计算?
当From区满时会启动Minor GC,此时From区中存活的对象将被放到To区,需要注意的是,To区一定为空,当From区将对象转移到To区后,这时原来的From区变为To区,原来的To区变为了From区,每当进行转换一次,对象的年龄就 +1 ,当对象年龄到达最大值15时(可通过参数修改,但不能超过15)便送入老年代。 -
老年代
从Survivor区生存年龄达到15(默认值)时对象就进入老年代,在老年代中主要产生Full GC,由于老年代比较稳定,因此垃圾回收不会频繁进行。 -
(方法区)永久代
永久代的垃圾回收主要是废弃常量和无用的类,回收废弃常量于回收Java堆中的对象很类似,例如常量池中的字符串“aaa”,如果没有任何对象引用这个“aaa”,如果此时发生内存回收必要的话会回收该字符串内存空间。
如何判断类无用需回收呢?
①该类所有实例都已经被回收,即不存在该类任何实例
②加载该类的ClassLoader已经被回收
③该类对应的Class对象没有被任何地方引用,即无通过反射新建该类实例对象的可能。
这里只是能回收,不表示一定回收。在大量使用反射、CGLib等字节码框架、动态生成JSP已经OSGI这类频繁自定义ClassLoader类的场景都需要虚拟机具备类回收功能,以保证方法区(永久代)不会溢出。 -
分代图
二、判断对象是否可回收
-
引用计数法
给对象添加一个引用计数器,每当有一个地方引用它时,计算器就加1;当引用失效时,计数器就减1;任何时刻引用计数器为0的对象将不可再被使用,即可回收。
但是这种方法在互相引用的时候会导致无法回收,比如A引用B,B引用A,A和B的其他引用都失效了,此时A和B应该已经无用可回收了,但由于引用计数器不为零,因此仍无法回收。 -
可达性分析法
目前主流的语言都是使用可达性分析法进行对象回收判断。这个算法基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点出发开始向下搜索,搜索走过的路径称为引用链,当一个对象没有引用链到达GC Roots时,则该对象是不可用的(图论上叫做GC Roots到该对象不可达),这些对象会被判定为可回收对象。
GC Roots对象
①虚拟机栈(栈帧中局部变量表)中引用的对象
②方法区中类静态属性引用的对象
③方法区中常量引用的对象
④本地方法栈中native方法引用的对象
三、GC类型
-
Minor GC(新生代GC):指发生在新生代的垃圾收集,因为Java对象大多都是朝生夕亡的,所以Minor GC很频繁,一般回收速度也比较快。
-
Major GC / Full GC(老年代 GC):指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(不绝对,Parallel Scavenge收集器收集策略就有直接进行Major GC的策略选择过程)。Full GC一般比Minor GC慢10倍以上,执行次数也比Minor GC少。
四、垃圾回收算法
-
标记-清除算法
最基础的收集算法,算法分 “标记” 和 “清除” 两个阶段。首先标记出所有需要回收的对象,在标记完成后统计回收所有被标记的对象。
算法缺点:
①效率问题,标记和清除两个过程效率不高
②空间问题,标记清除后会产生大量不连续的空闲空间(内存碎片)
示意图:
-
复制算法
将内存分为大小相同的两块,每次使用只使用其中一块。当这块内存用完后就将还存活的对象复制到另一块,同时把原来的空间清理掉,这样每次内存回收都是堆内存区间的一半进行回收。
算法缺点:
内存利用率不高,50%以上内存处于空闲状态
示意图:
-
标记-整理算法
根据老年代的特点提出的一种算法,标记过程和“标记-清除”算法一样,但是接着将所有存活对象向前移动到内存前部形成连续的占用空间,然后清理掉最后一个存活对象之后的内存。
算法缺点:
速度比“标记-清除”算法慢,因为比该算法多了一个整理阶段。
示意图:
-
分代收集算法
目前的垃圾收集器基本都采用“分代收集”算法,实际就是根据对象存活周期不同划分内存。由于Java堆分为新生代和老年代,因此我们可以根据各代特点分别选择垃圾回收算法,实际上也就是上面几种算法的综合使用。
新生代中,每次gc时都有大量对象被回收,所以选择复制算法,这样只需复制存活的少量对象即可完成gc任务。
老年代对象由于较稳定,如果使用复制算法每次都会有很多对象转移,而且没有额外的空间对他进行分配担保,因此一般选择“标记-清除”算法或“标记-整理”算法。
五、笔记目录
JVM关键总结(一)——类加载机制
JVM关键总结(二)——JVM内存结构
JVM关键总结(三)——分代与垃圾回收算法
JVM关键总结(四)——垃圾回收器及调优命令与工具
参考资料:
1.《深入理解Java虚拟机第二版》——周志明
2.http://www.360doc.com/content/17/0919/14/20874412_688384439.shtml
3.https://www.cnblogs.com/Qinmp-Blog/p/5693318.html