Java垃圾回收机制

在面试中我之前被闻到过,什么是垃圾回收机制?能否讲讲垃圾回收机制?
当时说的有点语无伦次,没有很好的逻辑性,特此来总结。

垃圾回收机制

垃圾回收机制主要做了两件事情
1.跟踪并监控每一个java对象,当某个对象处于不可达状态时,回收该对象所占用的内存。
2.清理内存分配,回收过程中产生的内存碎片。

JVM内存模型

因为垃圾回收都是在内存中进行的,因此我们有必要了解一下jvm的内存结构。
Java垃圾回收机制_第1张图片
程序计数器:是线程私有区,是内存中较小的区域,是当前线程执行字节码的行号指示器。
虚拟机栈:是线程私有的,该区域描述的是java方法执行的动态内存模型,每一个方法在执行的时候都会产生一个栈帧,用来存储局部变量等信息,操作数栈、动态链接、方法出口等信息。
本地方法栈:是为本地native服务的。
:线程共享的一块区域,用来存放对象实例,该区域是垃圾回收的主要区域,堆空间不足的时候会抛出OOM异常(内存溢出)。
方法区:线程共享的区域,存储JVM加载的类的信息、常量、静态变量以及编译后的代码等数据,当方法区空间不足时就会抛出OOM异常。方法区中还有一个运行时常量池,class文件中的常量池在类加载之后就放入运行时常量池。
除了java虚拟机规定的这几个区域之外,还存在一个堆外内存,即直接内存,直接内存可以减少IO时内存复制,实现零拷贝,而且没有GC,减少了垃圾回收的工作,加快了复制速度,同样该区域也难以控制,如果内存泄露很难排查。

如何判断一个对象是否是垃圾呢?

1.引用计数法
为每一个创建的对象分配一个引用计数器,用来存储该对象被引用的个数,当该对象的引用计数为0时,则代表没有地方在被使用了,就可以被认为是“垃圾对象”。每当一个地方引用了一个对象之后引用计数加1。
但是该方法有一个缺陷:无法检测循环引用的问题,当两个对象相互引用时,这两个对象的引用计数器都不是0,但这两个对象对我们来说已经没有用了,但是没有办法回收掉。因此Java中没有采用这种方法判断对象的“存活性”。
2.可达性分析
可达性行分析的基本思想就是通过一个“GC Root”对象为起点,这些节点开始向下搜索,节点走过的路径称为引用链,当一个对象到GC Root时没有任何引用链,则证明此对象不可用。
Java垃圾回收机制_第2张图片
那么有哪写点可以作为GC Root呢?
1.虚拟机栈中的引用对象
2.方法区中静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI引用的对象

垃圾回收算法

1.标记清除算法
垃圾回收器先从根开始访问所有可达对象,将其标记为可达状态,然后再遍历一次整个内存区域,把没有标记的对象进行回收处理。
未回收之前内存:
Java垃圾回收机制_第3张图片
垃圾回收之后:
Java垃圾回收机制_第4张图片
这种算法需要两次循环遍历算法,第一次遍历将可达性对象标记出来,第二次在便利的时候将不可达对象进行回收,造成遍历的成本比较大,因此造成程序暂停的时间随堆空间大小线性增加。而且通过该算法回收回来的内存往往是不连续的,导致回收后的内存碎片化严重。
特点:①两次遍历成本比较高,随着堆空间的变大遍历时间也会增加
②垃圾回收之后内存碎片化

2.标记整理算法
首先进行一次可达性遍历,将可达与不可达对象区分出来;将所有存活的对象搬迁到一起,这个过程也成为内存压缩。
回收之后的内存:
Java垃圾回收机制_第5张图片
这样就避免了之前标记清除算法中的内存碎片化的问题。
特点:①适合存活对象多,垃圾对象少的情况
②需要对可达对象与不可达对象的整理。

3.复制算法
将内存按照容量划分为两个相等大小的控件,从根开始访问每一个关联的可达对象,当一块用完了之后,将或者对象赋值到另一块上 ,然后再一次性回收整个空间。
回收之前:
Java垃圾回收机制_第6张图片
通过复制算法回收垃圾之后:
Java垃圾回收机制_第7张图片
特点:①并不会产生内存碎片
②内存使用率太低

分代回收

在说垃圾分代回收之前我们要先讲一下方法区中的回收。
方法区又称永久代。永久代的垃圾回收主要有两种:废弃常量和无用的类。
废弃常量:没有任何地方对这个常量进行引用就表示是废弃常量。
回收无用的类
①Java堆中不存在该类的任何实例,也就是该类的所有实例都被回收
②加载该类的ClassLoader已经被回收
③该类对应的Class对象在任何地方没有引用了,也不能通过反射获取到该类的方法。
满足以上三个条件就可以回收,但不是强制的。在《Java虚拟机规范》说到过不要求虚拟机对方法区进行回收。并且方法区进行垃圾回收的性价比比较低。

分代回收
Java垃圾回收机制_第8张图片
java堆控件分为三部分:
新生代:刚刚创建的对象。
老年代:存活了一段时间的对象。
永久代:永久存在的对象。

现在的垃圾回收器用分带的方式来采用不同的回收设计。分代回收的基本思路是根据对象生存时间长短,将堆中的内存分为三个代:

年轻代
特点:存活对象少,垃圾多。
新生代对象中,由于每次GC都会有大量新对象死去,只有少量存活,因此采用复制算法,GC时只需要将少量存活的对象复制过去即可。
工作原理
①首先Eden对外提供堆内存,当Eden快满的时候触发垃圾回收机制,将存活的对象放入到SurvivorA区,清空Eden区。
②Eden清空之后继续对外提供堆内存。
③当Eden再次填满之后,对Eden和SurvivorA同时进行垃圾回收,将存活的对象放入SurvivorB区中,同时清空Eden和SurvivorA区。
④当某个Survivor被填满时,将多余的对象放入到Old区。
⑤当Old区被填满时,进行下一轮的垃圾回收。

老年代
特点:存活对象多,垃圾少。
Old代的大部分对象都是久经考验,没有那么容易死,并且随着时间堆积Old代的对象会越来越多,因此Old代的空间要比年轻代空间更大。
根绝老年代的特点我们可以知道Old代很少有对象会死掉所以Old代进行垃圾回收的频率无需太高;由于老年代不容易死掉,所以在垃圾回收需要更长时间来完成。
针对以上特点,Old代通常采用标记整理算法,这种算法避免复制大量对象,并且Old不会很快死亡,回收过程不会产生大量的内存碎片。

永久代
永久代主要用于装载Class、方法等信息,默认64M,垃圾回收器通常不会回收永久代中的对象。对于一些需要加载很多类的服务器程序,往往需要加大永久代的内存,否则可能会因为内存不足导致程序终止。

垃圾回收器

前面我们都介绍了垃圾回收器的所有算法,垃圾回收器就是实现上面讲到算法的一个东西。
在了解垃圾回收器之前我们要先知道几个名词:
吞吐量:CPU用于运行用户代码和CPU总消耗时间的占比。
停顿时间:之垃圾回收器正在运行时,程序停顿的时间。
MinorGC:新生代GC
MajorGC:老年代GC
并发与并行:①串行:垃圾回收线程在进行垃圾回收时,此时用户线程处于等待状态。
②并发:用户线程与垃圾回收线程交替执行
③并行:用户线程和多条垃圾回收线程在不同的CPU上执行。

垃圾回收器是基于HotSpot虚拟机:
Java垃圾回收机制_第9张图片
Serial收集器(复制算法):新生代单线程收集器,进行标记和清理时不存在线程切换,在单核CPU中执行效率比较高。
Serial Old收集器(标记整理算法):老年代单线程收集器。
ParNew收集器(复制算法):新生代并行收集器,是在Serial收集器的基础上演化而来,在多核CPU情况下比Serial更好。
Parallel Scavenge收集器(复制算法):新生代并行收集器,追求高吞吐量,高效利用CPU。
Parallel Old收集器(标记整理算法):老年代并行收集器,优先考虑吞吐量。
CMS收集器(标记清除算法):老年代并行收集器,以获取最短停顿时间为目标,具有高并发、短停顿的特点,追求最短GC停顿时间。
G1收集器(标记整理算法):Java堆并行收集器。G1收集器是JDK1.7出现的垃圾收集器,由于采用的算法不会产生内存碎片,并且面向的范围是整个堆(包括老年代、新生代)。

整理一个表格会更加直观:

收集器 串行/并行/并发 新生代/老年代 算法 目标 适用场景
Serial 串行 新生代 复制算法 响应速度优先 单核CPU情况下的Client模式
Serial Old 串行 老年代 标记整理 响应速度优先 单核CPU情况下的Client模式、CMS后备预案
ParNew 并行 新生代 复制算法 响应速度优先 多核CPU情况下的Server模式下与CMS配合
Parallel Scaveneg 并行 新生代 复制算法 吞吐量优先 在后台运算而不需要太多交互的任务
Parallel Old 并行 老年代 标记整理 吞吐量优先 在后台运算而不需要太多交互的任务
CMS 并发 老年代 标记清除 响应速度优先 集中在互联网站或B/S系统服务端山东个java程序
G1 并发 两者都包含 标记整理+复制算法 响应速度优先 面向服务端应用,将来替换CMS

到这了Java垃圾回收器的相关内容基本上总结完了,用来面试的话也没有问题了,希望对大家有帮助。

你可能感兴趣的:(Java,面试)