垃圾收集器与内存分配策略
为什么学习垃圾收集?
看起来jvm好像一切帮你做好,但是当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这种自动化的技术进行监控和调节。
垃圾回收区域
虚拟机栈,本地方法栈,程序计数器是线程私有的,和线程同生共死,当线程销毁时,内存自然回收,所以这部分不是考虑的重点。
所以研究重点应该在方法区和堆,而方法区的回收效率较低,重点在堆。
引用计数
对象有一个引用计数器 new String()
有引用,计数器加一,String jiajun=new String()
引用失效,计数器减一,jiajun=null;
出现一个问题
Person jia=new Persoon();
Person jun=new Person();
jia.bro=jun;
jun.bro=jia;
jia=null;
jun=null;
此时虽然我们没有办法用jia和jun这两个对象,也就是这个是垃圾了,但是两个对象又互相引用,如果用这种算法是无法进行回收
可达性分析
GC Roots的对象:虚拟机栈中引用的对象(比如方法中定义的对象),本地方法栈中引用的对象,方法区中类静态属性引用的对象(static),方法区中常量应用的对象(final)
通过GC Roots对象作为起点,当GC Roots到一个对象没有引用链的话,那么就证明这个而对象不可用(如下图)
垃圾收集算法
标记清除
确定回收对象,进行标记,然后回收标记的对象
一个问题是标记清除效率不高
一个问题是标记清除后产生大量不连续的内存碎片,但需要分配较大对象的时候,因为没有足够大的连续空间分配给此对象,此时会触发另一次垃圾收集
复制
解决内存碎片的问题
将内存分为等大的A区和B区,创建对象时,存放于A区,当A区空间用完之后,将A区存活的对象复制到B区,然后A区清空,这样的话,就避免了内存碎片的问题
但是又有一个问题,这个时候我们只用到了一半的空间,所以我们要向办法提高空间利用率
研究发现,新生代对象大多都是朝生夕死,也就是存活的对象不多,那么我们就没必要分配一半的空间用于“粘贴”
所以虚拟机将新生代分为Eden和Surivior两个区,比例为8:1:1,可用的内存为一个伊甸区个一个存活区,一个存活区用于“粘贴,”,也就是新生代可用内存空间为容量的90%,这样的话就提高了空间了利用率
但是又有一个问题,如果这一个存活区存放不了复制的的对象,那么怎么办?于是,如果这些对象放不下,将直接进入另一块区域老年代
标记整理
对于朝生夕死的新生代来说,复制算法是不错的选择。但是对于存活率高的对象不是很好的选择,因为要进行较多的复制操作,效率会降低
过程:确定回收对象,进行标记,让存活的对象向一端移动,然后清理掉端边界以外的内存
那么这样的话,不会有内存碎片的问题,也没有复制过多效率降低的问题
分代收集
上面几种方法的综合利用
根据新生代和老年代的特点,分别选用适用的算法。
新生代存活率,那么可以选择复制算法
老年代存活率高,可以采用标记清理或标记整理算法
内存分配与回收策略
- Minor GC,新生代GC,回收速度快
Major GC/ Full GC , 收集整个堆,包括yong gen,old gen,perm gen,回收速度慢