哪些内存需要回收?
由于程序计数器、虚拟机栈、本地方法栈是线程私有的,即随着线程生而生,线程死而死;而java堆和方法区是线程共享的,所以垃圾回收GC面向java堆(主要)和方法区。
垃圾是什么?
一个对象,使用对象存活判定算法判定,发现不是存活状态,即为可回收的垃圾。
对象存活判定算法:
1. 引用计数算法:
- 给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1;当一个引用失效时,计数器减1。对一个对象,其引用计数为0,则未被引用,即判定为可回收的垃圾。
- 优点:算法简单,效率高
- 缺点:不能解决对象之间相互循环引用的问题
- eg:两个对象,除了互相引用之外,再无任何其他的引用,实际上这两个对象已经不可能被访问了,但是通过引用计数算法不能判断为可回收的垃圾。
2. 可达性分析算法(目前一般都用这种):
- 从一个“GC Roots”出发,向下搜索,到某一个对象节点没有任何路径可达,则称“GC Roots”到这个对象不可达,即该对象不可用,可以判定为可回收的垃圾。
- 可作为 GC Roots的对象包括:
- 虚拟机栈中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中Native方法引用的对象
- 可作为 GC Roots的对象包括:
- 优点:解决了引用计数算法不能解决的循环引用的问题
引用(插入讲解)
引用分为4种类型,从强到弱依次是:
强引用(Strong Reference) > 软引用(Soft Reference) > 弱引用(Weak Reference)> 虚引用(Phantom Reference)
- 强引用:代码中普遍存在的引用形式,只要强引用存在,就不能GC回收
- 软引用:描述一些还有用但非必需的对象,当内存不足,即将发生OOM(Out Of Memory)异常时,会加入GC回收范围,下次进行回收
- 弱引用:同样是描述非必需的对象,但比软引用强度弱,即不管内存是否充足,下次进行GC时,会被回收
- 虚引用(幽林引用/幻影引用):能在这个对象被收集器回收时受到一个系统通知
垃圾回收的方法?
垃圾收集算法,大致分为以下4种:标记-清除算法、复制算法、标记-整理算法、分代收集算法,其中标记-清除算法是最基础的收集算法,后面的算法都是在其基础上不断进行改进而产生的。
1. 标记-清除算法(Mark-Sweep):
- 分为两个过程:标记和清除,先标记出所有需要回收的对象,标记完成后,再统一回收所有被标记的对象。
- 缺点:
- 标记和清除的效率都不高;
- 会形成大量不连续的内存碎片,导致后面对较大对象的分配出现内存不足,而提前触发另一次GC。
2. 复制算法(Copying):
- 将可用内存一分为二,每次只使用其中的一块;当一块用完了,就将还存活的对象复制到另一块上面,再将前一块中使用过的内存空间一次性清理掉。
- 解决了效率和内存碎片的问题
- 缺点:将内存缩小为原来的一半
- 一般用于新生代
3.标记-整理算法(Mark-Compact):
- 分为两个步骤,标记和整理,标记和标记-清除算法中类似;至于整理是指:将所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
- 一般用于老年代
4.分代收集算法:
- 将Java堆分为新生代和老年代
- 新生代:对象大量死亡,少量存活,选用复制算法
- 新生代中将内存分为3块:一块较大的Eden、两块较小的Survivor,每次使用Eden和一块Survivor
- 当回收时,将Eden和Survivor中的存活对象复制到另一块Survivor中,再清理掉Eden和第一块Survivor空间
- HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,即每次新生代可用内存空间为整个新生代容量的90%
- 老年代:对象存活率高,选用标记-清理算法或标记-整理算法
- 新生代:对象大量死亡,少量存活,选用复制算法
内存分配和回收策略
- 对象优先在Eden分配
- 大对象直接进入老年代
- 长期存活的对象将进入老年代
- 动态对象进行年龄判定
- 如果Survivor中相同年龄所有对象大小的总和大于Survivor空间的一半,则年龄大于或等于该年龄的对象就可以直接进入老年代