泉州模板自助建站,服装网站技术解决方案,四川省建设工程质量与安全监督网站,装修室内设计培训学校垃圾收集策略与算法 程序计数器、虚拟机栈、本地方法栈随线程而生#xff0c;也随线程而灭#xff1b;栈帧随着方法的开始而入栈#xff0c;随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性#xff0c;在这几个区域内不需要过多考虑回收的问题#xff0c;因…垃圾收集策略与算法 程序计数器、虚拟机栈、本地方法栈随线程而生也随线程而灭栈帧随着方法的开始而入栈随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性在这几个区域内不需要过多考虑回收的问题因为方法结束或者线程结束时内存自然就跟随着回收了。 而对于 Java 堆和方法区我们只有在程序运行期间才能知道会创建哪些对象这部分内存的分配和回收都是动态的垃圾收集器所关注的正是这部分内存。
判定对象是否存活 若一个对象不被任何对象或变量引用那么它就是无效对象需要被回收。
引用计数法 在对象头维护着一个 counter 计数器对象被引用一次则计数器 1若引用失效则计数器 -1。当计数器为 0 时就认为该对象无效了。 引用计数算法的实现简单判定效率也很高在大部分情况下它都是一个不错的算法。但是主流的 Java 虚拟机里没有选用引用计数算法来管理内存主要是因为它很难解决对象之间循环引用的问题。 举个栗子 对象 objA 和 objB 都有字段 instance令 objA.instance objB 并且 objB.instance objA由于它们互相引用着对方导致它们的引用计数都不为 0于是引用计数算法无法通知 GC 收集器回收它们。 可达性分析法 所有和 GC Roots 直接或间接关联的对象都是有效对象和 GC Roots 没有关联的对象就是无效对象。
GC Roots 是指
Java 虚拟机栈栈帧中的本地变量表中引用的对象本地方法栈中引用的对象方法区中常量引用的对象方法区中类静态属性引用的对象
GC Roots 并不包括堆中对象所引用的对象这样就不会有循环引用的问题。
引用的种类 判定对象是否存活与“引用”有关。在 JDK 1.2 以前Java 中的引用定义很传统一个对象只有被引用或者没有被引用两种状态我们希望能描述这一类对象当内存空间还足够时则保留在内存中如果内存空间在进行垃圾收集后还是非常紧张则可以抛弃这些对象。很多系统的缓存功能都符合这样的应用场景。 在 JDK 1.2 之后Java 对引用的概念进行了扩充将引用分为了以下四种。不同的引用类型主要体现的是对象不同的可达性状态reachable和垃圾收集的影响。
强引用Strong Reference 类似 “Object obj new Object()” 这类的引用就是强引用只要强引用存在垃圾收集器永远不会回收被引用的对象。但是如果我们错误地保持了强引用比如赋值给了 static 变量那么对象在很长一段时间内不会被回收会产生内存泄漏。
软引用Soft Reference 软引用是一种相对强引用弱化一些的引用可以让对象豁免一些垃圾收集只有当 JVM 认为内存不足时才会去试图回收软引用指向的对象。JVM 会确保在抛出 OutOfMemoryError 之前清理软引用指向的对象。软引用通常用来实现内存敏感的缓存如果还有空闲内存就可以暂时保留缓存当内存不足时清理掉这样就保证了使用缓存的同时不会耗尽内存。
弱引用Weak Reference 弱引用的强度比软引用更弱一些。当 JVM 进行垃圾回收时无论内存是否充足都会回收只被弱引用关联的对象。
虚引用Phantom Reference 虚引用也称幽灵引用或者幻影引用它是最弱的一种引用关系。一个对象是否有虚引用的存在完全不会对其生存时间构成影响。它仅仅是提供了一种确保对象被 finalize 以后做某些事情的机制比如通常用来做所谓的 Post-Mortem 清理机制。
回收堆中无效对象 对于可达性分析中不可达的对象也并不是没有存活的可能。
判定 finalize() 是否有必要执行 JVM 会判断此对象是否有必要执行 finalize() 方法如果对象没有覆盖 finalize() 方法或者 finalize() 方法已经被虚拟机调用过那么视为“没有必要执行”。那么对象基本上就真的被回收了。 如果对象被判定为有必要执行 finalize() 方法那么对象会被放入一个 F-Queue 队列中虚拟机会以较低的优先级执行这些 finalize()方法但不会确保所有的 finalize() 方法都会执行结束。如果 finalize() 方法出现耗时操作虚拟机就直接停止指向该方法将对象清除。
对象重生或死亡 如果在执行 finalize() 方法时将 this 赋给了某一个引用那么该对象就重生了。如果没有那么就会被垃圾收集器清除。 任何一个对象的 finalize() 方法只会被系统自动调用一次如果对象面临下一次回收它的 finalize() 方法不会被再次执行想继续在 finalize() 中自救就失效了。 回收方法区内存 方法区中存放生命周期较长的类信息、常量、静态变量每次垃圾收集只有少量的垃圾被清除。方法区中主要清除两种垃圾
废弃常量无用的类
判定废弃常量 只要常量池中的常量不被任何变量或对象引用那么这些常量就会被清除掉。比如一个字符串 “bingo” 进入了常量池但是当前系统没有任何一个 String 对象引用常量池中的 “bingo” 常量也没有其它地方引用这个字面量必要的话bingo常量会被清理出常量池。
判定无用的类
判定一个类是否是“无用的类”条件较为苛刻。
该类的所有对象都已经被清除加载该类的 ClassLoader 已经被回收该类的 java.lang.Class 对象没有在任何地方被引用无法在任何地方通过反射访问该类的方法。 一个类被虚拟机加载进方法区那么在堆中就会有一个代表该类的对象java.lang.Class。这个对象在类被加载进方法区时创建在方法区该类被删除时清除。 垃圾收集算法 学会了如何判定无效对象、无用类、废弃常量之后剩余工作就是回收这些垃圾。常见的垃圾收集算法有以下几个
标记-清除算法 标记的过程是遍历所有的 GC Roots然后将所有 GC Roots 可达的对象标记为存活的对象。 清除的过程将遍历堆中所有的对象将没有标记的对象全部清除掉。与此同时清除那些被标记过的对象的标记以便下次的垃圾回收。
这种方法有两个不足
效率问题标记和清除两个过程的效率都不高。空间问题标记清除之后会产生大量不连续的内存碎片碎片太多可能导致以后需要分配较大对象时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
复制算法新生代 为了解决效率问题“复制”收集算法出现了。它将可用内存按容量划分为大小相等的两块每次只使用其中的一块。当这一块内存用完需要进行垃圾收集时就将存活者的对象复制到另一块上面然后将第一块内存全部清除。这种算法有优有劣
优点不会有内存碎片的问题。缺点内存缩小为原来的一半浪费空间。 为了解决空间利用率问题可以将内存分为三块 Eden、From Survivor、To Survivor比例是 8:1:1每次使用 Eden 和其中一块 Survivor。回收时将 Eden 和 Survivor 中还存活的对象一次性复制到另外一块 Survivor 空间上最后清理掉 Eden 和刚才使用的 Survivor 空间。这样只有 10% 的内存被浪费。 但是我们无法保证每次回收都只有不多于 10% 的对象存活当 Survivor 空间不够需要依赖其他内存指老年代进行分配担保。
分配担保 为对象分配内存空间时如果 EdenSurvivor 中空闲区域无法装下该对象会触发 MinorGC 进行垃圾收集。但如果 Minor GC 过后依然有超过 10% 的对象存活这样存活的对象直接通过分配担保机制进入老年代然后再将新对象存入 Eden 区。
标记-整理算法老年代 标记它的第一个阶段与标记-清除算法是一模一样的均是遍历 GC Roots然后将存活的对象标记。 整理移动所有存活的对象且按照内存地址次序依次排列然后将末端内存地址以后的内存全部回收。因此第二阶段才称为整理阶段。 这是一种老年代的垃圾收集算法。老年代的对象一般寿命比较长因此每次垃圾回收会有大量对象存活如果采用复制算法每次需要复制大量存活的对象效率很低。
分代收集算法 根据对象存活周期的不同将内存划分为几块。一般是把 Java 堆分为新生代和老年代针对各个年代的特点采用最适当的收集算法。
新生代复制算法老年代标记-清除算法、标记-整理算法