目录:
对象已死吗?
如何判断对象已经死了呢,如下:
1. 引用计数算法
引用计数算法给对象添加一个引用计数器,每当一个地方引用它是时,计数值就增加一;当引用失效时,计数值就建一,技术器为0的对象不可能在被使用。但是主流的JVM里没有使用它的,原因是他很难解决对象之间互相循环引用的问题。
例如对象objA和objB都有字段instance,objA.instance = objB,objB.instance = objA,除此之外这两个对象再无任何引用,实际上这两个对象已经不可能在被访问,但是因为相互引用,所以计数值不为0,无法收集他们。
2. 可达性分析算法
这个算法的基本思路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何的引用链相连时,证明此对象是不可用的,如图:
虽然对象object5,object6,object7是相互关联的,但是他们到GC Roots不可达,所以是可回收的。
可以作为gc root的有:
1 、 虚拟机栈(栈帧中的本地变量表)中引用的对象。
2、 本地方法栈中JNI(即一般说的native方法)引用的对象。
3、 方法区中的静态变量和常量引用的对象。
垃圾回收算法
1. 标记-清除(Mark-Sweep)算法
如同其名,它的回收有两个阶段,首先标记出所有的需要回收的对象,在标记完成后统一回收所有被标记的对象。它是一种最基本的回收方法,但他也有不足。一是效率不高,标记和清除的效率都不高,二是容易产生内存碎片(如图),碎片太多导致无法分配较大的对象时,就会提前触发另一次垃圾收集动作。后续的方法都是在它的基础上进行改进。
2. 复制算法
将内存的区域划分为相同大小的两块,每次只是用一块,在一块使用完成后,将其中的存活对象存储到另一块中,再把已使用的内存空间全部清理掉,这样就不用考虑内存碎片化的问题了,只需要移动堆顶的指针就行。但是这样的缺点就是只有一般半的内存可用。
复制算法也是新生代使用的回收算法,IBM的一项研究表明,新生代中的对象98%是“朝生夕死”的,也就是存活对象只占了大概2%。所以没有必要按照1:1分配空间,而是将内存划分为一块较大的Eden空间和两块小的Survivor空间,每次使用Eden和一块Survivor。回收时,将Eden和Survivor中还存活的对象一次性的复制到另一块Survivor中,最后清理掉Eden和使用的Survivor。
HotSpot虚拟机默认Eden和Survivot的内存大小比例为8:1,所以整个新生代中可用的空间容量为新生代总容量的90%。当然无法保证每次存活的对象只占不到10%,这时就可以通过分配担保机制使用老年代来继续存储。
3. 标记-整理(Mark-Compact)算法
进入老年代的对象一般都是存活率较高的,假如在一次对象回收的过程中全部的对象都存活,这时如果再用复制算法,那么需要的空间将是50%,浪费极高。所以老年代使用和标记-清除类似的方式,标记过程一样,但是后续操作不是直接清理,而是将可回收对象移动到内存的一端,然后清理掉另一端的其他剩余内存。如图:
4. 分代收集算法
这算是对GC垃圾收集算法的一个总结,当前虚拟机大都采用分代收集算法(),当前堆中根据对象的存活周期将内存划分为了几块,一般分为新生代和老年代。新生代中大量的对象都会死去,所以使用复制算法复制少量的对象;而老年代中对象存活效率高,没有额外的空间进行分配担保而且为了效率,就必须使用标记-整理算法。
垃圾收集器
垃圾收集器的演化历史:Serial -> Parallel(并行) -> CMS(并发) -> G1
总之,就是为了减少stop the world的时间甚至是达到并发的效果。
并行:多条垃圾收集线并行工作,但是用户线程处于等待状态。
并发:指用户线程和垃圾回收线程可以同时执行。
像Serial,Parallel这种是使用指针碰撞(即连续的内存地址)分配内存的,而CMS是使用空闲列表(即分散的内存地址)分配内存的。
Serial收集器: 新生代单线程收集器,在他进行回收时,必须暂停其他的工作进程。
Parallel收集器: 用在新生代,是Serial的多线程版本
Serial Old收集器: 老年代单线程
Parallel Old收集器: 老年代多线程
Parallel Scavenge收集器: 新生代吞吐量优先回收器,不需要设置SurvivorRatio,Xmn,晋升老年代大小。都会自动调整。但是无法和CMS匹配使用
CMS(Concurrent Mark Sweep)收集器:用在老年代,标记-清除方式,希望停顿的时间最短可以用这个
初始标记 -> 并发标记 -> 重新标记 -> 并发清除 四个阶段。只有初始标记和重新标记时需要stop the world。所以停止时间最短,但是在程序运行时就一直在标记,所以对CPU很敏感会占用CPU。另外,CMS是基于标记-清除算法的,所以会生成很多碎片。
G1(Garbage-first)收集器:最新的收集器,未来替代CMS的收集器
G1的优点:
- 支持并行和并发
- 分代收集,G1既可以支持新生代回收也可以支持老年代回收,
- 空间整合,不同于CMS的标记-清除,G1使用的标记-整理算法,所以内存碎片生成的要少。
内存分配与回收策略
虚拟机给每个对象定义了一个对象年龄计数器,在对象在Eden创建并经过第一次Minor GC后仍然存活,并能被Suivivor容纳的话,将会被移动到Survivor空间,并对象年龄设置为1。每经历过Minor GC,年龄就增加1岁,当到一定程度(默认15岁,可以通过参数-XXMaxTenuringThreshold设置),就将会晋升年老代。
Full GC频繁原因:
在 CMS 启动过程中,新生代提升速度过快,老年代收集速度赶不上新生代提升速度
在 CMS 启动过程中,老年代碎片化严重,无法容纳新生代提升上来的大对象
如何从新生代进入老年代:
1,大对象直接存入老年代,为了减少full gc,这个大小值可以设置。
2,长期存活的对象直接存入老年代,例如年龄超过15了,这个年龄值可以设置。
3,动态年龄判定:同等年龄的超过survivor的一半,大于这个年龄的进入老年代。
空间分配担保:如果老年代最大的连续空间小于新生代的所有的对象之和,那么担保失败,就会启动一次Full GC。
参考:《深入理解Java虚拟机》