首先咱们必须名明白为什么需要学习以及深入研究垃圾回收。
其次咱们得学会合理使用各种垃圾回收器。
第一个问题为什么要学习,有如下几个原因
1.解决各种生产故障
2.写出高性能代码(和写代码没联系?您别搞笑!明白每个对象怎么回收,才能真正掌控程序。否则只是闭着眼睛堆积木而已,随时就倒了)
3.有成就感,向架构师更进一步
第二个问题
1.会合理使用垃圾回收器程序才能从单车变跑车
2.节省没必要的资源浪费(积少成多)
其实我刚学习JAVA的时候也知道有垃圾回收这个东西,但从来没看过。是工作了快两年了才开始想着系统学习一下,知耻而后勇。。。都是泪
前两年我的印象里垃圾回收就是标记对象用一次标记一次,每失去一次引用标记减少一次,当标记为0的时候对象回收。
但是尼玛现在的回收器没有一个是这么玩,坑爹的博客和某些从哪个角落来的边角料知识。
好了下面开始系统说
第一个问题怎么判断一个对象该被回收?
通过可达性分析算法
选取一个对象当作GC Root节点向下遍历获取所有的引用链。当触发垃圾回收的时候,引用链下面有节点无法抵达GC Root了说明这些对象可以回收。
那么好了第二个问题来了
该选择什么对象当作GC Root?
咱们前面是不是学习过JMM,里面有提到过一个东西叫做栈。
程序的执行都在栈里面建立引用与执行指令的排列,这就很清楚了。当一个对象和栈没有任何关联的时候是否意味着没人需要使用到了?
所以可以选择如下对象作为GC Root
1.虚拟机栈中栈帧中的引用对象
2.方法区中类静态属性引用对象
3.方法区中常量引用对象
4.Native栈中引用对象
到这咱明白了怎么判断一个对象死亡,咱们继续聊怎么回收它们的算法。有如下几种
1.标记清除(会造成内存碎片,导致大对象进不来)
2.标记整理(耗时较长)
3.拷贝复制(导致可用内存减少)
上面三种都有各自的弊端所以牛逼的大哥出现了,结合上面三位的基础出现了
4.分代收集算法
这个算法就牛逼了,通过将内存分成各种块。使用上面不同的三种回收算法实现各种垃圾回收期。可以达到一种比较均衡理想的运行状态。
咱们知道了对象啥时候可回收用什么方式回收。但是又有一个问题来了
我不可能一边回收一边产生垃圾吧。所以需要标记一个位置让线程都停下来(当然现在是有不用stop the world 的方式回收的后面说)
所以大佬们想到了一个安全点,也就是线程都跑到这个点就停下来进行垃圾回收
回收点的定义可以有如下
1.方法调用
2.循环跳转
3.异常跳转
4.创建对象的时候
这个怎么计算出来?
通过栈与计数器里面存放的引用信息以及指令执行位置,并且类加载的时候咱们是知道偏移量的
就可以准确获取安全点,安全点多了会造成记录信息过多而造成不要的消耗,太少会造成垃圾回收等待时间过长。
这里还有一个安全区的概念也就是线程处于sleep或者blocked的状态,这尼玛就坑爹了永远不会进入安全点了。
垃圾回收也不可能一直等待吧
所以会直接不管它而进行GC,但是该线程突然需要运行则需要等待GC结束
好了真个垃圾回收的生命周期基本就说完了
总结一下就是下面几个步骤
1.类加载记录引用偏移量
2.运行的时候通过栈与计数器获取计算安全点,并记录GC ROOT
3.内存不足触发GC的时候通过所以线程抵达安全点或者安全区,遍历GC Root中失去引用的对象进行回收。(并不是第一次失去引用就会被回收)
垃圾回收器我下面只是简单的说一说,以后有时间多补充实际生产中的使用方式。
先上几个链接
Oracle垃圾回收基础
G1垃圾回收器
今天先到这,明天继续~
首先列举一下到JAVA11总共有多少垃圾回收器
1.Serial
2.Serial Old
3.ParNew
4.Parallel Old
5.Parallel Scavenge
6.CMS
7.G1
8.ZGC
单线程采用复制算法回收新生代的内存,不适合web项目。
书上会说对于单核client的应用来说该回收器合适,其实该回收器早已不适用现在的设备。
单线程采用标记整理算法回收老年代,同样不适用web项目。
标记整理耗时长,处理慢。
不过也有其优点,那就是不会导致内存碎块话导致大对象无法放到内存中。
多线程采用复制算法回收新生代
多线程标记整理老年代回收配合Parallel Scavenge使用,可以提高吞吐量。
吞吐量是可以上来,但是并不意味着处理就快。嘿嘿~~
俗称吞吐量GC,同样是多线程复制算法的新生代回收器
但是它可以设置GC时长或者吞吐量大小来控制吞吐量
这个回收器挺有意思。上面的回收器都会STW,但是它虽然也会但是目标却是减少STW时间
分为四个阶段执行
1.初始标记
2.并发标记
3.重新标记
4.并发清除
13两个阶段依然需要停顿
2的过程是GC Root 遍历的过程,但是不会STW。所以会需要重新标记修正回收对象结果集
4就是执行垃圾回收,这里有一个很重要的参数CMSInitiatingOccupancyFraction用来设置CMS垃圾回收启动阈值
因为CMS垃圾回收的时候是不STW的所以老年代需要预留一定的内存空间给应用使用就是通过该阈值
设置过小,垃圾回收频繁
设置过大,导致浮动垃圾过多预留空间不够而产生Full GC
一般设置为80,现在1.7默认是92从1.6版本开始
然后由于使用标记清除的算法,会导致大对象无法进入内存触发Full GC
所以又提供了一个参数CMSFullGCsBeforeCompaction来设置多少次不带整理的Full GC之后进行一次整理
还有一个参数UseCMSCompactAtFullCollection就是当需要Full GC的时候先进行一次整理
如果想研究一下可以通过写一个程序使用jvisualvm.exe观察新生代老年代内存转移的过程。
以及触发Full GC的耗时和频率这样学习才比较正确。
为啥老年代Full GC慢有以下几个原因
1.需要回收方法区,设计到类卸载提交比较多
2.由于老年代存储的对象都会比较大或者生命周期长不适用复制算法,使用的整理算法速度较慢
3.老年代容量大
G1垃圾回收器的思路是将整个内存划分为多个区域,每个区域可以独立回收
思路有点类似于1.7的ConcurrentHashMap划分为多个区域,每个区域加一把锁。
因为没用过所以先写这么多,等我这几天再看看。
没用过。。。不写了
欢迎扫码加入知识星球继续讨论