Java 三 JVM 垃圾回收初解
什么情况下JVM内存中的一个对象会被垃圾回收
JVM中使用了一种可达性分析算法来判定哪些对象是可以被回收的,哪些对象是不可以回收的。这个算法就是说对每个对象,都分析一下有谁在引用他,然后一层一层往上去判断,看是否有一个 GC Roots。有GC Roots 就不能被回收了。
Java 里有不同的引用类型。分别是 强引用,软引用,弱引用和虚拟引用。
强引用

只用是强引用的类型,那么垃圾回收的时候绝对不会去回收这个对象的。
软引用

正常情况下垃圾回收是不会回收软引用对象的,订单数如果进行垃圾回收后,发现内存空间还是不够存放新的对象,内存快;溢出了,此时就会把这些软引用对象给回收掉。
弱引用

如果发生垃圾回收,就会把这个对象回收掉。
JVM中有哪些垃圾回收算法,各自的优劣
复制算法
把新生代内存划分为两块内存区域,然后只使用其中一块内存待那块内存快满的时候,就把里面的存活对象一次性转移到另外一个内存区域,保证没有内存碎片,接着一次性回收原来那块内存区域的垃圾对象,再次空出来一块内存区域。两块内存区域就这么重复循环使用。

复制算法的缺点
只有一半的内存可以使用,对内存的使用效率太低了。
复制算法的优化:Eden 区和 Survivor 区
把新生代内存区域划分为三块:1个Eden区,2个Survivor区,其中 Eden 区占80%内存空间,每一块Survior区各占10%内存空间。平时使用Eden区和其中一块Survivor区,如果Eden区快满了,此时就会触发垃圾回收。触发 Minor GC ,就会把 Eden 区和放着上一次 Minor GC 后存活对象的 Survivor 区的存活对象,转移到另外一块 Survivor 区去。

年轻代和老年代分别适合什么样的垃圾回收算法
躲过15次GC之后进入老年代
对象每次在新生代里躲过一次GC被转移到一块Survivor区域中,此时他的年龄会增长一岁。默认的设置下,当对象的年龄达到15岁的时候,也就是躲过15次GC的时候,他就会移到老年代里去。
JVM参数 "-XX:MaxTenuringThreshold" 来设置,默认是15岁。
动态对象年龄判断
假如当前放对象的Survivor区域里,一批对象的总大小大于这块Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象就可以直接进入老年代了。
实际这个规则运行的时候是如下的逻辑:年龄1+年龄2+年龄n的多个年龄对象总和超过了Survivor区域的50%,此时就会把年龄n以上的对象都放入老年代。
大对象直接进入老年代
JVM参数 "-XX:PretenureSizeThreshold",单位为字节数,比如 "1048576",就是1MB。
若创建一个大于这个大小的对象,此时就直接把这个对象放到老年代去,不经过新生代。
Minor GC 后的对象太多无法放入Survivor区
若Minor GC 之后发现剩余的存活对象太多了,没法放入另外一块 Survivor区。这时就必须把这些对象直接转移到老年代去。


老年代空间分配担保规则
在执行任何一次 Minor GC 之前,JVM会先检查一下老年代可用的内存空间,是否大于新生代所有对象的总大小。
1.若发现老年代的可用内存大小大于新生代所有对象,正常对新生代发起 Minor GC。
2.若发现老年代的可用内存小于新生代的全部对象的大小,就查看 "-XX:-HandlePromotionFailure" 这个参数是否设置,如果进行了设置,则进行下一步判断,查看老年代的内存大小,是否大于之前每一次 Minor GC 后进入老年代的对象的平均大小。如果是,则冒点风险尝试 Minor GC。这里有几种可能:
第一种可能:Minor GC后,剩余的存活对象的大小,小于Survivor区的大小,此时,存活对象进入 Survivor 区域即可。
第二种可能:Minor GC后,剩余的存活对象的大小,大于Survivor区的大小,但是小于老年代可用内存大小,此时就直接进入老年代
第三种可能:Minor GC后,剩余的存活对象的大小,大于Survivor区的大小,也大于老年代可用内存大小,此时,老年代都放不下这些存活对象了,就会发生 "Handle Promotion Failure" 的情况,这个时候就会触发一次 "Full GC"。
Full GC 就是对老年代进行垃圾回收,同时也一般会对新生代进行垃圾回收。如果 Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会导致 "OOM" 内存溢出。
老年代垃圾回收算法
老年代触发垃圾回收的时机,一般就是两个:
1.在Minor GC 之前,检查发现很可能Minor GC 之后要进入老年代的对象太多了,老年代放不下,此时需要提前触发 Full GC 然后再带着进行 Minor GC。
2.在Minor GC 之后,发现剩余对象太多,老年代放不下,需要触发 Full GC。
老年代采用的是标记整理算法,该算法首先标记出老年代当前存活的对象,接着让这些存活对象在内存里进行移动,把存活的对象尽量挪动到一边去,让存活对象紧凑的靠在一起,避免垃圾回收过后出现过多的内存碎片,最后再一次性把垃圾对象都回收掉。
老年代的垃圾回收算法的速度至少比新生代的垃圾回收算法的速度慢10倍。如果系统频繁出现老年代的Full GC 垃圾回收,会导致系统性能被严重影响,出现频繁卡顿的情况。
JVM中都有哪些常见的垃圾回收器
Serial 和 Serial Old 垃圾回收器:分别用来回收新生代和老年代的垃圾对象
工作原理就是单线程运行,垃圾回收的时候会停止我们自己写的系统的其他工作线程,让我们系统之家卡死不动,然后让他们垃圾回收,这个现在一般写后台Java系统几乎不用。
ParNew和CMS垃圾回收器
ParNew现在一般都是新生代的垃圾回收器,CMS是用在老年代的垃圾回收器,他们都是多线程并发的机制,性能更好,现在一般是线上生产系统的标配组合。
G1垃圾回收器
统一收集新生代和老年代,采用了更加优秀的算法和设计机制。
JVM 的痛点 " Stop the World"
JVM 垃圾回收的时候会直接停止我们写的 Java系统的所有工作线程,让我们写的代码不再运行。
我们平时常用的新生代垃圾回收器是 ParNew,他对服务器一般都是做了多核CPU优化,他是支持多线程垃圾回收的,可以大幅度提升回收的性能,缩短回收的时间。

浙公网安备 33010602011771号