GC【垃圾回收】
位置: heap + metaspace
什么是垃圾:没有引用指向的对象
GC需了解:
1.哪些对象要被回收 which
2.什么时候被回收 when
3.采用什么方式回收 how
1.哪些对象要被回收 which
1.回收算法
1.引用计数法【了解】
给对象添加一引用计数器,被引用一次计数器值就加 1;当引用失效时,计数器值就减 1;计数器为 0 时,对象就是不可能再被使用的,简单高效,缺点是无法解决对象之间相互循环引用的问题
2.根节点可达性分析
通过一系列的称为 "GC Roots" 的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有
任何引用链相连时,则证明此对象是不可用的。此算法解决了上述循环引用的问题。
什么是GCroot?
1.栈
2.方法区
3.native stack
2.when +how
采用什么方式回收?
1.理论:=》 gc算法
2.落地:=》垃圾收集器
2.gc算法:
1.标记清除: mark-sweep
最基础的收集算法是“标记-清除”(Mark-Sweep)算法,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
它的主要不足有两个:
效率问题,标记和清除两个过程的效率都不高;
空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
标记—清除算法的执行过程如下图。
2.标记整理:
复制算法在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都 100% 存活的极端情况,所以在老年代一般不能直接选用这种算法。
根据老年代的特点,有人提出了另外一种“标记-整理”(Mark-Compact)算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存,“标记-整理”算法的示意图如下:
3.复制:
为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为了原来的一半。复制算法的执行过程如下图:
4.分代收集算法:
当前商业虚拟机的垃圾收集都采用“分代收集”(Generational Collection)算法,根据对象存活周期的不同将内存划分为几块并采用不用的垃圾收集算法。他不是新的算法,只是前三种算法的融合。
一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
3.下图是new一个对象之后,这个对象的内存分配流程:
1、对象优先在Eden分配
大多数情况下,对象在新生代 Eden 区中分配。当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC。当然如果有些小对象也可能直接在栈上分配。
2、Minor GC 和 Full GC 有什么不一样吗?
新生代 GC(Minor GC):指发生在新生代的垃圾收集动作,因为 Java 对象大多都具备朝生夕灭的特性,所以 Minor GC 非常频繁,一般回收速度也比较快。
老年代 GC(Major GC/Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC。Major GC 的速度一般会比 Minor GC 慢 10 倍以上。一旦出现fullGC就必然会出现STW,stop the world。
3、大对象直接进入老年代
所谓的大对象是指,需要大量连续内存空间的 Java 对象,最典型的大对象就是那种很长的字符串以及数组( byte[] 数组就是典型的大对象)。大对象对虚拟机的内存分配来说就是一个坏消息(特别是短命大对象,写程序的时候应当避免),经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来“安置”它们。
4、长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对象年龄(Age)计数器。这个年龄就记在对象头中。如果对象在 Eden 出生并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并且对象年龄设为 1 。对象在 Survivor 区中每“熬过”一次 Minor GC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就将会被晋升到老年代中。
5、动态对象年龄判定
为了能更好地适应不同程序的内存状况,无须等到 MaxTenuringThreshold 中要求的年龄,同年对象达到 Survivor 空间的一半后,他们以及年龄大于他们的对象都将直接进入老年代。
6、空间分配担保
在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代,所有对象总空间,如果这个条件成立,那么 Minor GC 可以确保是安全的。
只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行 Minor GC ,否则将进行 Full GC 。
4.垃圾回收器的搭配:
夭折:90%
老不死的:old 0-15 15 这个对象是足够的老
不灭的:class 、pool
年轻代参数:
-Xmnsize =》 年轻代
XX:SurvivorRatio=ratio =》 8:1:1
垃圾收集器 :
1.串行垃圾收集器
2.并行垃圾收集器
3.并发垃圾收集器
4.G1垃圾收集器【比较复杂】
1.串行垃圾收集器
1.一个线程处理垃圾回收
2.stw
app=>gc=>app
2.并行垃圾收集器
1.多个线程处理垃圾回收
2.比 串行垃圾收集器 stw时间短
3.并发垃圾收集器
你的程序线程 和 gc线程 并发执行
没有stw
分代算法:
年轻代 老年代
1.年轻代
1.年轻代:串行
-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseSerialGC
DefNew 年轻代 串行垃圾回收器
Tenured 老年代 串行垃圾回收器打印
2.年轻代:ParNew(并行)
-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseParNewGC
ParNew 年轻代 并行垃圾回收器
Tenured 老年代 串行垃圾回收器打印
3.年轻代:Parallel Scavenge(并发) jinfo
-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseParallelGC
PSYoungGen 年轻代 并发垃圾回收器
ParOldGen 老年代 并行垃圾回收器
注意:
可以从单单 一个方面入手 eg:年轻代 老年代的gc 就确定了
2.老年代
1.老年代:串行 【用不了】
-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseSerialOldGC【参数没有找到】
2.老年代:Parallel old(并行)
-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseParallelOldGC
PSYoungGen 年轻代 并发垃圾回收器
ParOldGen 老年代 串行垃圾回收器打印
3.CMS(并发标记清除)
1.目的
gc =》 停顿的时间比较短
-Xms5m -Xmx5m -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC
ParNew 年轻代 并发垃圾回收器
CMS 老年代 cms垃圾回收器
上述的垃圾收集器并非是可以随意搭配的,有搭配要求,详细搭配如下: