我们提到的不同引用类型其实是符合逻辑的,但是对于虚拟机来说,主要体现的是对象不同的可达状态和垃圾回收的效果。对于刚接触Java的C++程序员来说,了解栈和堆的关系可能会很陌生。在C++中,可以使用new运算符在堆上创建对象,也可以使用自动分配在堆栈上创建对象。下面的C++语句是合法的,但是Java编译器拒绝这样写代码,会出现语法错误编译错误。Integerfoo=整数(1);Java不同于C,Java中对象是放在堆上的,创建对象需要new操作符。局部变量存储在堆栈中,它们持有指向堆上对象的引用(指针)。下面是一个Java方法,它有一个Integer变量,它从一个String中解析出值,这行代码分配了一个新的Integer对象,JVM尝试在堆空间开辟一块内存空间。如果允许赋值,则调用Integer构造函数将String字符串转换为Integer对象。JVM在变量baz中存储指向该对象的指针。上述情况是我们乐于看到的。毕竟我们不想在写代码的时候遇到阻碍,但是这种情况是不可能发生的。当堆空间无法为bar和baz开辟内存空间时,会出现OutOfMemoryError,此时会调用垃圾收集器(garbagecollector)尝试释放内存空间。这就涉及到一个问题,垃圾回收器会回收哪些对象?垃圾收集器Java为你提供了一个new操作符来为堆中的对象开辟内存空间,但是它并没有提供delete操作符来释放对象空间。当foo()方法返回时,如果变量baz超过了最大内存,但是它指向的对象还在堆中。如果没有垃圾收集器,程序将抛出OutOfMemoryError错误。然而Java没有,它提供了垃圾收集器来释放不再被引用的对象。当程序试图创建一个新对象而堆中没有足够的空间时,垃圾收集器就会启动。当收集器访问堆时,请求线程被挂起,试图找到程序不再主动使用的对象并回收它们的空间。如果垃圾收集器无法释放足够的内存空间,并且JVM无法扩展堆,则会发生OutOfMemoryError,之后您的应用程序通常会崩溃。另一种情况是StackOverflowError,这是因为线程请求的堆栈深度大于虚拟机允许的深度而产生的。标记清除算法在Java中如此持久的原因之一是垃圾收集器。许多人认为JVM会为每个对象保留一个引用计数。每次引用该对象时,引用计数器的值为+1。当引用无效时,引用计数器的值为-1。垃圾回收器只会回收引用计数器有值的情况of0。这其实就是ReferenceCounting的收集方式。但是这种方法不能解决对象之间相互引用的问题,如下:b;b.a=a;}}然而,在实践中,JVM使用一种称为Mark-Sweep的算法。标记清除垃圾收集背后的思想很简单:程序无法到达的每个对象都是垃圾,都可以回收。Mark-sweepcollection有以下几个阶段Phase1:标记垃圾收集器将从根(root)引用开始,标记它到达的所有对象。如果老师类比给学生判断试卷,这就相当于判断试卷上所有答案是否正确的过程。阶段2:清理在第一阶段,所有可回收内容都可以由垃圾收集器回收。如果确定一个对象是一个可以回收的对象,那么这个对象就被放入一个finalizationqueue(回收队列)中,稍后由虚拟机自动创建的低优先级finalizer线程执行。.第三阶段:整理(可选)一些收藏家有第三步,整理。在这一步中,GC会将对象移动到垃圾收集器回收完对象后剩下的空闲空间中。这样做可以防止堆碎片,并防止由于堆空间不连续而在堆中分配大对象。所以上面的过程涉及到一个根节点(GCRoots)来判断是否有需要回收的对象。这个算法的基本思想是以一系列的GCRoots为起点,从这些节点开始向下搜索。搜索所经过的路径称为参考链(ReferenceChain)。当一个对象与GCRoots之间没有引用链连接时,证明该对象不可用。任何在引用链上可以被访问到的对象都是强引用对象,垃圾回收器不会回收强引用对象。所以,回到foo()方法,参数bar和局部变量baz只有在方法执行时才是强引用。一旦方法完成执行并且它们都超出范围,它们引用的对象将被垃圾收集。让我们考虑一个例子LinkedListfoo=newLinkedList();foo.add(newInteger(111));变量foo是对LinkedList对象的强引用。LinkedList(JDK.18)是一种链表数据结构,每个元素指向前驱元素,每个元素都有其后继元素。当我们调用add()方法时,会添加一个新的链表元素,链表元素指向值为111的Integer实例,这是一个强引用链,即这个Integer实例不符合条件用于垃圾收集。一旦foo对象超出程序执行范围,LinkedList及其引用内容都可以被收集。收藏的前提是没有强引用关系。FinalizersC++允许对象定义析构函数方法:当对象超出范围或被显式删除时,将调用析构函数来清理已使用的资源。对于大多数对象,析构函数会释放使用new或malloc函数分配的内存。在Java中,垃圾收集器会自动为您清除对象,分配内存,因此您不需要显式析构函数来执行此操作。这也是Java和C++的一大区别。但是,内存并不是唯一需要释放的资源。考虑FileOutputStream:当您创建此对象的实例时,它会从操作系统分配一个文件句柄。如果让流的引用在关闭之前超出范围,文件句柄会发生什么情况?事实上,每个流都会有一个终结器方法,在被垃圾收集器回收之前由JVM调用。对于FileOutputStream,finalizer方法会关闭流,将文件句柄释放给操作系统,然后清空缓冲区,保证数据可以写入磁盘。任何对象都有一个终结器方法,您所要做的就是声明finalize()方法。protectedvoidfinalize()throwsThrowable{//Clearobject}虽然finalizers的finalize()方法是一个很好的清理方法,但是这个方法的负面影响非常大,你不应该依赖这个方法做任何垃圾收集工作.因为finalize方法的运行开销比较大,不确定性强,无法保证每个对象的调用顺序。finalize可以做的任何事情,都可以使用try-finally或其他方法来完成,甚至更好。一个对象的生命周期综上所述,一个对象的生命周期可以概括为通过以下过程将被垃圾收集器收集。图中红色区域表示该对象处于强可达阶段。JDK1.2引入了java.lang.ref包。对象的生命周期有四个阶段:StronglyReachable(StronglyReachable#1113116;)、SoftReachable(软可达#1113116;)、WeakReachable#1113116;、PhantomReachable#1113116;。如果只讨论符合垃圾回收条件的对象,无外乎三种类型:软可达、弱可达和幻可达。Softreachable:Softreachable是指我们只能通过软引用访问,软可达对象是软引用引用的对象,不存在强引用对象。软引用用于描述有用但不是必需的对象。垃圾收集器将尽可能长时间地保留软引用对象,但会在OutOfMemoryError发生之前回收软引用对象。如果软引用对象被回收了,内存还是不够分配,会直接抛出OutOfMemoryError。WeaklyReachable:弱可达对象是被WeakReference引用的对象。垃圾收集器可以随时收集弱引用对象,不会试图保留软引用对象。Phantom可达性:Phantom可达性是由PhantomReference引用的对象。Phantomreachability是指没有强引用、软引用或弱引用与之关联,并且已经确定。仅当幻象引用指向此对象时。此外,还有两个可达性判断条件:强可达和不可达。):无法到达的对象意味着该对象可以被清理。下面是不同可达性状态的转换图,用来判断可达性条件,这也是JVM垃圾回收器在决定如何处理对象时考虑的一部分。所有对象可达性引用都是java.lang.ref.Reference的子类,它有一个返回引用对象的get()方法。如果此引用对象已被清除(以编程方式或由垃圾收集器清除),则此方法返回null。也就是说,除了虚引用,软引用和弱引用都可以获得对象。而且,这些对象可以被人为拯救,变成强引用,比如给对象赋this关键字,只要重新关联到引用链上的任何对象即可。ReferenceQueue引用队列又名ReferenceQueue,位于java.lang.ref包下。当我们创建各种引用(软引用、弱引用、虚引用)并将它们与响应对象关联起来,我们可以选择是否关联引用队列。JVM会在特定的时间将引用入队到队列中,程序通过判断引用队列中是否加入了引用就可以知道被引用的对象是否被GC回收了。Referencejava.lang.ref.Reference是软引用、弱引用、幻引用的父类。因为Reference对象是与垃圾收集紧密合作实现的,所以这个类可能不会被直接子类化。
