浅谈垃圾收集机制与垃圾收集器(一)

时间记录:2020-05-03
最近在重温垃圾回收机制,然后能够更好的理解垃圾收集机制以及垃圾收集器的差异,不同的收集器之间的组合搭配和使用上。这次系统的学习了垃圾收集器和垃圾收集机制,以及触发条件等,收益良多。

前言
说起垃圾收集器,大部分人会把这项技术当做java语言伴生产物,走走早起就是因内存动态分配和垃圾收集技术语言。这里有个注意的是这里是动态分配和垃圾回收带也就是说完全由第三方进行管理的,不需要我们进行关心的。那为什么还需要我们去关系这个东西,当需要排查各种内存溢出、内存泄露问题时,当垃圾收集器集成为系统达到鞥高并发量的瓶颈时,就需要我们结合这些原理内容进行监控和调节。
在java中并不是所有的区域都需要进行垃圾回收的,程序计数器,虚拟机栈,本地方法三个区域是不需要要考虑垃圾回收的,方法结束或者线程结束时,内存自然就跟着回收了。而java堆和方法区则不一样,一个接口中的多个方法实现类需要的内存可能不一样,一个方法中多分分支需要的内存也可能不一样,我们只知道处于运行期间时才能知道会穿件那些对象,这部分内存的分配是动态的,垃圾收集器就是正对于这部分的内容。

回收算法
回收的时候判断一个对象是否能够被回收的标准就是说一个对象是否已死,对象是否已死的判断标准就是说这个对象是否被引用,也就是说是否还被使用着。大致的回收算法如下:

1:引用计数算法
给对象中添加一个引用计数器,每当有一个地方引用它的时候,计数器就加1;当引用失效时,计数器值减少1;当计数器为0时对象就是不可能再被使用了,也就是死亡了。但是这里其实是有问题的,如果对象是循环引用的,但是对象均都没有用了,就可以放弃,但是照这个就不不成立了,现在主流的java虚拟机里面没有选用引用计数算法来管理内存。

2:可达性分析算法
现在主流的商用程序语言的主流实现中,都是通过可达性分析来判断对象是否存活。这个算法的基本思路就是通过一系列的称为GC Roots 对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有引用链相连(GC Root到这个对象不可达),则证明此对象是不可用的。
在这里插入图片描述
我们可以很明显的知道其中的GC Root是一直存在的,而object1被引用了,则不能被销毁,而object5是处于游离的状态,所以是可以被回收的,这我们思考下之前的引用计数情况,可以知道如果是处于游离的不被任何人所使用的,就不能被回收了,就和object5情况一样了。可达性分析是目前使用的算法,但是如何回收这里另有说法。
GC Roots的对象包含以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象

引用的细节内容
如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存地表着一个引用,但是我们需要一些特殊的内容,当内存空间还足够时则能保留在内存中,如果内存空间在进行垃圾回收后还是非常的紧张,则可以抛弃这些对象。由此便村咋爱一些特殊的引用类型,现在将引用区分为强引用、软引用、弱引用、虚引用。

  • 强引用:就是程序代码中普遍存在的,只要强引用存在,垃圾收集器永远不会回收调被引用的对象
  • 软引用:用来描述一些还有用但是不是必须存在的对象,在系统将要发生内存溢出异常前,就会将这些对象列进回收范围之中进行第二次回收。(SoftReference)
  • 弱引用:用来描述非必须对象,但是它的请对比软引用更弱写,被弱引用关联的对象只能生存到下一次垃圾收集之前,且无论内存是否够用均会被回收。(WeakReference)
  • 虚引用:最弱的一种引用关系,一个对象是否是虚引用存在,完全对其生存时间构成影响,也无法通过虚引用来取得一个对象的实例。为一个对象停机虚引用关联的唯一目的就是能子这个对象被垃圾回收器回收的时候能够收到一个系统通知。(这里可以知道回收情况)(PhantomReference)

ThreadLocal中的使用 :在ThreadLocal中存储一些数据是与线程进行关联的,当线程消亡的时候对应的数据就会被消除,但是线程是线程池的方式使用的情况下,就会造成释放不了资源,所有存在一个弱引用,ThreadLcoalMap中Entry是一个弱引用,且是对应的一个数组,当ThreadLocal被置空的时候对应的ThreadLocalMap中的key就会应为弱引用的关系在下一次就会被回收,但是对应的value不会被回收,所以存在一个remove的方法,这样就保证了value的值会被释放,在使用的时候要注意用完了以后就马上进行remove操作,避免内存泄露。

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);//内容获取均与线程有关系,如果线程是线程池里的,
        //那么对应ThredLocalMap就不会不存在,这样就会导致一直存在内存中,所以才会使用弱引用来解决这个问题
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

虚引用必须和引用队列一起进行使用。

对象死亡过程
当可达性分析中不可达对象并不是非死不可的,这个时候会放到一个缓存中标记,而主要的取决点在于finalize()方法(名为析构函数,其实不是真的意思的析构,只是在死亡前的一个操作)。所以如果在对象进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行第一次的筛选。当对象有必要执行finalize()方法的时候会将对象置于一个叫F-Queue的队列中,这个有虚拟机的finalizer的线程进行执行的,我们在运行程序的时候注意下线程就会看到这个线程了,但是只会触发执行finalize()的方法,但是不会等待其执行结束,为的是防止方法内的造成阻塞的情况,如果不这样操作会导致整个的回收系统毁坏。第二次会对F-Queue中的队列进行第二次标记,如果然后将对象从队列中移除,然后就会被释放,如果在执行finalize()时候将对象引用放出则会逃脱被回收的结果。注意一个对象的finalize()方法只会被调用一次,调用完了就不在调用,所以只有一次逃脱的机会。

方法区回收
方法区位于永久代之中,我们通常认为永久代是不会被回收的,但是实际上却并不是这样的,方法区中包含了常量和被加载的类的结构数据,但是由于其回收的效率并不高所以代价大所以并不划算但是还是会进行回收的。回收的对象主要是针对于废弃常量和无用的类。在动态代理之中会生成很多代理类,这些代理类有时候会不再使用这个时候需要进行卸载,在如tomcat中会永久代的内存溢出,也就是因为加载的类太多了,所以进行回收还是有必要。而判断一个类无用的主要看一下几点:

  • 改类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例
  • 加载改类的ClassLoader已经被回收
  • 该类对应的java.lang.class对象没有在任何地方呗引用,无法在任何地方通过反射的方式可以访问此类。

在大量使用反射、动态代理、GCLib等字节码操作的框架都都需要虚拟机具备类卸载功能,以保证永久代不会溢出。

时间记录:2020-05-04

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值