前言
要想了解垃圾回收器首先要先了解 引用计数算法 和 可达性算法,还有一些 标记清除 和 标记整理还有 标记复制 ,最后还有个分代收集
了解这些之后才能看懂后面的!先标记一些没有被GC Root给引用对象 都会被标记当成垃圾回收
先来看看引用计数算法:
引用计数算法:
简单的说就是 判断对象是否存活
给new出来的对象设置一个计数器, 谁引用了该对象该对象的寿命加1 如果寿命为0就可以被垃圾回收
同时有的缺陷 比如A 引用了 B B的寿命加1 相反 B 引用了 A A 寿命加1
那么这就已经产生了循环 相互引用 一直都是1 时时不会被垃圾回收
导致可能会内存泄漏内存溢出等。
可达性算法: 目前都是用可达性算法判断对象是否存活
引用计数算法的缺陷就是互相调用 但可达性算法可以 解决循环调用。
标记清除 :先标记一些没有被GC Root给引用对象 都会被标记当成垃圾回收
优点:速度快,因为直接清除掉了无脑的清
缺点 :会产生内存碎片 , 因为清除掉了很乱 然后再来个对象可能这些小的内存都不够用。
内存碎片是什么?
内存碎片就是 当内存空间分配了大量的内存 和 释放内存就会产生一些小小的内存 这些就是些碎片
如果还有对象要放进去那么就可能会应内存碎片而内存溢出,内存泄漏
标记之后:
金色表示存活的对象 灰色表示没有被引用对象称为死对象
清除之后:
标记整理 :
标记之后 :
整理之后:
标记整理就是 先标记 没有被GC Roots引用的对象标记起来
然后一个个的整理 不会产生内存碎片 ,
优点:不会产生一些小小的内存碎片
缺点:就是要一个一个整理太耗时了或者效率不高
标记复制 :
大概流程:
标记复制 有两个部分 form 和 to (from存放一些存活的对象,to就是复制过来的对象)
当内存不足时就会将一些没有被GC Roots(也就是不是可达性的对象)所引用的对象标记
然后把存活的对象存入到to中 把标记的对象作为垃圾回收掉并且 from 和 to 交换位置 这时
from就变成了to了 to就变成了from了。
优点和缺点:
优点:速度快,不会产生内存碎片而内存溢出和内存泄漏
缺点:会造成双倍的内存,就是两个内存
分代收集:
当新生代内存不足时就会进行第一次Minor GC
把没有被GC roots (根对象 从第一个根对象找
比如 A 引用了 B B 引用了 C 那么就先去找A对象看看引用的对象是否被引用着) 引用的对象进行标记 当成垃圾回收
然后把存活的对象复制到幸存区( TO ) 中 然后 To 和 From 交换位置 并且存活的对象寿命加1
过了一段时间后 新生代内存又不足了 进行了第二次minor GC
把没有被引用的对象标记当成垃圾回收
把存活的对象 复制到 To 并且寿命加1 From 和 To 交换位置
如果新生代中的对象存活的时间达到了一定的岁数(比如是寿命是15 达到了晋级的要求)就会直接晋级到老年代中
如果 老年代的垃圾又满了 就又会进行一次Old GC 垃圾回收
又过了一段时间 新生代 、老年代、幸存区1,幸存区2内存都不足时就会进行 full GC 对整个堆内存做一次垃圾回收 从而加快运行的效率。
串行 ( Serial GC垃圾回收器 ):
串行是一个时代过了很久的一个垃圾回收器,串行被jdk1.3所支持
串行是一个单线程(一个人工作,一个一个回收线程的垃圾)
串行的工作流程:
新生代内存达到一定的阈值了就引发了垃圾回收( minor GC )把没有被GC Roots引用的对象做一个标记 之后就回收掉
然后复制到 幸存区当中 这时就会阻塞所有线程(STW) 之后一个垃圾回收线程 进行 垃圾回收 垃圾回收完之后 线程恢复正常运行
如果是老年代达到了一定的阈值就会引发 Full GC 把没有被引用的做一个标记
然后整理存存活对象 把死去的对象做一个垃圾回收这时是会STW暂停所有线程 等垃圾回收完了 线程就恢复正常运行
老年代采用的是标记整理或者是标记清除
吞吐量优先 (Parellel GC 垃圾回收器)并行回收: 会STW( stop the word)
jvm的参数:顾名思义
-XX:+UseParallelGC (新生代) -XX:+UseParallelOldGC (老年代) (这是个开关 开启任意一个就会自动的把另一个开启)
-XX:+UseAdaotiveSizePolicy (用来动态的管理堆内存中 的 新生代和老年代 还有 伊甸园(Eden) 幸存区 (survivor) )
-XX:+ParallelGCThreads=n ( 用来设置线程数量的)
-XX:+GCTimeRatio 吞吐量的计算公式 : 吞吐量 = 用户线程 / (用户线程 + 垃圾回收时间)
比如: 回收垃圾100分钟只有1分钟来回收垃圾
-XX:+MaxGcpauseMillis=ms (每次回收的时间 默认200ms)
吞吐量优先是一个多线程(多个人一起工作)
吞吐量优先是jdk 1.8 引入进来的 并且还是jdk8默认的垃圾回收器
吞吐量优先的基本流程:
首先内存不足时就会 引发垃圾回收 Minor GC
把一些没有被GC Roots 所引用的对象标记起来,然后就复制到 幸存区中
回收时会发生STW暂停所有线程等垃圾回收完毕再恢复运行
如果是老年代垃圾回收不足时就会引发Full GC 进行垃圾回收
突发STW暂停所有线程 等垃圾回收完毕恢复其他线程
响应时间优先 (CMS GC垃圾回收器)并发回收:
响应时间优先是一个多线程(多个人一起工作)
只要是并发 都不会stop the word(STW)暂停其他线程
响应时间优先的执行流程:
当老年代的内存不足时 会进入安全点 然后进行初始标记(会STW)暂停其他线程 ,
初始标记 完了之后就进行并发标记(因为是并发标记 用户线程和垃圾回收线程并发执行)不会干扰其他用户线程 过了这并发标记之后
就会进行重新标记(会STW)在做重新标记之前会把新生代的 垃圾做一次垃圾回收 免得全部都重新标记一下就效率就低了很多
最后做一个并发清理 不会干扰到其他用户线程 然后进入重置线程的候就会先清除掉之前标记的对象,然后再来次重置线程就一直重复一直重复
注意的是:如果老年代的回收垃圾不足时 做一个并发标记 但是 标记失败了那么就会退回掉串行(serial) 之后就采用full GC 来回收垃圾就会STW的时间可能会比较长,为什么?因为是多个线程。
G1 GC ( garbage first 垃圾回收器 )区域分代:每个区域都划分为相同大的区域 也是一个并发的一个垃圾回收器
分别为3个部分 young Collection -->> young Collection + Concurrent Mark -->> Mined
Collection 然后又回来 young Collection
可以看到这三个部分是循环的
G1 是jdk9默认的垃圾回收器 ,将堆内存划分为多个相同的大小的区域(region)
而这些区域包含新生代(伊甸园 Eden 幸存区 survivor)老年代
young Collection (对新生代的一个垃圾回收):
首先新生代的内存达到了一定的阈值,引发了 minor GC(新生代) 回收垃圾
把没有被GC Roots关联的对象做个标记
然后把存活的对象复制到幸存区中 把标记的对象回收掉
如果又内存不足了,新生代就又会第二次的 minor GC(新生代) 回收垃圾
把没有被引用(GC Roots)的对象做一个标记
注意:然后把存活的对象放到另一个幸存区中 并且寿命加1 (因为G1 把堆内存分为多个相同
大小的区域)然后回收掉标记的对象,如果新创建出来的对象还是放到新生代中
如果寿命达到要求就会晋级到老年代中 。
young Collection + ComCurrent Mark (对新生代的一个垃圾回收并且并发标记)
-XX:InitialtingHeapOccupancyPercent=percent(默认为45)你应该知道
该参数是当内存达到一定的阈值比如说默认是45,到了45就会直接进行full gc 会释放所有堆的内存
内存不足时 会young Collection 标记伊甸园 (Eden)中没有被根对象所引用的会被当成垃圾回收,
然后把存活的复制到幸存区 如果幸存区内存不足时也是一样的 会标记 然后 复制到另一个幸存区中 。
young Collection 跨代引用
从GR Root 对象中找 对象 判断老年代中的对象是否可达性,那不是这种要扫描整个老年代的内存中的对象是否是可达性的?
解决方案:
使用 Remembered set 就是用来避免扫描全局的对象。
Remembered set 就是有哪些对象被外部引用了的
然后垃圾回收就会先去remembered set 是否被引用了 不会扫描全局堆内存 大大的提高了效率
Mixed Collection 混合回收 :
会对新生代(伊甸园 和 幸存区) 和老年代进行混合回收(就是对堆内存全部都回收一次)
有两个角色:拷贝存活(拷贝存活的对象)、最终标记(标记最大的对象)
回收老年代的内存时会将最大的对象回收掉,就是回收时会从最大对象开始回收,然后复制到新的老年代中 意思就是拷贝。
十分注意的一件事 : 我只是写写保存下来 ,只是个初学者 ,
都是随便乱写的哈,别喷哈 如果有什么写的不对的欢迎评论哈 如果有什么写的不对的欢迎到评论区留言 谢谢!