垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,而此时存在一个很大的矛盾,C++ 等语言创建对象要不断的去开辟空间,不用的时候又需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的 allocated,然后不停的析构。于是,有人就提出,能不能写一段程序实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?
1960年,基于 MIT 的 Lisp 首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时 Java 还没有出世呢!所以实际上 GC 并不是Java的专利,GC 的历史远远大于 Java 的历史!
Java中为了简化对象的释放,引入了自动的垃圾回收机制。通过垃圾回收器来对不再使用的对象完成自动的回收,垃圾回收器主要对堆上的内容进行回收,其他很多现代语言C#、Python Go都有自己的垃圾回收器。
引用计数算法(Reachability Counting)是通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,那么它的引用计数就减1,当该对象的引用计数为0时,那么该对象就会被回收。
缺点:存在循环引用的问题,当A引用B,B同时引用A时会出现对象无法回收的问题
可达性分析算法(Reachability Analysis)的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。
可达性分析法解决了引用计数器中的存在的循环引用的问题。
1、线程Thread对象。
2、系统类加载器加载的java.lang.Class对象。
3、监视器对象,用来保存同步锁synchronized关键字持有的对象。
4、本地方法调用时使用的全局对象。
可达性算法中描述的对象引用,一般指的是强引用,是GCRoot对象对普通对象有引用关系,只要这层关系存在,普通对象就不会被回收。
其他引用方式:
软引用:相对于强引用是一种比较弱的引用关系,如果一个对象只有软引用关联到它,当程序内存不足时,就会将软引用中的数据进行回收,在JDK1.2之后提供了SoftReference类来实现软引用,软引用常用于缓存中
软引用执行过程:
1.将对象使用软引用包装起来,new SoftReference<对象类型>(对象)。
2.内存不足时,虚拟机尝试进行垃圾回收。
3如果垃圾回收仍不能解决内存不足的问题,回收软引用中的对象。
4.如果依然内存不足,抛出OutOfMemory:异常。
软引用中的对象如果在内存不足时回收,SoftReference对象本身也需要被回收。如何知道哪些SoftReference对
象需要回收呢?
SoftReference提供了一套队列机制:
1、软引用创建时,通过构造器传入引用队列
2、在软引用中包含的对象被回收时,该软引用对象会被放入引用队列
3、通过代码遍历引用那队列,将SoftReference的强引用删除
弱引用的整体机制和软引用基本一致,区别在于弱引用包含的对象在垃圾回收画时,不管内存够不够都会直接被回收。在JDK1.2之后提供了weakReference类来实现弱引用,弱引用只要在Thread Local中使用
其他引用:虚引用、终结器引用、强引用
● 标记阶段:将所有存活对象进行标记,Java中使用可达性分析算法,从GCRoot开始通过引用链遍历出所有存活对象
● 清除阶段,从该内存中删除没有被标记的对象
1、碎片化问题:
由于内存是连续的,所以在对象被删除之后,内存中会出现很多细小的可用内存单元。如果我们需要的是一个比较大的空间,很有可能这些内存单元的大小过小无法进行分配。
2、分配速度慢
由于内存碎片的存在,需要维护一个空闲链表,极有可能发生每次需要遍历到链表的最后才能获得合适的内存空间。
1.准备两块空间From空间和To空间,每次在对象分配阶段,只能使用其中一块空间(Fro空间)。
2.在垃圾回收GC阶段,将From中存活对象复制到To空间。
3.将两块空间的From和To名字互换。
1、吞吐量高:复制算法只需要遍历一次存活对象,复制到To空间即可,比标记-整理算法少了一次遍历的过程,因而性能较好,但是不如标记-清除算法,因为标记清除算法不需要进行对象的移动
2、不会发生碎片化:复制算法在复制之后就会将对象按顺序放入TO空间中,所以对象以外的区域都是可用空间。不存在碎片化内存空间。
每次只能让一半的内存空间来为创建对象使用
标记整理算法也叫标记压缩算法,是对标记清理算法中容易产生内存碎片问题的一种解决方案。
核心思想分为两个阶段:
1.标记阶段,将所有存活的对象进行标记。Jva中使用可达性分析算法,从GC Root开始通过引用链遍历出
所有存活对象。
2整理阶段,将存活对象移动到堆的一端。清理掉存活对象的内存空间。
1、内存使用率高:整个堆内存都可以使用,不会像复制算法只能使用半个堆内存
2、在整理阶段可以将对象往内存的一侧进行移动,剩下的空间都是可以分配对象的有效空间
整理算法有很多种,比如L1sp2整理算法需要对整个堆中的对象搜索3次,整体性能不佳。可以通过Two一Finger、表格算法、ImmixGC等高效的整理算法优化此阶段的性能
分代垃圾回收将整个内存区域划分为年轻代和老年代
分代回收时,创建出来的对象,首先会被放入Eden伊甸园区。
随着对象在Eden区越来越多,如果Eden区满,新创建的对象已经无法放入,就会触发年轻代的GC,称为Minor GC或者Young GC。
Minor GC会把需要eden中和From需要回收的对象回收,把没有回收的对象放入To区。
接下来,S0会变成To区,S1变成From区。当eden区满时再往里放入对象,依然会发生Minor GC.
此时会回收eden区和S1(from)中的对象,并把eden和from区中剩余的对象放入S0.
注意:每次Minor GC中都会为对象记录他的年龄,初始值为0,每次GC完加1。当值达到15的时候就会把该对象放到老年代
当老年代中空间不足,无法放入新的对象时,先尝试minor gc如果还是不足,就会触发Full GC,Full GC会对整个堆进行垃圾回收。
如果Full GC依然无法回收掉老年代的对象,那么当对象继续放入老年代时,就会抛出Out Of Memory异常。
总的来说,垃圾回收机制是一项非常重要的技术,它解决了内存管理方面的难题,减轻了开发人员的负担,提高了程序的健壮性和可靠性。通过自动回收不再使用的内存资源,垃圾回收机制能够避免内存泄漏和悬空指针等问题,提高了程序的性能和效率。
然而,垃圾回收并非完美无缺,它也会带来一定的性能开销,因为垃圾回收机制需要占用一定的系统资源和时间。为了更好地利用垃圾回收机制,开发人员需要对内存管理有清晰的认识,并且在编写代码时尽量避免产生大量垃圾对象,合理地管理内存资源。
随着计算机技术的不断发展,垃圾回收机制也在不断改进和优化,未来有望在性能、效率和智能化方面取得更大的突破。同时,我们也需要不断学习和探索,以更好地利用垃圾回收机制,提高软件的质量和用户体验,为构建更加稳健和可靠的软件系统做出贡献。