当前位置:   article > 正文

c语言需要手动进行内存回收_Java虚拟机浅谈——垃圾收集器与内存分配策略(一)...

c语言需要自己gc么

在C语言中,有些由内存需要程序员在代码中进行手动回收,但是在Java中,没有这样的声明式操作。有没有人有去想过,Java到底做了什么可以自动进行垃圾回收呢?Java中的垃圾回收,是一点都不需要程序员关心,万无一失的吗?

这章里我们就一起来聊聊,Jvm中的垃圾收集器和内存分配策略。

垃圾收集(Garbage Collection,GC),并不是随着Java一起诞生的。GC的历史比Java来得更加久远,早在1960年的时候,MIT的Lisp是第一门真正使用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,人们就在思考GC需要完成的三件事情:□ 哪些内存需要回收?□ 什么时候回收?□ 如何回收?

在经过半个世纪的发展后,对于这三个问题的答案越来越清晰,总结成就是:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

在Java程序编写的过程中,我们可以知道代码的逻辑是怎样的,但是具体的分支只有在运行过程中才能知道。而这部分的内存分配和回收也是动态进行的,垃圾收集器主要关注的就是这部分内存。

那么实际中,一个需要解决的问题就是,如何判断对象是否存活,对于不再存活的对象,进行垃圾回收。

在经过漫长的发展后,目前主要有下面几种算法来进行对象存活判断。

一、引用计数算法

算法的定义为:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。

这是实现简单,且效率非常高效的一种算法。在redis、python的虚拟机、FlashPlayer等应用中,也都有采用这样的算法。但是Java中并没有采用这样的算法实现,主要原因是其存在相互循环引用的问题。

简单来说,A对象引用B对象,B对象引用A对象的情况下。A和B互相引用,于是他们的计数器都不会为0,于是GC收集器便就永远无法回收他们。

二、根搜索算法

算法的定义为:通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连,或者说不可达的时候,则证明此对象不可用。

在Java语言中,可以作为GC Roots的对象包括下面几种:

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

三、引用

在早期的JDK定义中,引用的定义为,如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。但这样的定义方式过于纯粹,一个对象只有两种状态,即被引用或者没有被引用两种。对于一些缓存类型的数据,则显得有些鸡肋,更无法体现内存分配的价值。

之后JDK对于引用进行了概念扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。

  • 强引用就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  • 软引用用来描述一些还有用,但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中并进行第二次回收。如果这次回收还是没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
  • 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。
  • 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是希望能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

四、是否死亡

在根搜索算法中,在GCRoots没有可以到达的引用链之后,就一定会“死亡”吗?其实也不一定,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行根搜索后发现没有与GCRoots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

当这个对象需要执行finalize()方法时,这个对象会被放置在一个名为F-Queue的队列中,并稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里的“执行”是虚拟机会触发这个方法,但并不一定会等待它运行结束。因为如果对象在finalize()方法中死循环或者超长时间执行,可能导致F-Queue队列中的其他对象永久处于等待状态,甚至可能导致内存回收系统奔溃。

finalize()方法是对象可以存活的最后一次机会,在这里可以将自己和引用链上的任何一个对象建立关联即可,否则就会进入到垃圾回收的系统中。但finalize()依旧是一种充满不确定性的方法,在诞生之初亦是为了C/C++程序员的更容易接受的一种妥协,推荐目前的try-finally方法处理更加优雅,也更安全可靠。


Java虚拟机浅谈系列 —— 往期文章:
郑小民:Java虚拟机浅谈--探索Java堆​zhuanlan.zhihu.com
郑小民:Java虚拟机浅谈--简介和基本结构​zhuanlan.zhihu.com
b562b097ec811ad19508686831a167a4.png
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小桥流水78/article/detail/785017
推荐阅读
相关标签
  

闽ICP备14008679号