垃圾收集概述垃圾收集(以下简称GC)可以理解为无用内存的回收。出生于1960年的Lisp语言的作者约翰·麦卡锡认为垃圾回收需要完成三件事:什么内存需要回收?什么时候回收?如何回收?为什么要学习垃圾回收和内存分配?答案很简单:当需要排查各种内存溢出和内存泄漏,当垃圾回收成为系统实现更高并发的瓶颈时,我们必须对这些“自动化”技术进行必要的监控和调整。哪些内存需要回收?Java内存运行区中的程序计数器、虚拟机栈、本地方法栈是随着线程一起诞生和销毁的。每个栈帧分配的内存量在类结构确定的时候基本就知道了,所以这些区域的内存分配和回收都是确定性的,不需要过多考虑如何回收。当方法结束或者线程结束的时候,内存自然会被回收。Java堆和方法区这两个区域的内存空间是线程共享的,在程序运行过程中一直存在,并且有参数设置限制最大占用空间,所以需要回收这两部分的内存空间,而垃圾收集器关心的是如何管理这部分内存。几乎所有的对象实例都存储在堆中。在回收堆之前,垃圾回收器需要判断堆中哪些对象还“存活”,哪些对象“死了”(“死”是指不能被重用)判断一个对象在内存中是否是垃圾,“引用计数法”就是其中之一。判断一个对象是否存活的算法是这样的:只要有一个地方引用它,就给对象加上一个引用计数器,计数器值加一,当引用失效时,计数器值减一一。不能再使用计数器随时为零的对象。客观的说,引用计数算法(ReferenceCounting)虽然会占用一些额外的内存空间进行计数,但是原理简单,判断效率高。在大多数情况下,这是一个很好的算法。也有一些著名的使用引用计数算法进行内存管理的应用案例,例如:微软的COM(ComponentObjectModel)技术在Squirrel中使用了ActionScript3的FlashPlayerPython语言,在游戏脚本领域得到了广泛的应用。但是在Java领域,至少主流的Java虚拟机并没有使用引用计数算法来管理内存。主要原因是这个看似简单的算法有很多例外需要考虑,需要做很多额外的处理才能保证正确工作,比如简单的引用计数很难解决对象间循环引用的问题。ReachabilityAnalysisAlgorithm目前主流的商业编程语言(Java,C#,可以追溯到上面提到的古老的Lisp)的内存管理子系统都是使用ReachabilityAnalysis算法来判断对象是否存活。.这个算法的基本思想是:用一系列称为“GCRoots”的根对象作为起始节点集,从这些节点开始,按照引用关系向下查找。搜索过程所经过的路径称为“引用链”(ReferenceChain)如果一个对象与GCRoots之间没有引用链接,或者用图论的术语来说,当该对象从GCRoots无法到达时,证明对象不能再使用了。如下图所示,对象object5、object6、object7虽然相互关联,但是GCRoots不可达,所以会被判断为可回收对象。在Java技术体系中,可以固定为GCRoots的对象包括:虚拟机栈中引用的对象(栈帧中的局部变量表),如各个线程调用的方法栈中使用的参数,Local变量、临时变量等方法区中类静态属性引用的对象,如Java类的引用类型静态变量。方法区常量引用的对象,如字符串常量池(StringTable)中的引用。本地方法栈中JNI引用的对象(俗称Native方法)。Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻异常对象(如NullPointExcepiton、OutOfMemoryError)等,以及系统类加载器。同步锁(synchronized关键字)持有的所有对象。反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。除了固定的GCRoots集合外,还可以“临时”加入其他对象,组成一个完整的GCRoots集合(根据到用户选择的垃圾收集器和当前回收的内存区域)。例如,当垃圾回收只针对Java堆中的某个区域发起时(比如最典型的新生代垃圾回收),这个区域的对象可能会被位于堆中其他区域的对象引用。这时,你需要将这些关联区域中的对象也加入到GCRoots集合中,以保证可达性分析的正确性。无论是通过引用计数算法判断对象的引用次数,还是通过可达性分析算法判断对象的引用链是否可达,以及对象是否存活,Java中的引用都离不开“引用”。在JDK1.2之前,Java中的引用是一个很传统的定义:如果引用类型数据中存储的值表示另一块内存的起始地址,就说引用数据表示一块内存,某个对象引用.一个对象在这个定义下只有“被引用”和“未被引用”两种状态,用“食之无味,弃之可惜”来形容一些对象是无能为力的。比如我们希望描述一类对象:当内存空间还足够的时候,可以将它们保存在内存中。如果垃圾回收后内存空间还是很紧张,那么这些对象就可以丢弃——很多系统缓存功能都符合这样的应用场景。在JDK1.2版本之后,Java扩展了引用的概念,将引用分为四种类型:StronglyReference、SoftReference、WeakReference和PhantomReference,这四种引用强度依次减弱。强引用强引用是“引用”最传统的定义,指的是程序代码中普遍存在的引用赋值,即“Objectobj=newObject()”这样的引用关系。不管怎样,只要强引用关系还存在,垃圾回收器就永远不会回收被引用的对象。软引用软引用用于描述有用但不是必需的对象。仅与软引用关联的对象将被纳入回收范围,以便在系统即将发生内存溢出异常之前进行第二次回收。如果本次回收内存不足,会抛出内存溢出异常。JDK1.2之后提供了SoftReference类来实现软引用。弱引用弱引用也用于描述非必要的对象,但其强度弱于软引用,弱引用关联的对象只能存活到下一次垃圾回收发生。当垃圾回收器开始工作时,无论当前内存是否充足,只与弱引用相关联的对象都会被回收。JDK1.2之后提供了WeakReference类来实现弱引用。幻影引用幻影引用,也称为“幽灵引用”或“幻影引用”,是最弱的一类引用关系。一个对象是否有虚引用,根本不会影响它的生命周期,也不能通过虚引用来获取对象实例。为对象设置幻影引用关联的唯一目的是在对象被收集器回收时收到系统通知。JDK1.2之后提供了PhantomReference类来实现虚引用。个人总结一下垃圾回收器关注内存区域的哪一部分?程序计数器、虚拟机栈、本地方法栈都是随线程创建和销毁的。当方法或者线程结束的时候,内存自然会被回收,所以不用想太多如何回收。Java堆和方法区这两个区域有很大的不确定性。这部分内存的分配和回收是动态的,垃圾收集器关注的是如何管理这部分内存。判断一个物体是否存活的常用算法有哪些?判断过程是怎样的?引用计数算法为对象添加一个引用计数器。只要有对它的引用,计数器值就会加一;当引用无效时,计数器值减一;任何时候计数器为零的对象都不可能被再次使用。可达性分析算法以一系列称为“GCRoots”的根对象作为起始节点集,从这些节点开始,按照引用关系向下搜索。搜索过程所经过的路径称为“参考链”(ReferenceChain)。)如果一个对象与GCRoots之间没有引用链接,即当GCRoots无法到达这个对象时,证明这个对象不能再使用了。Java为什么不选择引用计数的方式来判断对象是否“活着”呢?引用计数算法虽然会占用一些额外的内存空间进行计数,但原理简单,判断效率高。在大多数情况下,这是一个很好的算法。引用计数的方法看似简单,但是要考虑很多异常情况,比如对象之间循环引用的问题。JDK1.2以后的Java引用类型有哪些?强引用。指程序代码中普遍存在的引用赋值,即“Objectobj=newObject()”这样的引用关系。不管怎样,只要强引用关系还存在,垃圾回收器就永远不会回收被引用的对象。软参考。用来描述一些有用但不是必需的对象。在系统即将发生内存溢出异常之前,这些对象会被列在回收范围内进行二次回收。如果本次回收内存不足,则会抛出内存溢出异常。SoftReference类用于实现软引用。弱引用。用来描述那些非必要的对象,但是它的强度比软引用要弱。与弱引用关联的对象只能存活到下一次垃圾回收发生。提供了WeakReference类来实现弱引用。幻影参考。也称为“幽灵引用”或“幽灵引用”,是最弱的一种引用关系。无法通过虚引用获取对象实例,唯一目的是在对象被回收器回收时收到系统通知。PhantomReference类用于实现幻象引用。参考《深入理解Java虚拟机第三版》
