写点什么

Java 世界里的垃圾回收规则你搞懂了吗?,java 编程思想百度云

用户头像
极客good
关注
发布于: 刚刚


引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。


看似很美好,但我们知道 JVM 的垃圾回收就是"Stop-The-World"的,那是什么原因导致我们最终放弃了引用计数算法呢?看下面的例子。


public class ReferenceCountingGC {


public Object instance;


public ReferenceCountingGC(String name){}


}


public static void testGC(){


ReferenceCountingGC a = new ReferenceCountingGC("objA");


ReferenceCountingGC b = new ReferenceCountingGC("objB");


a.instance = b;


b.instance = a;


a = null;


b = null;


}


1. 定义 2 个对象


2. 相互引用


3. 置空各自的声明引用



我们可以看到,最后这 2 个对象已经不可能再被访问了,但由于他们相互引用着对方,导致它们的引用计数永远都不会为 0,通过引用计数算法,也就永远无法通知 GC 收集器回收它们。


可达性分析算法


可达性分析算法(Reachability Analysis)的基本思路是,通过一些被称为引用链(GC Roots)的对象作为起点,从这些节点开始向下搜索,搜索走过的路径被称为(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时(即从 GC Roots 节点到该节点不可达),则证明该对象是不可用的。



通过可达性算法,成功解决了引用计数所无法解决的问题-“循环依赖”,只要你无法与 GC Root 建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些属于 GC Root。


Java 内存区域


在 Java 语言中,可作为 GC Root 的对象包括以下 4 种:


  • 虚拟机栈(栈帧中的本地变量表)中引用的对象

  • 方法区中类静态属性引用的对象

  • 方法区中常量引用的对象

  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象



虚拟机栈(栈帧中的本地变量表)中引用的对象


此时的 s,即为 GC Root,当 s 置空时,localParameter 对象也断掉了与 GC Root 的引用链,将被回收。


public class StackLocalParameter {


public StackLocalParameter(String name){}


}


public static void testGC(){


StackLocalParameter s = new StackLocalParameter("localParameter");


s = null;


}


方法区中类静态属性引用的对象


s 为 GC Root,s 置为 null,经过 GC 后,s 所指向的 properties 对象由于无法与 GC Root 建立关系被回收。


而 m 作为类的静态属性,也属于 GC Root,parameter 对象依然与 GC root 建立着连接,所以此时 parameter 对象并不会被回收。


public class MethodAreaStaicProperties {


public static MethodAreaStaicProperties m;


public MethodAreaStaicProperties(String name){}


}


public static void testGC(){


MethodAreaStaicProperties s = new MethodAreaStaicProperties("properties");


s.m = new MethodAreaStaicProperties("parameter");


s = null;


}


方法区中常量引用的对象


m 即为方法区中的常量引用,也为 GC Root,s 置为 null 后,final 对象也不会因没有与 GC Root 建立联系而被回收。


public class MethodAreaStaicProperties {


public static final MethodAreaStaicProperties m = MethodAreaStaicProperties("final");


public MethodAreaStaicProperties(String name){}


}


public static void testGC(){


MethodAreaStaicProperties s = new MethodAreaStaicProperties("staticProperties");


s = null;


}


本地方法栈中引用的对象


任何 Native 接口都会使用某种本地方法栈,实现的本地方法接口是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。当线程调用 Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈。然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不再在线程的 Java 栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。



怎么回收垃圾




[](


)在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。由于 Java 虚拟机规范并没有对如何实现垃圾收集器做出明确的规定,因此各个厂商的虚拟机可以采用不同的方式来实现垃圾收集器,这里我们讨论几种常见的垃圾收集算法的核心思想。


===========================================================================================================================================================================================================


[](


)标记 — 清除算法


=======================================================================


[](


)



=======================================================================================================================


【一线大厂Java面试题解析+核心总结学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


======================================================================


[](


)标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为 2 部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。就像上图一样,清理掉的垃圾就变成未使用的内存区域,等待被再次使用。


=============================================================================================================================================================================


[](


)这逻辑再清晰不过了,并且也很好操作,但它存在一个很大的问题,那就是内存碎片。


================================================================================================


[](


)上图中等方块的假设是 2M,小一些的是 1M,大一些的是 4M。等我们回收完,内存就会切成了很多段。我们知道开辟内存空间时,需要的是连续的内存区域,这时候我们需要一个 2M 的内存区域,其中有 2 个 1M 是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。


=================================================================================================================================================================================================


[](


)复制算法


==================================================================


[](


)



=============================================================================================================================================================================================


复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。


上面的图很清楚,也很明显的暴露了另一个问题,合着我这 140 平的大三房,只能当 70 平米的小两房来使?代价实在太高。


标记整理算法



用户头像

极客good

关注

还未添加个人签名 2021.03.18 加入

还未添加个人简介

评论

发布
暂无评论
Java 世界里的垃圾回收规则你搞懂了吗?,java编程思想百度云