共计 2957 个字符,预计需要花费 8 分钟才能阅读完成。
开篇介绍
大家好,我是 Java 最全面试题库
的提裤姐,明天这篇是 JavaSE 系列
的第十八篇,次要总结了JVM 中的垃圾回收
,在后续,会沿着第一篇开篇的常识线路始终总结上来,做到日更!如果我能做到百日百更,心愿你也能够跟着百日百刷,一百天养成一个好习惯。
GC 是什么? 为什么要有 GC?
Java 提供的 GC 性能能够主动监测对象是否超过作用域从而达到主动回收内存的目标,
Java 语言没有提供开释已分配内存的显式操作方法。
在堆中,找到曾经无用的对象,并把这些对象占用的空间发出使其能够从新利用。
要申请垃圾收集,能够调用上面的办法之一:
System.gc()
Runtime.getRuntime().gc()
。
算法思路:把所有的对象组成一个汇合,或者能够了解为树状构造,从树根开始找,只有能够找到的都是流动对象,如果找不到就应该被回收了。
Java 的 GC 哪些内存须要回收?
内存运行时 JVM 会有一个运行时数据区来治理内存。它次要包含 5 大部分:
- 程序计数器(Program Counter Register)
- 虚拟机栈(VM Stack)
- 本地办法栈(Native Method Stack)
- 办法区(Method Area)
- 堆(Heap)
而其中程序计数器、虚拟机栈、本地办法栈是每个线程公有的内存空间,随线程而生,随线程而亡。例如栈中每一个栈帧中调配多少内存基本上在类构造确定是哪个时就已知了,因而这 3 个区域的内存调配和回收都是确定的,无需思考内存回收的问题。
但办法区和堆就不同了,一个接口的多个实现类须要的内存可能不一样,咱们只有在程序运行期间才会晓得会创立哪些对象,这部分内存的调配和回收都是动静的,GC 次要关注的是这部分内存。
总结:GC 次要进行回收的内存是 JVM 中的办法区和堆
介绍一些常见的垃圾回收器
Serial 收集器
:单线程的收集器,收集垃圾时,必须 stop the world,应用复制算法。ParNew 收集器
:Serial 收集器的多线程版本,也须要 stop the world,复制算法。Parallel Scavenge 收集器
:新生代收集器,复制算法的收集器,并发的多线程收集器,指标是达到一个可控的吞吐量。如果虚拟机总共运行 100 分钟,其中垃圾花掉 1 分钟,吞吐量就是 99%。Serial Old 收集器
:是 Serial 收集器的老年代版本,单线程收集器,应用标记整顿算法。Parallel Old 收集器
:是 Parallel Scavenge 收集器的老年代版本,应用多线程,标记 - 整顿算法。CMS(Concurrent Mark Sweep) 收集器
:是一种以取得最短回收进展工夫为指标的收集器,标记革除算法,运作过程:初始标记,并发标记,从新标记,并发革除,收集完结会产生大量空间碎片。G1 收集器
:标记整顿算法实现,运作流程次要包含以下:初始标记,并发标记,最终标记,筛选标记。不会产生空间碎片,能够准确地管制进展。
CMS 收集器和 G1 收集器的区别?
- CMS 收集器是
老年代
的收集器,能够配合新生代的 Serial 和 ParNew 收集器一起应用,G1 收集器收集范畴是老年代和新生代
,不须要联合其余收集器应用 - CMS 收集器以最小的进展工夫为指标的收集器,G1 收集器可预测垃圾回收的进展工夫
- CMS 收集器是应用“
标记 - 革除
”算法进行的垃圾回收,容易产生内存碎片,G1 收集器应用的是“标记 - 整顿
”算法,进行了空间整合,升高了内存空间碎片。
GC 的收集办法有哪些?具体说一下每个的原理与特点?
标记 - 革除算法(Mark-Sweep)
从根节点开始标记所有可达对象,其余没有标记的即为垃圾对象,执行革除。但回收后的空间是不间断的。标记 - 革除算法采纳从根汇合进行扫描,对存活的对象标记,标记结束后,在扫描整个空间中未被标记的对象,进行回收。
标记 - 革除算法不须要进行对象的挪动,并且仅对不存活的对象进行解决,在存活对象比拟多的状况下极为高效,但因为标记 - 革除算法间接回收不存活的对象,因而会造成内存碎片。
复制算法
复制算法采纳从根汇合扫描,并将存活对象复制到一块新的,没有应用过的空间中,这种算法当控件存活的对象比拟少时,极为高效,然而带来的老本是须要一块内存替换空间进行对象的挪动。也就是 s0,s1 等空间。
标记 - 整顿法
标记 - 整顿算法采纳标记 - 革除算法一样的形式进行对象的标记,但在革除时,在回收不存活的对象占用的空间后,会将所有的存活对象网左端闲暇空间挪动,并更新相应的指针。标记 - 整顿算法是在标记 - 革除算法的根底上,又进行了对象的挪动,因而老本更高,然而却解决了内存碎片的问题。
Java 的 GC 什么时候回收垃圾?
对于堆中的对象,用可达性分析判断一个对象是否还存在援用,如果该对象没有任何援用就应该被回收。依据理论对援用的不同需要,分成了 4 种援用,每种援用的回收机制也是不同的。
对于办法区中的 常量
和类
,当一个常量没有任何对象援用它,它就能够被回收了。
对于 类
,如果能够断定它为无用类,就能够被回收了。
如何判断一个对象是否存活?
判断一个对象是否存活有两种办法:
1. 援用计数法
所谓援用计数法就是给每一个对象设置一个援用计数器,每当有一个中央援用这个对象
时,就将计数器加一,援用生效时,计数器就减一。当一个对象的援用计数器为零时,说
明此对象没有被援用,也就是“死对象”, 将会被垃圾回收.
援用计数法有一个缺点就是无奈解决循环援用问题,也就是说当对象 A 援用对象 B,对象
B 又援用者对象 A,那么此时 A,B 对象的援用计数器都不为零,也就造成无奈实现垃圾回
收,所以支流的虚拟机都没有采纳这种算法。
2. 可达性算法 (援用链法)
该算法的思维是:从一个被称为 GC Roots 的对象开始向下搜寻,如果一个对象到 GC
Roots 没有任何援用链相连时,则阐明此对象不可用。
在 java 中能够作为 GC Roots 的对象有以下几种:
- 虚拟机栈中援用的对象
- 办法区类动态属性援用的对象
- 办法区常量池援用的对象
- 本地办法栈 JNI 援用的对象
尽管这些算法能够断定一个对象是否能被回收,然而当满足上述条件时,一个对象比不肯定会被回收。
当一个对象不可达 GC Root 时,这个对象并不会立马被回收,而是出于一个死缓的阶段,若要被真正的回收须要经验两次标记:
如果对象在可达性剖析中没有与 GC Root 的援用链,那么此时就会被 第一次标记
并且进行一次筛选,筛选的条件是是否有必要执行 finalize()办法。当对象没有笼罩 finalize()办法或者已被虚拟机调用过,那么就认为是没必要的。
如果该对象有必要执行 finalize()办法,那么这个对象将会放在一个称为 F-Queue 的对队列中,虚构机会触发一个 Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺始终期待它运行完,这是因为如果 finalize()执行迟缓或者产生了死锁,那么就会造成 F-Queue 队列始终期待,造成了内存回收零碎的解体。GC 对处于 F-Queue 中的对象进行 第二次被标记
,这时,该对象将被移除”行将回收”汇合,期待回收。
什么是动态分派与动静分派?
动态分派
所有依赖动态类型来定位办法执行版本的分派动作称为动态分派,其典型利用是办法重载(依据参数的动态类型来定位指标办法)。
动态分派产生在编译阶段,因而确定动态分派的动作实际上不是由虚拟机执行的。
动静分派
在运行期依据理论类型确定办法执行版本。