JAVA垃圾回收机制

jvm中的内存结构

JAVA垃圾回收机制_第1张图片

运行时数据区分为了两部分,分别为线程共享区和线程独占区。

  • 线程共享区中,程序计数器、虚拟机栈、本地方法栈3个区域是所有线程独有的一块区域,随线程而生,随线程而灭。栈中的栈帧随着方法的进入和退出而有条不紊地执行着入栈和出栈操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题,因为方法结束或者线程结束,内存自然就跟随着回收了。
  • Java堆和方法区则不一样,一个接口中或方法中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,只有在程序处于运行期间时才能知道会创建哪些对象,这部分内存的分配和回收是动态的,垃圾收集关注的是这部分的内存。

查找‘垃圾’的算法

1.引用计数法

为每一个创建的对象分配一个引用计数器,用来存储该对象被引用的个数。当该个数为零,意味着没有人再使用这个对象,可以认为“对象死亡”。每当有一个地方去引用它时候,引用计数器就增加1。但是这种方案存在严重的问题,就是无法检测“循环引用”,当两个对象互相引用,它俩的计数都不为零,因此永远不会被回收。而实际上对于开发者而言,这两个对象已经完全没有用处了。两个类相互是对方的成员变量,toString的时候相互调用,造成循环引用。因此,Java里没有采用这样的方案来判定对象的“存活性”。

2.可达性分析

基本思路是把所有引用的对象想象成一棵树,从树的根结点GC Roots出发,持续遍历找出所有连接的树枝对象,这些对象则被称为“可达”对象,或称“存活”对象。不能到达的则被可回收对象。

回收‘垃圾’的算法

首先我们先确定哪些是垃圾,哪些是存活对象,哪些是空白区域。

JAVA垃圾回收机制_第2张图片

第一种:标记-清理

第一步(标记),利用可达性遍历内存,把存活对象和垃圾对象进行标记。第二步(清理),我们再遍历一遍,把所有垃圾对象所占空间直接清空即可。简单方便,但容易产生碎片。

结果如下:

JAVA垃圾回收机制_第3张图片

第二种:标记-整理

第一步(标记):利用可达性遍历内存,把存活对象和垃圾对象进行标记。第二步(整理):把所有存活对象堆到同一个地方,这样就没有内存碎片了。适合存活对象多,垃圾少的情况。需要整理的过程

JAVA垃圾回收机制_第4张图片

第三种:复制

将内存按照容量划分为大小相等的两块,每次只使用其中的一块。当这一块用完了,就将还活着的对象复制到另一块上,然后再把使用过的内存空间一次性清理掉。不会产生碎片,但内存利用率太低,只用了一半。

JAVA垃圾回收机制_第5张图片

方法区的垃圾回收算法

方法区又叫做永久代。永久代的垃圾回收主要有两部分:废弃常量和无用的类。判断是否有用,之后回收

首先是废弃常量垃圾回收的一般步骤:

第一步:判定一个常量是否是废弃常量(没有任何一个地方对这个常量进行引用就表示是废弃常量)

第二步:垃圾回收

然后是无用的类垃圾回收的一般步骤:

第一步:判定一个类是否是“无用的类”(Java堆中不存在该类的任何实例,也就是该类的所有实例都被回收加载该类的ClassLoader已经被回收该类对应的Class对象在任何地方没有引用了,也不能通过反射访问该类的方法)

第二步:满足上面三个条件就可以回收了,但不是强制的。

堆的垃圾回收算法

java堆分为新生代(存刚刚创建的对象,垃圾多)、老年代(存活一段时间的对象,垃圾少)、永久代(永久存在的对象) 

新生代,采用第三种:复制(由于每次GC都会有大量新对象死去,只有少量存活。因此采用复制回收算法,GC时把少量的存活对象复制过去即可)

老年代,采用第二种:标记-整理(根据老年代的特点,这里仅仅通过少量地移动对象就能清理垃圾,而且不存在内存碎片化。也就是标记整理的回收机制。既然是标记整理算法,而且老年代内部也不存在着内存划分,所以只需要根据标记整理的具体步骤进行垃圾回收就好了)

垃圾回收器

如果说收集算法是内存回收的方法论,那么垃圾回收器就是内存回收的具体实现。

关键名词

1. 吞吐量

CPU用于运行用户代码的时间与CPU总消耗时间的比值。比如java虚拟机总运行了100分钟,用户代码时间99分钟,垃圾回收时间1分钟,那么吞吐量就是99%

2. 停顿时间

停顿时间指垃圾回收器正在运行时,应用程序的暂停时间

3. GC的名词

新生代GC:Minor GC

老年代GC:Major GC

第一种:Serial(单线程)

Serial回收器是最基本的新生代垃圾回收器,是单线程的垃圾回收器。采用的是复制算法。垃圾清理时,Serial回收器不存在线程间的切换。因此在单CPU的环境下,垃圾清除效率比较高。

第二种:Serial Old(单线程)

Serial Old回收器是Serial回收器的老生代版本,单线程回收器,使用标记-整理算法。在JDK1.5及其以前,它常与Parallel Scavenge回收器配合使用,达到较好的吞吐量,另外它也是CMS回收器在Concurrent Mode Failure时的后备方案。

第三种:ParNew(多线程)

ParNew回收器是在Serial回收器的基础上演化而来的,属于Serial回收器的多线程版本,采用复制算法。运行在新生代区域。在实现上,两者共用很多代码。在不同运行环境下,根据CPU核数,开启不同的线程数,从而达到最优的垃圾回收效果。

第四种:Parallel Scavenge(多线程)

Parallel Scavenge回收器也是运行在新生代区域,属于多线程的回收器,采用复制算法。与ParNew不同的是,ParNew回收器是通过控制垃圾回收的线程数来进行参数调整,而Parallel Scavenge回收器更关心的是程序运行的吞吐量。即一段时间内用户代码运行时间占总运行时间的百分比。

第五种:Parallel Old(多线程)

Parallel Old回收器是Parallel Scavenge回收器的老生代版本,属于多线程回收器,采用标记-整理算法。Parallel Old回收器和Parallel Scavenge回收器同样考虑了吞吐量优先这一指标,非常适合那些注重吞吐量和CPU资源敏感的场合。

第六种:CMS(多线程回收)

CMS回收器是在最短回收停顿时间为前提的回收器,属于多线程回收器,采用标记-清除算法

第七种:G1回收器

G1是 JDK 1.7中正式投入使用的用于取代CMS的压缩回收器。它虽然没有在物理上隔断新生代与老生代,但是仍然属于分代垃圾回收器。G1仍然会区分年轻代与老年代,年轻代依然分有Eden区与Survivor区。G1首先将堆分为大小相等的 Region,避免全区域的垃圾回收。这种使用区域划分内存空间以及有优先级的区域回收方式,保证G1回收器在有限的时间内可以获得尽可能高的回收效率。

 

 

 

 

你可能感兴趣的:(后端技术,java)