您好,我是湘王,这是我的慕课手记,欢迎您来,欢迎您再来~
既然程序运行会产生大量的废弃物,也就是「垃圾」,那总不能一直堆着不管吧。现在就来粗浅地谈谈Java里面什么时候会触发GC以及有哪些GC。
通过之前的电商支付系统,可以知道系统运行创建的对象都是优先分配在JVM的年轻代中的,年轻代里面的对象越来越多快满的时候就会触发垃圾回收机制。
这就涉及到一个「可达性分析法」,也就是判定哪些对象可以被回收,哪些不能被回收:只要某个对象被局部变量引用,就说明该对象有一个GC Roots,此时就不能被回收了。
所谓GC Roots,在一般情况下指的是方法的局部变量和静态变量。只要对象被方法的局部变量、静态变量给引用了,它就不能被回收了,至少不能被马上回收。
Java对象有几种不同的引用类型:
1、强引用:一个变量显式地引用一个对象;
2、软引用:对一个匿名内部类或者对引用的引用,例如:
// 强引用
String str = new String(“abc”);
// 软引用
SoftReference<String> softref = new SoftReference<String>(str);
// 也是软引用
SoftReference<String> softref = new SoftReference<String>(new String(“abc”));
3、弱引用:只具有弱引用的对象拥有更短暂的生命周期,例如:
// 弱引用
WeakReference<String> weakref = new WeakReference<String>(new String(“abc”));
str = null;
4、虚引用:顾名思义,就是形同虚设,实际中很少使用,可以不管。
再来看看有哪些GC方法。
年轻代的内存被分为两部分,其中一块创建对象实例,供栈中的局部变量引用。当空间不足时,就可能触发年轻代的垃圾回收:
由于一瞬间回收掉大量垃圾对象时,可能会造成大量的空白内存区域,大小不一,非常凌乱,造成大量的内存浪费,因此不能直接清除垃圾对象而保留存活对象。所以合理的做法就是复制(复制算法):
1、先保留存活对象;
2、再将存活对象转移(复制)到另一块空白内存中;
3、最后将原区域整体回收;
4、两块区域循环往复着使用。
这就是GC当中最最简单、最最基础的复制算法。
它的缺点是:空间使用效率只有50%。从图上可以明显看得出来,比较浪费,空间越大越浪费。
优化:将年轻代划分为三块:
1、1个Eden区:占80%年轻代空间;
2、2个Survivor区:S0、S1各占10%年轻代空间;
平常情况下可以使用的,就是Eden区和其中一块Survivor区(比如S0):
如果Eden区快满了,就会触发Minor GC:
1、Minor GC时,Eden中的存活对象会一次性转移到另一块空闲的Survivor区中区(比如S1);
2、Eden区被清空,然后被再次分配新对象;
3、如果Eden再满,再次触发Minor GC;
4、将Eden和S1中的存活对象转移到S0中去;
5、如此循环往复。
对象不可能永远年轻,总会慢慢变老。自然世界用光阴让人两鬓斑白,JVM则默默记住对象的「劫数」,并以此决定它的生死:
对象每在年轻代“躲过”一次GC而被转移到另一个Survivor时,对象“年龄”就会增加“一岁”(被标记一次),默认是被标记15次之后,就会被转移到老年代,可通过-XX:MaxTenuringThreshold参数设置。
当然计算机世界还有自己的规则——除了被标记,还有另一个规则可让对象进入老年代——如果当前Survivor区中,年龄相同的一批对象总大小 ≥ Survivor × 50%,那么这批对象及比它们年龄更大的对象,就都直接进入老年代。例如:如果年龄1 + 年龄2 + 年龄3 ≥ Survivor × 50%,那么大于等于年龄3的所有对象都进入老年代。
计算机的规则只有工程师可以干预:通过设置参数XX:PretenureSizeThreshold的值,可以直接将大对象放到老年代,根本就不经过年轻代。
工程师就是计算机的上帝!
感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~