一、关于对象死亡
![](https://img-blog.csdn.net/20170428102411521?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHVhbm11Y2hlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20170428102520554?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHVhbm11Y2hlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20170428102548086?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHVhbm11Y2hlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20170428102606336?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHVhbm11Y2hlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
![](https://img-blog.csdn.net/20170428102626978?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZHVhbm11Y2hlbmc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
1. 引用计数算法
在对象中添加一个引用计数器,当对象被引用时计数器加1;当引用失效时计数器减1;任何时刻计数器都为0的对象就是不可被使用的
但Java语言没有使用引用计数法来管理内存,其中最主要的原因是它很难解决对象之间的相互循环引用的问题
2. 根搜索算法(被Java语言使用)
从GC Roots对象开始向下搜索,如果能到达对象,说明该对象时存活的
能成为GC Roots对象的有:
虚拟机栈中局部变量引用的对象
本地方法栈中native方法引用的对象
方法区中常量引用的对象
方法区中静态变量引用的对象
3. 引用的类型(4种)
(1)强引用:垃圾收集器永远无法回收强引用对象
(2)软引用:当内存不够时才会回收软引用对象,如果回收后内存还不够,才会抛出内存溢出异常
(3)弱引用:无论当前内存是否足够,弱引用对象都会被回收掉
(4)虚引用:设置虚引用的唯一目的就是在对象被收集器回收时收到一个系统通知
4. 如何判断对象死亡
判断对象死亡要经过两次标记:
(1)根搜索发现对象不可达,被第一次标记,并筛选出有必要执行finalize()方法的对象,将这个对象放置到一个F-Queue队列中,虚拟机将启动一个线程去执行
(2)如果F-Queue队列中的对象无法与引用链上的对象建立关联,就会被第二次标记,真正死亡
5. 回收方法区
主要回收废弃常量和无用的类
废弃常量:
没有任何对象引用了该常量
无用的类:
(1)该类所有的实例都已经被回收
(2)加载该类的类加载器被回收
(3)该类对应的Class对象没有被引用,无法通过反射访问该类的方法
二、垃圾收集算法
1. 标记-清除算法:
统一标记,统一回收
缺点:
(1)效率不高
(2)空间问题
2. 复制算法:
将内存分为大小相等的两块,每次只使用其中的一块,将这块中存活的对象复制到另一块中,然后对前者整体回收
现在的商业虚拟机一般将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor
当回收时,将Eden和Survivor中还存活的对象一次性拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间
HotSpot虚拟机默认Eden和Survivor的大小比例是8:1
3. 标记-整理算法:
统一标记后,将存活的对象移到一端,然后对另一端进行回收
4. 分代收集算法:
将Java堆分成新生代和老年代,
对新生代采用复制算法,对老年代采用标记-清除或标记-整理算法
三、垃圾收集器
新生代的收集器主要使用复制算法,老年代的收集器主要使用标记-整理和标记-清除算法
1. Serial收集器
单线程,在进行垃圾收集时,必须暂停其他所有的工作线程
优点:简单高效,没有线程交互的开销,专心做垃圾收集,所以可以获得最高的单线程收集效率
应用场景:适合客户端虚拟机的新生代收集器
2. ParNew收集器
多线程,可看作Serial收集器的多线程版本
只有Serial收集器和ParNew收集器能跟CMS收集器配合工作
应用场景:主要用于服务器端虚拟机的新生代收集器,适合多CPU的情况,单CPU下还性能不如Serial收集器
3. Parallel Scavenge收集器
多线程,主要关注吞吐量,可以提高CPU的利用率,适合后台运算
采用自适应调节策略,将内存调优的任务交给虚拟机去完成
4. Serial Old收集器
Serial收集器的老年代版本,单线程,使用标记-整理算法,主要用于客户端虚拟机
Server模式下有两大用途:
(1)与Parallel Scavenge收集器搭配使用
(2)作为CMS收集器的后备预案
5. Parallel Old收集器
是Parallel Scavenge的老年代版本,使用多线程和标记-整理算法
应用场景:在注重吞吐量和CPU资源敏感的场合,可以优先考虑Parallel Scavenge和Parallel Old组合使用
6. CMS收集器
优点:并发低停顿,适合服务器端提高响应速度
缺点:
(1)对CPU资源敏感
(2)无法处理浮动垃圾
(3)会产生大量空间碎片
只能跟Serial和ParNew搭配使用
7. G1收集器
原理:将java堆分成大小固定的独立区域,优先回收垃圾最多的区域
优点:
(1)使用标记-整理算法,不会产生空间碎片
(2)可以精确控制停顿,指定垃圾收集的时间
四、内存分配与回收策略
所谓内存管理,其实就是指内存的分配与回收
java虚拟机的内存分配策略主要有:
(1)对象优先在Eden区分配
(2)大对象直接进入老年代
(3)对象存活到一定年龄将进入老年代
(4)动态对象年龄判定
(5)空间分配担保
Java堆的内存分配:
一般将java堆分成新生代和老年代两块,默认比例为1:2
新生代又被分为Eden区和两个Survivor区(from Survivor/to Survivor),比例一般为8:1:1
实际使用Eden区和from Survivor区进行内存分配,to Survivor区用于复制用,当Eden区内存不够时将发生一次Minor GC,如果to Survivor内存也不够,将通过分配担保机制转移到老年代
Minor GC:新生代GC,一般回收速度比较快(复制算法)
Major GC/Full GC:老年代GC,速度一般比Minor GC慢10倍以上(标记-整理/标记-清除)
空间分配担保:
在进行Minor GC时,如果Survivor的内存不够容纳所有存活的对象,就需要老年代进行分配担保,让Survivor无法容纳的对象直接进入老年代。
但这时要进行如下判断:
如果之前进入老年代的平均大小大于老年代的剩余空间,就直接进行一次Full GC;如果小于,则查看是否允许担保失败;如果允许,则只进行Minor GC;如果不允许,则要进行一次Full GC
(所谓分配担保,就是允许新生代对象进入老年代;所谓允许担保失败,就是允许新生代不进入老年代;所谓不允许担保失败,就是新生代必须可以进入老年代)