1.GC引入
(1) GC是什么?
GC (garbage collection),就是垃圾回收的意思。不管是在C中还是Java 中,都要进行垃圾回收,字C中的垃圾回收是程序员手动写代码完成回收的,可靠性更高。Java中 是JVM中的里边自己实现的一种自动回收垃圾的机制。
(2)为什么要进行垃圾回收?
在程序执行的过程中,有的变量的生命周期是很短的,这些变量可能只用一会会,用完之后,没有引用指向他们,他们就是无用的变量。要是将这些变量放在内存中,就是一种浪费。要是不把这些不用的变量、数值及时的清理掉,就会导致CPU的性能越来越低。在程序不停的调用方法时,会导致OOM 产生。
(3)什么是垃圾?
在java中,要是对象实例,没有指向他们的引用就表示这些这些变量,实例就是没用的,就是垃圾。就是要进行回收的对象。
(4)什么时候进行垃圾回收?
在创建新的对象、类加载等操作时,先要划分内存空间。要是划分的空间发现不足,就会进行GC操作,来清理内存中的无用数据,变量,让内存有更大的空间。
2.GC中的内存划分
这部分讲的是在哪些区域会存在GC操作。
(1)在JVM中的
方法区
里边会存在:
在 jdk 1.7中叫方法区。在 1.8中叫元空间。在GC中叫永久代
虽然叫做永久代,并不是意味着,它区域里边的对象就是永久存活的,它里边的内容还是可以回收的,只不过回收效率和回收频率较低而已。在1.8 中可以理解为堆里边包含了方法区
(2)
堆区
此块区域里边放的是具体对象的实例,也是进行GC的最重要的一块区域。在堆里边又将有了更详细的划分。
(3)在堆里边又将堆里边细化为: 新生代(Eden区、S0区、S1区)、老年代、永久代。给大家插入一张图描述:
(4)对于新生代:对用户线程影响比较小,对象的存活时间很短,被回收的概率很大,回收的效率也就越高
.
(5)对于老年代:对用户线程影响比较大,对象的存活时间长,效率更低,通常是新生代的10倍左右
。
3.如何判断对象已经死
就是如何判断一个对象是垃圾,要是没有引用指向他就说明是垃圾,判断是引入了两个算法:引用计数算法、可达性分析算法
(1)引用计数算法:
1)含义:
有新的引用指向该对象的时候,就给计数器 + 1,要是少了该对象的应用就给计数器 - 1.
2)存在问题:
要是有两个引用相互调用
的时候,计数器就会不停的进行 + 1操作,造成循环
。还是用一张图表示:
(2)可达性分析法:
就是将刚开始的一个对象记为 GC Roots ,依次往下边找,
要是有对象的引用和GC roots有一个完整的链状结构
,则这些连在一块的对象就是有用的
。要是发现有和GC Roots之间断开的所有的对象就是无用的
,就是要内进行回收的。还是用一张图来表示:
4.垃圾回收算法
垃圾回收算法是用来回收垃圾的一种手法,具体分为以下四种:
(1)标记清除算法:(老年代回收算法)
1)标记阶段:先要
遍历
每一个区域,将内存中的无用对象(垃圾)标记出来
。
2)清除阶段:遍历
刚标记好的垃圾,进行删除。
3)存在的问题:要牵扯两次遍历,效率太低
。 存在内存碎片
问题。
结合上边的三点,给大家用图来表示:
(2)复制算法:(新生代回收算法)
1)将一个大的内存空间,
划分为两个一样大的两个小的内存空间
。一个里边是空白的,用来辅助。
2)将要回收的内存里边的存活对象,复制到空白的内存中
。
3)将原来的内存里边的所以内容进行清空。
4)缺点:内存利用率太小
。
5)优点:不存在内存碎片化,性能更高。
因为这是新生代的,赋值存活的效率高
6)用图来表示过程:
(3)标记整理算法:(老年代回收算法)
1)在标记清除的基础上,对要回收的垃圾进行整理,将他们放在一块,不在是以前的凌乱分布。
2)优点:防止了内存碎片化。
3)用图来表示:
(4)分代收集算法:
为了解决
上边的复制算法带来的内存利用率低的问题
,将内存区域进行了进一步的划分
。
1)将本来的内存区域划分成:
伊甸区、S0区、S1区
,就是这张图:
2)对于新生代来说,就是复制算法的升级,对于老年代来说,还是采用标记清除法或者标记整理法来进行。
3)将内存区域划分为 伊甸区,和两块较小的幸存区(就是上边的 S1 、S2),初始三者比例是8 : 1 : 1。
4)每次回收的时候,只有一个S区是空的,也就是回收的是伊甸区和S0区中的垃圾。
5)每一次有新的对象加入时,只给伊甸区加入
。然后将剩余的两个区域的内容全部清空。
6)在下一次对象来的时候,必定有一块S区域是空的,循环前边的过程即可。
7)优点:在复制算法的基础上,将原来 50% 的空白区域编程了 10% ,提高了内存利用率.
8)用图来表示:
5.垃圾收集器
(1)前置概念:
1)吞吐量:
用户代码运行时间 / (用户代码运行时间 + 垃圾回收运行时间 + 停顿)。
举例:用户代码执行时间是 90 秒,垃圾回收用了 9 秒,还有 1 秒是其中的停顿时间,总计100 秒。此时的吞吐量就是 90% 。
2)吞吐量优先:用户线程停顿的总时间短
,单个时间长也无所谓。
3)用户体验优先:单个线程停顿时间短
,及时线程总的时间长也无所谓。
4)停顿时间是需要用吞吐量去换取的
。吞吐量下降,会使单个线程停顿时间变短。
5)串行:执行垃圾回收线程时,用户线程停止
。
6)并行:执行垃圾回收线程时,用户线程和垃圾回收线程同时执行。
(2)垃圾收集器图:
相互用线连在一块的才能进行搭配使用。
有以下垃圾收集器:
Serial
收集器
Serial
收集器(新生代收集器,串行GC):用于新生代、复制算法、串行GC
。
ParNew
收集器
ParNew收集器(新生代收集器,并行GC):用于
新生代、复制算法
。是 Serial 的多线程版本、能搭配 CMS 收集器让用户体验优先
的应用先使用。
Parallel Scavenge
收集器
Parallel Scavenge 收集器(新生代收集器,并行GC):
新生代、复制算法
、搭配parallel old 让吞吐量优先
serial Old
收集器
serial Old 收集器(老年代收集器,串行GC):
单线程、标记整理算法。
Parallel Old
收集器
Parallel Old 收集器(老年代收集器,并行GC):
标记整理算法,吞吐量优先
CMS
收集器
1)CMS 收集器(老年代收集器,并行GC):
2)用户体验优先的收集器,并发收集、低停顿
:
3)执行流程:
1.并发收集,低停顿
2.用的是标记 - 清除算法
3.分为 a,b,c,d4 个阶段:
a:初始标记
: 先找出GC Roots 下边能关联到的所有对象
,并标记出来。
b:并发标记
:就是对GC Roots 进行 追踪。
c:重新标记
:因为在标记的过程中,程序要是还运行的 话就会,导致有新的垃圾或者对象产生。这个阶段就是在将型产生的垃圾进行标记,时间较长。
d:并发清除:
上边已经标记好了,下边进行清除。
4)缺点:用户体验优先,所以吞吐量会下降
。浮动垃圾
问题和`内存碎片化
G1
收集器
(1)叫全堆收集器,对堆上边的所有内容进行收集
(2)将堆划分成很多的 region 块
,然后并行对这些块进行划分。
(3)里边还是总的分为Eden 区,S区,T区
。
(4)整体看是标记整理算法,局部看是复制算法。
(5)回收年轻代时用的是复制算法
,老年代用的是标记整理算法
。
(6)老年代也是四个阶段:初始标记:与CMS不同,可以进行并发执行。 并发标记:与CMS不同,先回收存活率低的对象。 最终标记: 用的是SATB算法。 筛选标记:能对存活率低的对象,进行并发的回收。
下边是对以上的垃圾收集器进行总结,以思维导图的方式整理出来:
6.内存分配和回收策略
(1)对象先给Eden区中放
1)就是
新加入进来的对象,先给伊甸区中放
。要是能放下,就直接放。
2)要是放不下
,就进行minor gc
。具体就是采用复制算法,对伊甸区的存活对象和其中一个S区的存活对象,进行复制,在清除两个空的区域。
(2)大的对象直接进老年代
1)将
大的对象直接放在老年代中
。
2)但是对于生存时间很短的大对象
,对程序的性能影响非常大
3)因为在大对象进入老年代的时候,就需要申请连续的大空间,需要空间就可能会提前触发major gc
,速度比minor gc 慢10倍以上。
(3)将长期存活的对象直接进入老年代
1)长期存活的对象指的是在
新生代里边长期存活的对象
。
2)判断存活:在新生代里边,要是进行一次minor gc
就给里边存活的对象的年龄 + 1
。要是一个对象的年龄大于 15岁
,则认为是长期存活
的,这里的15 也可以自己设置,默认是15.
(4)动态对象年龄判断
要是在存活对象中,一个年龄的所有对象的大小之和 >= S / 2区域,就把这个年龄段和这个年龄段以上的所有对象放在老年代中
(5)空间分配担保
1)在进行新生代GC 的时候是
从 90%的Eden区和S区
(下边我们叫大区域)中,找存活对象给剩余的 10% S区里边放
的。
2)要是大区域的存活对象 < S区域
,则直接给里边放
。
3)要是大区域存活对象 > S区域
,向老年代申请空间担保
。
4)要是老年带的空间能够放下S区放不下的对象
,就表示担保成功,进行minor gc 。
5)要是老年代的区域放不下
,就看他以前担保成功的几率高(信誉度高)。信誉度高,就能进行 minor gc
6)要是信誉度不够,就不能进行minor
。此时会进行全堆进行gc(full gc)
.
下边是关于内存分配和回收策略的总结,还是以思维导图的方式呈现出来。