最近学习了周志明老师的《深入理解Java虚拟机》,收获颇多,留下一些学习笔记,供以后复习用。
一.学习目标
1.对象存活判断
2.GC(garbage collection)算法学习
3.垃圾回收器
4.内存分配与回收策略
对于GC学习,我们主要考虑三个问题:哪些内存需要回收 -> 什么时候回收 -> 如何回收
二.那些内存需要回收
内存运行时,程序计数器、本地方法栈和虚拟机栈是随着线程的产生而产生,随着线程的消亡而消亡的,这几部分的内存分配和回收是确定好了的,随方法结束或线程结束时,内存就紧跟着回收了。而Java堆和方法区不一样。一个接口中多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在运行期间才知道会创建哪些对象,故内存回收与分配重点关注的是堆内存和方法区内存。
三.什么时候回收(判断对象的存活)
对于方法区,永久代的垃圾收集主要回收两部分内容:废弃常量和无用的类。
对于堆,其中存放的是对象实例,对于对象实例的回收,我们首先要判断哪些对象是“存活的”,对于那部分“死亡的”对象,就是我们要回收的。判断对象的存活有两种方法:
引用计数算法
可达性分析算法
引用计数算法:给对象添加一个引用计数器, 每当有一个地方引用它时, 计数器值+1, 引用失效, -1, 为0的对象不能被使用。
优势:实现简单,效率高。
缺点:无法解决对象相互引用的问题——会导致对象的引用虽然存在,但是已经不可能再被使用,却无法被回收。
可达性分析算法:通过一系列的称为”GC Roots”的对象作为起始点, 从这些节点开始向下搜索, 搜索走过的路径称为引用链(Reference Chain), 当一个对象到GC Roots不可达(也就是不存在引用链)的时候, 证明对象是不可用的。如下图: Object5、6、7 虽然互有关联, 但它们到GC Roots是不可达的, 因此也会被判定为可回收的对象。(注意:不可达的对象, VM也并不是马上对其回收, 因为要真正宣告一个对象死亡, 至少要经历两次标记)
在Java, 可作为GC Roots的对象包括:
方法区: 类静态属性引用的对象;
方法区: 常量引用的对象;
虚拟机栈(本地变量表)中引用的对象.
本地方法栈JNI(Native方法)中引用的对象。
四.如何回收
垃圾收集算法
1.标记清除算法
分为标记和清除两个阶段,先标记出需要回收的对象(可达性分析算法或者引用计数算法),在标记完成后统一回收所有被标记的对象。
不足之处:效率问题,标记和清除效率都不高。空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾回收动作。
2.复制算法
将可用内存划分为大小相等的两块,每次只使用其中的一块。当这块用完了,就将还存活的复制到另一块上,然后将这一块一次性清除。商业虚拟机都是采用该方法来回收新生代,新生代98%都是朝生夕死的。将内存分为较大Eden和两个较小的survivor空间。每次使用其中一块Eden和survivor,回收时将存活的对象一次性地复制到另一块survivor中,再清理掉之前的。HotSpot虚拟机Eden与Survivor默认的大小比例为8:1:1。survivor空间不够时,需要依赖其他内存(老年代)进行分配担保,即让对象进入老年代。
3.标记整理算法
复制在对象存活率较高时效率很低。根据老年代的特点提出该算法。标记过程同标记清除一样,但不是直接对可回收对象进行清理,而是让存活对象朝着一端移动,然后直接清理掉端边界外的内存。
4.分代收集算法
根据各年代特点分别采用最适当的GC算法。在新生代:中每次垃圾收集都能发现大批对象已死, 只有少量存活. 因此选用复制算法, 只需要付出少量存活对象的复制成本就可以完成收集。在老年代: 因为对象存活率高、没有额外空间对它进行分配担保, 就必须采用“标记—清理”或“标记—整理”算法来进行回收, 不必进行内存复制, 且直接腾出空闲内存。即:
新生代:存活率低,使用复制算法
老年代:存活率高,使用“标记-整理”或“标记-清除”算法
垃圾收集器
重点笔记:
并行(Parallel):多条垃圾收集线程
并发(Concurrent):用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户线程在继续运行,而垃圾收集程序运行于另外一个CPU上。
新生代:Serial收集器 ParNew收集器 Parallel Scavenge收集器
老年代:Serial Old收集器 Parallel Old收集器 CMS收集器
新生代
1. Serial收集器
最悠久,最基本的收集器;单线程收集器,只使用一个CPU或一条收集线程去完成垃圾收集工作,且在收集时,必须暂停其他所有的工作线程,直到收集结束。新生代采取复制算法。老年代采取标记整理算法。在进行垃圾收集时必须暂停其他所有的工作线程,即“Stop The World”。依然是虚拟机运行在Client模式下的默认新生代收集器。简单而高效(与其他收集器单线程相比),对于单个CPU环境来说,Serial收集器由于没有线程交互的开销,专注于垃圾回收,因此能获得最高的单线程收集效率。
2.ParNew收集器
Serial收集器的多线程版。多条线程进行垃圾收集,采用复制算法。其余和Serial收集器一样。目前唯一能与CMS收集器配合工作。
3. Parallel Scavenge收集器
新生代收集器,多线程并行、使用复制算法。
CMS的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间,而PS收集器的目的则是达到一个可控制的吞吐量。吞吐量即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集的时间))。
作者:Swen_9826
链接:https://www.jianshu.com/p/9d09f93001e4