GC-垃圾回收
确定对象是否垃圾
- 引用计数法
对于某个对象而言,只要应用程序中持有该对象的引用,就说明该对象不是垃圾,如果一个对象没有任何指针对其引用,它就是垃圾。
弊端 :如果AB相互持有引用,导致永远不能被回收。
- 可达性分析算法
通过GC Root的对象,开始向下寻找,看某个对象是否可达
能作为GC Root:类加载器、Thread、虚拟机栈的局部变量表、static成员、常量引用、本地方法栈的变量等。
GC的具体回收方法
- 标记-清除
找出内存中不需要回收的对象,并且把它们标记出来此时堆中所有的对象都会被扫描一遍,从而才能确定需要回收的对象,比较耗时。
清除掉被没被标记的对象,释放出对应的内存空间,然后将剩下的对象标记复原。
标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
- 标记和清除两个过程都比较耗时,效率不高
- 会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
- 标记-压缩
标记过程仍然与"标记-清除"算法一样,但是后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
让所有存活的对象都向一端移动,清理掉边界以外的内存。
- 复制算法
将内存划分为两块相等的区域,每次只使用其中一块,如下图所示:
当其中一块内存使用完了,就将还存活的对象复制到另外一块上面,然后把已经使用过的内存空间一次清除掉。
缺点:空间利用率降低。
分代收集算法
Young区:通常使用复制算法
Old区:通常使用标记整理算法
垃圾收集器
如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现,说白了就是落地咯
- 年轻代和老年代的垃圾回收器组合
Serial收集器
Serial收集器是基本、发展历史悠久的收集器,曾经(在JDK1.3.1之前)是虚拟机新生代收集的唯一选择。
它是一种单线程收集器,不仅仅意味着它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是其在进行垃圾收集的时候需要暂停其他线程。
优点:简单高效,拥有很高的单线程收集效率
缺点:收集过程需要暂停所有线程
算法:复制算法
适用范围:新生代
应用:Client模式下的默认新生代收集器
Serial Old收集器
Serial Old收集器是Serial收集器的老年代版本,也是一个单线程收集器,不同的是采用"标记-整理算法",运行过程和Serial收集器一样。
ParNew收集器
可以把这个收集器理解为Serial收集器的多线程版本。
优点:在多CPU时,比Serial效率高。
缺点:收集过程同样需要暂停所有应用程序线程,单CPU时比Serial效率差。
算法:复制算法
适用范围:新生代
应用:运行在Server模式下的虚拟机中首选的新生代收集器
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
采用的是"标记-清除算法",整个过程分为4步
- 初始标记:应用阻塞,找到所有GC ROOT
- 并发标记:标记被引用的GC
- 重新标记:应用阻塞,重新标记,在并发标记过程中新增的引用。
- 并发清理:清除掉垃圾
由于整个过程中,并发标记和并发清除,收集器线程可以与用户线程一起工作,所以总体上来说,CMS收集器的内存回收过程是与用户线程一起并发地执行的。
优点:并发收集、低停顿
缺点:产生大量空间碎片、并发阶段会降低吞吐量
Parallel Scavenge收集器
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器,看上去和ParNew一样,但是Parallel Scanvenge更关注系统的吞吐量。
吞吐量=运行用户代码的时间/(运行用户代码的时间+垃圾收集时间) 比如虚拟机总共运行了100分钟,垃圾收集时间用了1分钟,吞吐量=(100-1)/100=99%。
若吞吐量越大,意味着垃圾收集的时间越短,则用户代码可以充分利用CPU资源,尽快完成程序的运算任务。
Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和"标记-整理算法"进行垃圾回收。
G1收集器
- G1的优势
并行与并发
分代收集(仍然保留了分代的概念)
空间整合(整体上属于“标记-整理”算法,不会导致空间碎片)
可预测的停顿(比CMS更先进的地方在于能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒)
使用G1收集器时,Java堆的内存布局与就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
- 工作流程
- 初始标记
- 并发标记
- 最终标记:前三步和CMS区别不大
- 筛选回收:根据算法排列出region被回收的优先级,然后根据设置的时间按照优先级最高原则进行回收。
垃圾收集器总结
串行收集器->Serial和Serial Old
只能有一个垃圾回收线程执行,用户线程暂停。 适用于内存比较小的嵌入式设备 。
并行收集器【吞吐量优先】->Parallel Scanvenge、Parallel Old
多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。 适用于科学计算、后台处理等若交互场景 。
并发收集器【停顿时间优先】->CMS、G1 用户线程和垃圾收集线程同时执行(但并不一定是并行的,可能是交替执行的),垃圾收集线程在执行的时候不会停顿用户线程的运行。 适用于相对时间有要求的场景,比如Web 。
吞吐量:应用程序从开始到结束,进行GC的总时间。 停顿时间:每次GC的时候需要停顿的时间。
JVM性能调优
- 性能优化不是只靠学习才能学会的。只有你自己真正的解决过许多问题才能明白优化的真谛。
GC总结
- 内存泄漏与内存溢出
内存泄露:对象无法得到及时的回收,持续占用内存空间,从而造成内存空间的浪费。
内存泄露:内存泄漏到一定的程度就会导致内存溢出,但是内存溢出也有可能是大对象导致的。
- young gc会有stw吗
所有的GC都会有 stop-the-world
- major GC 和 Full GC的区别
major GC:是对老年代进行垃圾回收 Full GC:是对 (整个堆 + 元空间) 进行垃圾回收
- G1与CMS的区别
G1:标记-压缩,整个堆进行回收,采用region分快,不会产生碎片化问题
CMS:标记-清除,老年代进行回收,
- 什么是直接内存
直接内存是在java堆外的、直接向系统申请的内存空间。通常访问直接内存的速度会优于Java堆。因此出于性能的考虑,读写频繁的场合可能会考虑使用直接内存。
- 不可达对象一定会回收吗
不一定,Object有一个 finalize(),所以垃圾回收至少会对对象进行二次判断,第一次先执行 finalize() 方法,并且标记该对象已经执行过该方法,第二次如果该对象需要被GC,并且方法已经被执行,直接回收掉。
finalize()方法只会执行一次。
- 方法区中的无用字节码文件信息进行回收
该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
加载该类的 ClassLoader 已经被回收。
该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收,一般情况很难满足上述条件
- 不同的引用
强引用:只要该对象被强引用就不会被回收
软引用:当GC完后还需要释放空间,就会回收软引用引用的对象
弱引用:每次GC的时候被弱引用引用的对象都会被回收
虚引用:不是很理解,暂时理解为没有被引用。