1. 什么是垃圾
new
出来 最后不用了并且没有被回收的那块内存 就是垃圾;
C、C++让用户自己回收,java、python、js等带有垃圾收集器,Golang也有(仍STW);
C: malloc free
C++:new delete
new
出来内存不及时回收就会导致内存泄漏,泄露多了就会容易造成内存溢出;
不用管回收,又没有垃圾回收器帮我回收,没有严重停顿——只有 Rust;
把钱放在一边,不功利性地学习,钱就来了
垃圾:一个对象没有引用指向它,那它就是垃圾;
2. 检测/识别垃圾
引用计数法(Reference Count)
但 RC 解决不了循环引用的问题:
根可达算法(Root Searching)
对于对象树这种处理得尤为巧妙,从根开始找,寻得到的都是有用的,寻不到的都是垃圾,不管你循环引用还是啥;
❗哪些是根对象?
大厂常问
3. 垃圾回收算法
标记清除法(Mark-Sweep)
缺点:容易 碎片化,效率偏低;
拷贝法(Copying)
内存一分为二,下半部分不给new,需要垃圾回收的时候,把上半部分活着的对象复制到下半部分,然后把上半部分全部清理;
没有碎片;
缺点:浪费空间;
当存活对象很多时,copying就要复制一大堆对象,效率降低;所以比较适合存活对象少,垃圾多的场景;
标记压缩法(Mark-Compact)
边释放边整理(把有用的挪到一起)
没有碎片;
缺点:效率低;
当垃圾很多时,MC 算法就要进行很多次 清理-移动操作,效率降低,所以它比较适合 存活对象多、垃圾少的场景
❗JVM内存分代模型(分代垃圾回收算法)
JDK 从1.0到14.0 所用到的十种垃圾回收器;
JVM 内存模型归 GC 来管;
堆内存逻辑分区:
年轻代:常常大量产生、大量回收对象;所以很多垃圾,就把幸存者 复制 到survivor1 区,然后把 eden 全部清空;
为什么年轻代用 copying 算法?
就是因其在 存活对象少、垃圾多时 回收效率较高,但考虑到 copying算法 1:1的内存划分代价太大,所以新生代内存被划分了 大块eden 和 两个小块 survivor ;;每次使用 eden 和 其中一块 survivor;(HotSpot虚拟机默认其比例为 8:1:1 )
为什么要设两个 survivor 区?
如果只有一个 survivor 区,当第一次把 eden 的幸存者复制到 survivor 时,假设有四个对象,在下一次 GC 时,怎么执行copying 算法把 survivor 里面的垃圾干掉,所以就需要另一个 survivor 区,把 survivor 和 eden 的幸存者复制过去,清空它门;然后交换两个 survivor区,循环使用;
年轻代循环:
eden中产生对象,把幸存者复制到survivor1区,清空eden区;
while( true )
{
eden产生对象,把幸存者和survivor1区幸存者复制到survivor2区,清空eden和survivor1区;
if( age>=15 || survivor2区==full )
{
将该幸存者复制到老年代;
}
此时survivor1区空,survivor1区和survivor2区角色交换(即1区变2区,2区变1区);
所有幸存者年龄+1;
}
老年代因为幸存者多、垃圾少,所以更适合 MC 标记压缩算法;
年轻代效率更改,更频繁;
学东西,先学脉络,再学细节!
总结:
调优最先知道的就是 用的哪种垃圾回收器;
jdk > 1.8
以上的 建议用 G1
;1.8
默认Parallel Scavenge & Parallel Old
Serial
A stop-the-world(STW),copying collector which uses a single GC thread;
一堆小朋友(线程)在家里(堆)玩,家长(回收器)进来,说你们站一边去(STW),然后她就打扫屋里的垃圾,打扫完之后,告诉小朋友,好了你们可以继续玩了;
Parallel Scavenge
A stop-the-world,copying collector which uses multiple GC threads;
之前屋子面积(内存)小,一个人就够了,现在面积大了,一个人不够用了,需要多派几个人打扫;
垃圾回收器随着内存增大而演进;
Serial Old
采用标志收集算法的 serial 而已,针对老年代;
Parallel Old
采用标志收集算法的 Parallel 而已,针对老年代;
ParNew
PN≈PS
有点增强使之能配合 CMS
❗STW缺陷
STW 时期长的有一天以上的,主要服务器不能重启,很麻烦;
CMS
concurrent mark sweep
a mostly concurrent,low-pause collector;
分四个阶段
- 初始标记:找到根节点 GC roots,需要STW;
- 并发标记:进行GC Roots Tracing;
- 重复标记:统计每个对象存活信息,进行修正,需要STW;
- 并发清除:用标记清理算法清理;
优点:
- 大部分时间可与用户线程并发工作
- 低停顿
缺点:
- 产生内存碎片;此时,若来一个大对象,没空间它就会用 Serial Old 进行清理(即一个人打扫大屋子);为啥不用 PO ?只有开发者知道;
并发标记算法:
三色标记
发生错标的情况:
D 其实不是垃圾,但会当成垃圾处理掉,因为它是白色
白色的 表示 没标到的 都是垃圾;
为什么A指向D后,不把D变色,因为A已经是黑色,它不会检查自己的孩子了;
ems 阶段三 重新标记就会从头扫一遍,但由于有缓存,没有初始扫描时间长,用来修正;