第3章 垃圾回收与内存分配策略
主要讲了有关垃圾回收的两个问题:1、什么样的对象是垃圾需要被回收 2、以何种算法回收垃圾
3.2 对象已死吗
如何判断堆里的对象是否需要回收(垃圾回收针对方法区吗)。
3.2.1 引用计数法
给每一个对象添加一个引用计数器,当该对象存在一个引用的时候就加一,引用失效就减一,当一个对象的引用计数器为0的时候代表该对象需要被回收。缺点是容易产生循环引用。
3.2.2 可达性分析
从一系列被称为GCRoots的对象出发,沿着GCRoots对象的引用链搜索,如果一个对象没有任何GCRoots的引用链可以达到的话,需要被回收。
可以被用作GCRoots的有:
- 虚拟机栈中栈帧的本地变量表引用的对象
- 本地方法栈引用的对象
- 方法区中static变量引用的对象
- 方法区常量引用的对象(常量怎么会引用对象?)
3.2.3 强、软、弱、虚引用与垃圾回收
在jdk1.2之前对象只有引用和没有被引用两种状态,对于没有被引用的对象都会被垃圾回收。如果希望那些没有被引用的对象能够存活一段时间那么就需要扩展“没有被引用”的概念,也就有了软、弱、虚引用。这三种引用都是Reference的子类,除了虚引用外都可以通过get方法返回一个对象的地址,他们的区别在于垃圾回收时的策略不同。
- 强引用,普遍存在的引用,不会被回收
- 软引用,地位稍微低一点的引用,如果内存充裕的话软引用会一直存在,如果在内存不足即将发生OOM之前会把所有软引用的对象回收
- 弱引用,如果一个对象只有弱引用,那么无论是否有可能发生OOM在辣鸡回收的时候一定会被会输
- 虚引用,不能通过该引用获得其指向的对象,虚引用也不会影响辣鸡回收行为,虚引用只会在辣鸡回收的时候有提示作用
3.2.4 生存还是死亡
在一个对象GC不可达的时候并不是立即回收,而是要经过虚拟机判断是否有必要执行finalize(),如果该方法没有被重写或者已经被执行,那么该对象的自救失败,否则尝试执行一次该方法但不保证该方法会被执行。不推荐使用该方法。
3.2.5 回收方法区
之前的分析是针对堆的垃圾回收。方法区(HotSpot虚拟机的永久带)也有垃圾回收,但其条件较为苛刻,代缴较为高昂,效率较为低下,虚拟化规范中并没有对方法区垃圾回收做出规范。
方法区的垃圾回收针对两部分:1、常量 2、类
常量被回收判断比较简单,如果一个常量没有指向他的引用那么他就会被回收。
类的回收较为苛刻,只有满足三个条件才能被回收:1、该类所有的对象已经被回收 2、该类的CLassLoader被回收 3、该类的Class对象没有任何引用。在大量使用反射、动态代理的框架内要注意方法区的垃圾回收。
3.3 垃圾收集算法
3.3.1 标记-清除算法
首先标记出所有需要回收的对象然后统一回收可回收的对象。
缺点:效率低下,会带来内存碎片
3.3.2 复制算法
把内存划分为两个部分,每次在其中一个部分上进行内存分配当垃圾回收的时候把其中一个部分的对象复制到剩余空间上,避免了内存碎片。
把内存(新生代)按照8:1:1的关系划分成Eden和两个survivor区域,每次在其中的一个survivor和Eden区域分配内存,垃圾回收的时候把存活的对象分配到另一个survivor上去,循环往复的利用两个survivor区域。
3.3.3 标记-整理
如果内存对象的存活率较高在复制算法的时候会有大量的移动操作,这种算法不适用与对象生存率较高的老年代。老年代使用标记整理算法,每次把存活的对象朝着一个方向移动,然后直接清理边界意外的内存空间。
3.3.4 分代收集算法
和上面的三种不同,这并不是一种特定的收集算法,这是一种对内存划分的思想。根据对象的存活周期把内存划分成不同的区域,一般来说把堆根据对象存活时间的长短划分成新生代和老年代,新生代具有“朝生暮死”的特点所以可以采用复制算法,老年代的存活时间较长一般采用标记清楚或者标记整理算法。
3.5 垃圾收集器
垃圾回收主要是针对新生代和老年代,新生代和老年代都是堆内存,也有针对永久代(方法区)的垃圾回收。
3.5.1 Serial 收集器
特点:单线程的垃圾收集器,是最古老的垃圾收集器。在执行回收的时候会有臭名昭著的"stop-the-world"情况发生,一定程度上会降低执行性能。
场景:在虚拟机运行在clinet模式下默认新生代的垃圾收集器,因为在client模式下堆内存较小,一次垃圾收集的时间也较短,'stop-the-world'的时间也短;在单线程情况下没有线程交互的开销。
3.5.2 ParNew 收集器
特点:Serial的多线程版本;单线程的环境下由于存在多线程的开销效率小于Serial收集器
场景:虚拟机在Server模式下新生代垃圾收集器,因为只它可以和CMS收集器一起工作。
3.5.3 Parallel Scavenge 收集器
特点:也是一个多线程的适用于新生代的垃圾收集器,该收集器的特点是其关注点为“可控制的吞吐量”而非“缩短stop-the-world”。吞吐量=运行用户代码时间/(用户代码时间+垃圾收集时间),高吞吐量意味着能够更快的处理完用户工作线程,停顿时间短倾向于用户体验,由此可见Parallel Scavenge适用于Server模式等不需要和用户交互的模式。所以该收集器也被称为吞吐量优先收集器。
3.5.4 Serial Old 收集器
特点:Serial收集器的老年版本
算法:标记-清除
场景:Client模式下的老年代收集器,与吞吐量优先收集器搭配使用;CMS的后备垃圾收集器。
3.5.5 Parallel old
特点:Parallel Scavenge的老年版本,在没有该收集器之前如果新生代使用了吞吐量优先收集器,老年代是能使用Serial Old收集器
算法:标记-整理
场景:对吞吐量比较敏感的服务端,和Parallel Scavenge组合使用
3.5.6 CMS收集器
特点:标记阶段需要stop-the-world清除线程是与用户线程并发执行的,减少了stop-the-world的时间
算法:标记-清除
3.5.7 G1收集器