一、概述
正所谓垃圾收集,即内存回收,将一些用不到的对象等从内存中去掉,使内存充足,而不至于OOM。那就自然面临下面的三个问题?
- 哪些内存需要回收?
- 何时进行回收?
- 如何进行回收?
二、如何判断对象已死?(不再使用该对象)
只有先判断出该对象是否已死,才能确定是否对该对象占有的内存进行回收。
1、引用计数算法
对每个对象都有一个引用计数器,用来记录该对象被引用的次数。缺点就是很难解决对象之间的相互循环引用问题。
2、可达性分析算法
虚拟机会维持一个从根节点为GC Roots的对象引用树,当对象没有在该树上时则可回收。
三、如何回收(垃圾收集算法)
1、标记-清除算法
对可回收的对象进行标记,然后统一进行回收。
缺点:导致回收完之后内存不连续,以致于后续存储大对象内存不足
2、复制算法(新生代)
将内存分为两块AB,一块A存储,一块B空闲,当A空间不足进行垃圾回收时,将A中活跃对象直接复制到B,然后将A清空,解决了内存不连续问题。
缺点:一分为二,内存的使用率只有50%,直接造成内存浪费。并且对于活跃对象比较多的进行GC后复制,耗时耗CPU。
复制算法改进版
将新生代内存分为3块,eden,survivor1,survivor2。比例为8:1:1(默认可改)。因为研究表明新生代98%对象都是“朝生夕死”,真正能持续存活的比较少。
eden和survivor1 为新的对象分配内存,survivor2 存放MinorGC(对新生代的GC称呼)后存活下来的对象,survivor1和eden则被清空,即survivor1和survivor2轮流空闲,因为eden和一块survivor始终在使用,所以实际上浪费的内存只占10%。
当survivor内存不足以存放MonirGC存活下的对象,则向老年代进行分配担保(handle promotion),即将survivor对象放入老年代中。
3、 标记-整理算法(老年代)
因为老年代中对象存活时间比较长,所以不适合于复制算法。
标记-整理算法实质上是标记-清除算法的改进版,过程与之一模一样,只是后续会对回收后的内存进行整理而使其变成连续的块内存。
4、分代收集算法(当前)
根据对象的存活周期将内存划分为几块,如将java堆划分为新生代和老年代,每一个划分实行不同的垃圾收集算法。
新生代对象存活时间短,GC后存活对象少,则复制到survivor的对象也比较少,所以选用复制算法。
老年代存活率高,没有额外的空间进行担保,所以必须使用标记算法。
四、垃圾收集器(对上述算法的具体实现)
1、Serial 收集器
一个单线程进行垃圾回收,并且在回收的时候,暂停其他所有工作线程,直到收集结束。俗称Stop The World。
目前是虚拟机Client模式下的默认收集器
2、ParNew收集器
于Serial收集器类似,不同处在于,其是所线程进行垃圾回收。
是虚拟机在Server模式运行下首选的收集器,因为只有 ParNew收集器才能与CMS收集器配合工作。
3、Parallel Scavenge收集器(新生代)
新生代,复制算法,并行多线程。
于ParNew不同在于,其目标主要用来控制吞吐量
吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间)
如总共运行100分钟,其中垃圾收集运行1分钟,则吞吐量为99%。
高吞吐量的好处:高效率的利用CPU时间,尽快完成程序的运算任务,主要适合于大量在后台运算不需要与用户进行太多前台交互的任务。
无法与CMS收集器(老年代)配合—所谓配合即某个收集器专门用于新生代,某个专门用于老年代。
4、Parallel Old 收集器(老年代)
5、CMS收集器
6、G1收集器
五、对象的内存分配规则(哪些对象进入新生代哪些进入老年代)
六、何时回收
一个对象实例化时 先去看伊甸园有没有足够的空间
如果有 不进行垃圾回收 ,对象直接在伊甸园存储.
如果伊甸园内存已满,会进行一次minor gc
然后再进行判断伊甸园中的内存是否足够
如果不足 则去看存活区的内存是否足够.
如果内存足够,把伊甸园部分活跃对象保存在存活区,然后把对象保存在伊甸园.
如果内存不足,向老年代发送请求,查询老年代的内存是否足够
如果老年代内存足够,将部分存活区的活跃对象存入老年代.然后把伊甸园的活跃对象放入存活区,对象依旧保存在伊甸园.
如果老年代内存不足,会进行一次full gc,之后老年代会再进行判断 内存是否足够,如果足够 同上.如果不足 会抛OutOfMemoryError.