🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] <br> 接下来2节会一起来讲解哪些垃圾需要回收、什么时候回收、常用的垃圾回收算法以及垃圾收集器 ![](https://box.kancloud.cn/29d85108388ba9a7b565b88ddaeb0e5f_605x591.jpg) 在java内存模型中,程序计数器、虚拟机栈、本地方法栈这些区域每个线程独立拥有,与线程“同生共死”。每个栈帧内存大小是确定可知的,方法或线程结束时自动回收,这些区域不需要考虑内存回收问题。这里的垃圾回收指的是jvm堆内存和方法区的回收,这些区域内存在程序运行期间动态分配、动态回收,需要考虑选择合适的垃圾回收算法。 ## 一、如何判断对象是否已经“消亡“ ### 1.引用计数法 在Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。因此,很显然一个简单的办法是通过引用计数来判断一个对象是否可以回收。简单说,即一个对象如果没有任何与之关联的引用,即他们的引用计数都不为0,则说明对象不太可能再被用到,那么这个对象就是可回收对象。这种方法有个缺点就是无法检测到引用环的存在。 ### 2.根搜索算法(GCRootsTracing)/可达性分析算法 为了解决引用计数法的循环引用问题,Java使用了可达性分析的方法。通过一系列的“GCroots”对象作为起点搜索。如果在“GCroots”和一个对象之间没有可达路径,则称该对象是不可达的。 要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。 ![](https://box.kancloud.cn/6a43a2ffb8e33ca670fc7581f4784ede_491x297.JPEG) 左图中每个对象都存在引用链与GCRoots相连,表明对象还在,不能回收。 有图中三个对象虽然互相引用,但是没有链接与GCRoots相连,则可判断它们是可回收的对象。 <br> 在Java语言中,可作为GC Roots的对象包括下面几种:   a) 虚拟机栈中引用的对象(栈帧中的本地变量表);   b) 方法区中类静态属性引用的对象;   c) 方法区中常量引用的对象;   d) 本地方法栈中JNI(Native方法)引用的对象。 ## 二、常用垃圾回收算法 ### 1.标记-清除算法 ![](https://box.kancloud.cn/0028661ae688a6baac036e12bc1ff358_400x197.gif) 此算法分两段执行,mark阶段从引用根节点开始标记所有被引用的对象,寻找引用链,即仍然活着的对象。sweep阶段直接将未标记的对象清除。该算法的缺点是需要停顿整个应用(stoptheworld),而且容易产生内存碎片。 标记清除算法:未标记的对象被清除 ### 2.标记-复制算法 此算法把内存划分为相等大小的两个区域,每一只使用其中一个,回收过程中将存活的对象全部复制到另一个区域中,清空原区域。在年轻代中eden区和两个survivor区就是使用了此种算法。这种算法只复制存活的对象,成本较低,而且不会出现内存碎片问题,缺点是需要2倍的内存空间。 :-: ![](https://box.kancloud.cn/d4db06f3388f1411dd77a06b5ed7fe0d_413x143.gif) 标记--复制算法:只复制存活的对象 ### 3.标记-整理算法 该算法标记阶段和Mark-Sweep一样,但是在完成标记之后,它不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。所以,特别适用于存活对象多,回收对象少的情况下。效率比“标记-清理”算法低,但不会产生内存碎片。 :-: ![](https://box.kancloud.cn/20a44fdd1b8d1c2cf7f0f19036f36446_407x138.gif) ### 3.4 **分代收集算法**   **分代收集算法**是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),在堆区之外还有一个代就是永久代(Permanet Generation)。老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。 :-: ![](https://box.kancloud.cn/36eaf1f739b5db5024fb6e343743b01f_563x308.jpg) #### 3.4.1 **年轻代(Young Generation)的回收算法(复制算法)** (回收主要以Copying为主) a) 所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。 <br> b) 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空, 如此往复。 <br> c) 当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。若是老年代也满了就会触发一次Full GC(Major GC),也就是新生代、老年代都进行回收。 <br> d) 新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。 <br> #### 3.4.2 **年老代(Old Generation)的回收算法(标记回收算法)**(回收主要以Mark-Compact为主) 1. JAVA 虚拟机提到过的处于方法区的永生代(Permanet Generation),它用来存储 class 类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。 2. 对象的内存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前存放对象的那一块),少数情况会直接分配到老生代。 3. 当新生代的 Eden Space 和 From Space 空间不足时就会发生一次 GC ,进行 GC 后, EdenSpace和FromSpace区的存活对象会被挪到ToSpace,然后将EdenSpace和FromSpace进行清理。 4. 如果ToSpace无法足够存储某个对象,则将这个对象存储到老生代。 5. 在进行GC后,使用的便是EdenSpace和ToSpace了,如此反复循环。 6. 当对象在 Survivor 区躲过一次 GC 后,其年龄就会 +1 。默认情况下年龄到达 15 的对象会被移到老生代中。 简单来说 a) 在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。 <br> b) 内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。 <br> #### 3.4.3 **永久代(Permanent Generation)的回收算法**   用于存放静态文件,如Java类、方法等。永久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的永久代空间来存放这些运行过程中新增的类。永久代也称方法区,永久代的垃圾回收主要包括类型的卸载和废弃常量池的回收。当没有对象引用一个常量的时候,该常量即可以被回收。而类型的卸载更加复杂。必须满足一下三点,该类型的所有实例都被回收了,该类型的ClassLoader被回收了,该类型对应的java.lang.Class没有在任何地方被引用,在任何地方都无法通过反射来实例化一个对象 。