垃圾回收 garbage collection GC
首先垃圾回收比java出现之前还要早。
现在考虑的是jvm里面的垃圾回收,首先jvm里的本地方法栈(存有与native方法相关的)、虚拟机栈(里面有对象的引用等)、程序计数器(记录了 当前线程正在运行的字节码的行号)随线程创建而创建,由于程序计数器不会出现内存溢出,然后另外两个栈起始大小会被虚拟机设定,线程完毕后这三也随之毁灭。
所以,多个线程共享的方法区(已被加载的类的二进制文件、静态变量、常量(存在常量池内)等)、java堆(对象的实例、等)里面的内容被多个线程共享使用,什么时候会被回收,这个就是个问题,那怎么被回收?等等好多问题。
什么时候会被回收?
jvm根据可达性分析算法判断这个是否需要回收。
可达性分析算法其实是判断当前这块是否跟一个叫做gc roots有关联,如果能关联到,就表明还在使用,不然就认为应该回收。
选择哪些作为gc roots也是问题?
由于已经知道本地方法栈和虚拟机栈是随着线程毁灭的,所以,如果当前判断的这个对象如果跟某个线程的本地方法栈和虚拟机栈内的引用的对象有关联,说明这个线程还没完,说明正在使用。那么gc roots应该虚拟机栈引用的对象、本地方法栈jni引用的对象。
然后在方法区内有静态属性和常量,如果一个对象跟静态属性引用的对象有关联或者跟常量引用的对象有关联,说明这个对象也可能还是活的。
因此,gc roots包含:
- 虚拟机栈引用的对象
- 本地方法栈jni引用的对象
- 方法区的静态属性引用的对象
- 方法区的常量引用的对象
还一个是引用计数法(jvm不用反正)。就是有个被引用的就+1.等于0表示不会再用了。
其中【引用】用于存储对象的地址,可以分类为 强引用、软引用、弱引用、虚引用。
强度级别弱引用以下 每次触发gc时就回收。软引用 是将发生内存溢出时的gc会回收。
怎么回收的?
回收算法
-
标记-清除:线标记哪些要删除,然后再一起清除。耗时并且造成的碎片过多。
-
复制:将可用内存按大小分成两部分,每次用一边,满了就把目前用的区域内还活着的复制一份到另一边,然后再清除。
新生代 使用的就是复制算法,但是所分大小不是简单的两半。分成了Eden:Survivor1:Survivor2=8:1:1 如果活下来的数量比10%多,即一个survivor放不下,就要用到老年代里(一般来说整个老年代比整个新生代要大)。把那些活着的扔到老年代里去。 -
标记-整理:是标记清除的升级版,就是标记完后所有活着的对象向同一边移动得出一个活着对象的区域,然后把其他的删了。老年代一般就是用这个回收算法。当然有些也用标记清除。
垃圾回收器
serial回收器(新生代)
单线程收集器 stop the world暂停用户线程。Serial复制+Serial Old标记整理。
parNew(新生代)
serial的多条线程并行执行的版本。也是stop the world。parNew复制+Serial Old标记整理。cms收集器合作的默认的新生代收集器是parNew。
parallel Scavenge(新生代)
多条线程并行的,复制算法,跟parNew类似。parrallel S+parallel Old 。通过调节设置最大gc停顿时间或者吞吐量大小来控制吞吐量大小,即运行代码时间/(运行代码时间+gc时间)。吞吐量默认是99%。
Serial Old(老年代)、parallel Old(老年代)。
cms(老年代)
标记-清除
- 标记gc roots 直接关联的对象 stop the world
- 并发标记 就是进行可达性分析
- 重新标记 修正并发标记期间的变动的那一部分 stop the world
- 清理 并发执行
达到触发比就会执行。默认92%。同时可以通过设置参数开启清理后的碎片整理功能。默认开启。
g1
并行并发,分代收集,局部复制,整体标记整理,维护一个优先列表,优先回收价值最大的区域。生成或者改变一个引用时,会用remember set记录这个引用是否在不同region。后面进行回收的时候,gc roots 范围会加入remember set里的范围。
- 初始标记 stop the world
- 并发标记
- 最终标记 利用 remember set log 汇总到remember set。stop the world
- 筛选清除 挑出最有价值的来回收。比如成本、时间、回收后价值等综合考量最有价值。可以并发,但是还是stop the world 策略。
举例子:
一个创建对象触发新生代垃圾回收的过程
serial和serial old收集器,老年代10m,新生代10m 8:1:1,要依次生成三个2m的对象、一个4m的对象,在4m对象放进去的时候,jvm发现对应的新生代(8+1)放不下对应的4m对象了,触发minor gc。然后就是把这里的6m的存活对象放到 其中一个survivor去,发现还是放不下,咋办,就直接把这些存活的放到老年代去。然后,4m对象创建在了新生代的eden区。
但是如果设置了大对象参数,就是会在给对象分配内存时判断当前的对象如果大于这个数,那么它就会直接在老年代内创建。不会触发minor gc。
哪些对象会出现在老年代内?
-
超过设置的大对象参数的大小的那些对象(大数组)会在老年代内创建。(大对象控制)
-
对象年龄随着经历的minor gc 次数+1,其年龄当大于一定的阈值(参数可设置)时,它会在minor gc时,哪怕此时的survivor能够发下,由于年龄大了,就放到老年代了(年龄控制)。
-
当survior里面占有整个空间一半以上的是同一个年龄的对象,就把大于等于这个年纪的所有对象放到老年代里去(年龄判定)。
-
还有就是那些触发minor gc时,活着的但是超过survior整个大小的对象(分配担保机制)。
full gc 啥时候会触发?
- minor gc触发时,如果老年代也不够存放新生代过来的东西时,改为full gc。
- 任何要将对象放到老年代的过程 ,只要老年代要不够了 ,触发full gc。(上面也适用这个结论)
网友评论