上面我们们使用软引用我们发现在内存不足时,会把软引用对应的Byte数组对象,进行一个释放,但是我们发现遍历lIst集合的时候一些软引用的对象已经是null了,这些没必要在把它们保存到List集合中了有效的只有最后一个,遇到这种情况呢,我们希望把软引用也做一个清理 从List集合中清除掉,软引用本身也是占用内存的,只是占用比较少这种null的这种没必要保存了
怎么实现清理无用的软引用呢?需要配合一个引用队列来实现软引用的清理
queue.pool获取到放入队列的软引用对象
发现现在已经移出另外无用的软引用,List只剩下一个byte数组了
弱引用:List集合不直接引用byte数组对象,而是引用WeakReference然后再通过WeakReference 间接应用byte数组对象
这样呢当内存不足时会把把WeakReference所引用的byte数组所占用的内存释放掉 ,从而避免强引用导致的内存溢出问题
前三次没有问题,第四次触发了一次垃圾回收,但是没有回收掉前面的对象,第五次又触发一次垃圾回收,把第四个回收为null了,第五个才能放进去
修改多一次循环,第六次做一个垃圾回收,把第五个对象 变为null,第六个才能放进去
修改为10:第九次的时候,加第九个的时候把第八个变为null,相当于内存中只包含四个对象4*4=16差不多20兆
在放第十个的时候,弱引用也有一定的内存,放第十个的时候放不下啦导致了一次Full GC前面引用的byte数组全部清除掉了,只剩下最后一个
通过这个例子弱引用一般在垃圾回收时把一些弱引用对象它所占用的内存释放掉,同时弱引用自身占用的内存要释放也要配合引用队列来释放跟软引用非常的类似
垃圾回收算法:之前呢我们学过这个对象是否能作为垃圾回收,回收呢需要依赖一些回收方面的算法,常见有三种:
我们怎么判断一个对象是否是垃圾呢,沿着GC Root的引用链去找,扫描整个堆对象过程中发现这个对象被引用了,那么它需要保留,没有引用那么他就可以当做垃圾
标记清除:分两个阶段
先标记(看看哪些对象可以是垃圾)
清除(把对象所占用的空间进行释放)这里的释放是不是把这个内存每个字节进行清零操作呢?不是的,(并不会把所占用的内存进行一个清零操作)它把那个对象占用内存的起始结束地址,记录下来放到空闲的地址列表里,下次再分配对象的时候直接去这个地址列表中去找,看有没有一块足够的空间能够容纳我们的这个对象,从而进行内存分配
优点:速度快,只要把垃圾对象的起始地址做一下记录就完成了清除操作,不会做其他操作
缺点:可能产生内存碎片,这些空间不连续,虽然加起来内存较大,但是当有大的对象来时这些碎片不连续就存不进去,没有一个有效的内存给他用,可能造成内存的溢出问题
跟上面的标记清除第一个阶段是一样的把对象进行一个标记,看看哪些对象是垃圾:
整理:避免了标记清除时的碎片问题,当清除的时候,把可用的对象内存往前移动
整理之后呢,这些垃圾内存就没有了,我们发现这些内存变得更紧凑了,那么连续的空间就更多了,就会解决造成的垃圾碎片问题
优点:不会产生垃圾碎片问题
缺点:由于整理涉及到了对象的移动,必然效率变得较低,比如说一些变量引用了我这个对象,移动后这个变量就需要改变引用的地址啊,效率就低了,速度慢了
复制算法是把内存区划分成了两块大小的区域:
TO这个区域是空闲的
它首先也要做一个标记,找到不被引用的对象,标记为垃圾
然后呢把把From区域存活的对象复制到TO区域中,复制的过程中呢From区域也会完成碎片的整理
FROM区域全是垃圾了可以进行清除
并且交换FROM和TO 区域的位置:TO 变为FROM,FROM变为TO,TO总是一块空闲的区域
优点:不会产生碎片
缺点:占用双倍的一个内存空间
这三种算法在实际的JVM垃圾回收机制中,都会根据不同的情况来采用不会说只用其中一种算法,会结合多种算法实现垃圾回收,下面我们会讲一种分代垃圾回收机制