diff --git a/README.md b/README.md index e7f101c7701..fa0e66dfe8f 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## 一些闲话: -> 1. **介绍**:关于 JavaGuide 的相关介绍请看:[关于 JavaGuide 的一些说明](https://www.yuque.com/snailclimb/dr6cvl/mr44yt#vu3ok) 。PDF 版本请看:[完结撒花!JavaGuide 面试突击版来啦!](./docs/javaguide面试突击版.md) 。 +> 1. **介绍**:关于 JavaGuide 的相关介绍请看:[关于 JavaGuide 的一些说明](https://www.yuque.com/snailclimb/dr6cvl/mr44yt) 。PDF 版本请看:[完结撒花!JavaGuide 面试突击版来啦!](./docs/javaguide面试突击版.md) 。 >2. **PDF版本** : [《JavaGuide 面试突击版》PDF 版本](#公众号) 。 > 3. **面试专版** :准备面试的小伙伴可以考虑面试专版:[《Java 面试进阶指南》](https://xiaozhuanlan.com/javainterview?rel=javaguide) ,欢迎加入[我的星球](https://wx.zsxq.com/dweb2/index/group/48418884588288)获取更多实用干货。 > 5. **联系我** :如要进群或者请教问题,请[联系我](#联系我) (备注来自 Github。请直入问题,工作时间不回复)。 diff --git "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" index da60ba36cd7..8038016104b 100644 --- "a/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" +++ "b/docs/java/basis/Java\345\237\272\347\241\200\347\237\245\350\257\206.md" @@ -965,8 +965,6 @@ public class Student { **多态的特点:** - 对象类型和引用类型之间具有继承(类)/实现(接口)的关系; -- 对象类型不可变,引用类型可变; -- 方法具有多态性,属性不具有多态性; - 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定; - 多态不能调用“只在子类存在但在父类不存在”的方法; - 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。 diff --git "a/docs/java/jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" "b/docs/java/jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" index 5ed4cd57a26..b979285a59d 100644 --- "a/docs/java/jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" +++ "b/docs/java/jvm/JVM\345\236\203\345\234\276\345\233\236\346\224\266.md" @@ -1,37 +1,45 @@ - + + + + + - [JVM 垃圾回收](#jvm-垃圾回收) - - [写在前面](#写在前面) - - [本节常见面试题](#本节常见面试题) - - [本文导火索](#本文导火索) - - [1 揭开 JVM 内存分配与回收的神秘面纱](#1--揭开-jvm-内存分配与回收的神秘面纱) - - [1.1 对象优先在 eden 区分配](#11-对象优先在-eden-区分配) - - [1.2 大对象直接进入老年代](#12-大对象直接进入老年代) - - [1.3 长期存活的对象将进入老年代](#13-长期存活的对象将进入老年代) - - [1.4 动态对象年龄判定](#14-动态对象年龄判定) - - [2 对象已经死亡?](#2-对象已经死亡) - - [2.1 引用计数法](#21-引用计数法) - - [2.2 可达性分析算法](#22-可达性分析算法) - - [2.3 再谈引用](#23-再谈引用) - - [2.4 不可达的对象并非“非死不可”](#24-不可达的对象并非非死不可) - - [2.5 如何判断一个常量是废弃常量](#25-如何判断一个常量是废弃常量) - - [2.6 如何判断一个类是无用的类](#26-如何判断一个类是无用的类) - - [3 垃圾收集算法](#3-垃圾收集算法) - - [3.1 标记-清除算法](#31-标记-清除算法) - - [3.2 复制算法](#32-复制算法) - - [3.3 标记-整理算法](#33-标记-整理算法) - - [3.4 分代收集算法](#34-分代收集算法) - - [4 垃圾收集器](#4-垃圾收集器) - - [4.1 Serial 收集器](#41-serial-收集器) - - [4.2 ParNew 收集器](#42-parnew-收集器) - - [4.3 Parallel Scavenge 收集器](#43-parallel-scavenge-收集器) - - [4.4.Serial Old 收集器](#44serial-old-收集器) - - [4.5 Parallel Old 收集器](#45-parallel-old-收集器) - - [4.6 CMS 收集器](#46-cms-收集器) - - [4.7 G1 收集器](#47-g1-收集器) - - [参考](#参考) - - + - [写在前面](#写在前面) + - [本节常见面试题](#本节常见面试题) + - [本文导火索](#本文导火索) + - [1 揭开 JVM 内存分配与回收的神秘面纱](#1-揭开-jvm-内存分配与回收的神秘面纱) + - [1.1 对象优先在 eden 区分配](#11-对象优先在-eden-区分配) + - [1.2 大对象直接进入老年代](#12-大对象直接进入老年代) + - [1.3 长期存活的对象将进入老年代](#13-长期存活的对象将进入老年代) + - [1.4 动态对象年龄判定](#14-动态对象年龄判定) + - [1.5 主要进行 gc 的区域](#15-主要进行-gc-的区域) + - [2 对象已经死亡?](#2-对象已经死亡) + - [2.1 引用计数法](#21-引用计数法) + - [2.2 可达性分析算法](#22-可达性分析算法) + - [2.3 再谈引用](#23-再谈引用) + - [2.4 不可达的对象并非“非死不可”](#24-不可达的对象并非非死不可) + - [2.5 如何判断一个常量是废弃常量?](#25-如何判断一个常量是废弃常量) + - [2.6 如何判断一个类是无用的类](#26-如何判断一个类是无用的类) + - [3 垃圾收集算法](#3-垃圾收集算法) + - [3.1 标记-清除算法](#31-标记-清除算法) + - [3.2 复制算法](#32-复制算法) + - [3.3 标记-整理算法](#33-标记-整理算法) + - [3.4 分代收集算法](#34-分代收集算法) + - [4 垃圾收集器](#4-垃圾收集器) + - [4.1 Serial 收集器](#41-serial-收集器) + - [4.2 ParNew 收集器](#42-parnew-收集器) + - [4.3 Parallel Scavenge 收集器](#43-parallel-scavenge-收集器) + - [4.4.Serial Old 收集器](#44serial-old-收集器) + - [4.5 Parallel Old 收集器](#45-parallel-old-收集器) + - [4.6 CMS 收集器](#46-cms-收集器) + - [4.7 G1 收集器](#47-g1-收集器) + - [4.8 ZGC 收集器](#48-zgc-收集器) + - [参考](#参考) + + + + # JVM 垃圾回收 ## 写在前面 @@ -56,7 +64,7 @@ 当需要排查各种内存溢出问题、当垃圾收集成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。 -## 1 揭开 JVM 内存分配与回收的神秘面纱 +## 1 揭开 JVM 内存分配与回收的神秘面纱 Java 的自动内存管理主要是针对对象内存的回收和对象内存的分配。同时,Java 自动内存管理最核心的功能是 **堆** 内存中对象的分配与回收。 @@ -68,9 +76,9 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(G 上图所示的 Eden 区、From Survivor0("From") 区、To Survivor1("To") 区都属于新生代,Old Memory 区属于老年代。 -大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 +大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 -> 修正([issue552](https://github.com/Snailclimb/JavaGuide/issues/552)):“Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值”。 +> 修正([issue552](https://github.com/Snailclimb/JavaGuide/issues/552)):“Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。 > > **动态年龄计算的代码如下** > @@ -88,12 +96,10 @@ Java 堆是垃圾收集器管理的主要区域,因此也被称作**GC 堆(G > uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; > ... > } -> -> ``` > -> +> ``` -经过这次GC后,Eden区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To"。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到老年代中。 +经过这次 GC 后,Eden 区和"From"区已经被清空。这个时候,"From"和"To"会交换他们的角色,也就是新的"To"就是上次 GC 前的“From”,新的"From"就是上次 GC 前的"To"。不管怎样,都会保证名为 To 的 Survivor 区域是空的。Minor GC 会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到老年代中。 ![堆内存常见分配策略 ](./pictures/jvm垃圾回收/堆内存.png) @@ -115,6 +121,7 @@ public class GCTest { } } ``` + 通过以下方式运行: ![](./pictures/jvm垃圾回收/25178350.png) @@ -130,6 +137,7 @@ public class GCTest { ```java allocation2 = new byte[900*1024]; ``` + ![](http://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-8-26/28128785.jpg) **简单解释一下为什么会出现这种情况:** 因为给 allocation2 分配内存的时候 eden 区内存几乎已经被分配完了,我们刚刚讲了当 Eden 区没有足够空间进行分配时,虚拟机将发起一次 Minor GC.GC 期间虚拟机又发现 allocation1 无法存入 Survivor 空间,所以只好通过 **分配担保机制** 把新生代的对象提前转移到老年代中去,老年代上的空间足够存放 allocation1,所以不会出现 Full GC。执行 Minor GC 后,后面分配的对象如果能够存在 eden 区的话,还是会在 eden 区分配内存。可以执行如下代码验证: @@ -149,8 +157,8 @@ public class GCTest { ``` - ### 1.2 大对象直接进入老年代 + 大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。 **为什么要这样呢?** @@ -158,16 +166,16 @@ public class GCTest { 为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。 ### 1.3 长期存活的对象将进入老年代 + 既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这一点,虚拟机给每个对象一个对象年龄(Age)计数器。 如果对象在 Eden 出生并经过第一次 Minor GC 后仍然能够存活,并且能被 Survivor 容纳的话,将被移动到 Survivor 空间中,并将对象年龄设为 1.对象在 Survivor 中每熬过一次 MinorGC,年龄就增加 1 岁,当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 ### 1.4 动态对象年龄判定 +大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 -大部分情况,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 s0 或者 s1,并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 `-XX:MaxTenuringThreshold` 来设置。 - -> 修正([issue552](https://github.com/Snailclimb/JavaGuide/issues/552)):“Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值”。 +> 修正([issue552](https://github.com/Snailclimb/JavaGuide/issues/552)):“Hotspot 遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了 survivor 区的一半时,取这个年龄和 MaxTenuringThreshold 中更小的一个值,作为新的晋升年龄阈值”。 > > **动态年龄计算的代码如下** > @@ -185,27 +193,27 @@ public class GCTest { > uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold; > ... > } -> +> > ``` > -> 额外补充说明([issue672](https://github.com/Snailclimb/JavaGuide/issues/672)):**关于默认的晋升年龄是15,这个说法的来源大部分都是《深入理解Java虚拟机》这本书。** -> 如果你去Oracle的官网阅读[相关的虚拟机参数](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html),你会发现`-XX:MaxTenuringThreshold=threshold`这里有个说明 +> 额外补充说明([issue672](https://github.com/Snailclimb/JavaGuide/issues/672)):**关于默认的晋升年龄是 15,这个说法的来源大部分都是《深入理解 Java 虚拟机》这本书。** +> 如果你去 Oracle 的官网阅读[相关的虚拟机参数](https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html),你会发现`-XX:MaxTenuringThreshold=threshold`这里有个说明 > -> **Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15 for the parallel (throughput) collector, and 6 for the CMS collector.默认晋升年龄并不都是15,这个是要区分垃圾收集器的,CMS就是6.** +> **Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15 for the parallel (throughput) collector, and 6 for the CMS collector.默认晋升年龄并不都是 15,这个是要区分垃圾收集器的,CMS 就是 6.** -### 1.5主要进行 gc 的区域 +### 1.5 主要进行 gc 的区域 -周志明先生在《深入理解Java虚拟机》第二版中P92如是写道: +周志明先生在《深入理解 Java 虚拟机》第二版中 P92 如是写道: -> ~~*“老年代GC(Major GC/Full GC),指发生在老年代的GC……”*~~ +> ~~_“老年代 GC(Major GC/Full GC),指发生在老年代的 GC……”_~~ -上面的说法已经在《深入理解Java虚拟机》第三版中被改正过来了。感谢R大的回答: +上面的说法已经在《深入理解 Java 虚拟机》第三版中被改正过来了。感谢 R 大的回答: ![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2020-8/b48228c2-ac00-4668-a78f-6f221f8563b5.png) **总结:** -针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种: +针对 HotSpot VM 的实现,它里面的 GC 其实准确分类只有两大种: 部分收集 (Partial GC): @@ -242,19 +250,18 @@ public class ReferenceCountingGc { } ``` - - ### 2.2 可达性分析算法 这个算法的基本思想就是通过一系列的称为 **“GC Roots”** 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的。 ![可达性分析算法 ](./pictures/jvm垃圾回收/72762049.png) -可作为GC Roots的对象包括下面几种: -* 虚拟机栈(栈帧中的本地变量表)中引用的对象 -* 本地方法栈(Native方法)中引用的对象 -* 方法区中类静态属性引用的对象 -* 方法区中常量引用的对象 +可作为 GC Roots 的对象包括下面几种: + +- 虚拟机栈(栈帧中的本地变量表)中引用的对象 +- 本地方法栈(Native 方法)中引用的对象 +- 方法区中类静态属性引用的对象 +- 方法区中常量引用的对象 ### 2.3 再谈引用 @@ -276,7 +283,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 **3.弱引用(WeakReference)** -如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 +如果一个对象只具有弱引用,那就类似于**可有可无的生活用品**。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。 @@ -286,7 +293,7 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 **虚引用主要用来跟踪对象被垃圾回收的活动**。 -**虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 +**虚引用与软引用和弱引用的一个区别在于:** 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。 特别注意,在程序设计中一般很少使用弱引用与虚引用,使用软引用的情况较多,这是因为**软引用可以加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生**。 @@ -304,9 +311,9 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 > 修正([issue747](https://github.com/Snailclimb/JavaGuide/issues/747),[reference](https://blog.csdn.net/q5706503/article/details/84640762)): > -> 1. **JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代** -> 2. **JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代** 。 -> 3. **JDK1.8 hotspot移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)** +> 1. **JDK1.7 之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时 hotspot 虚拟机对方法区的实现为永久代** +> 2. **JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是 hotspot 中的永久代** 。 +> 3. **JDK1.8 hotspot 移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)** 假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池了。 @@ -322,7 +329,6 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 虚拟机可以对满足上述 3 个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使用了就会必然被回收。 - ## 3 垃圾收集算法 ![垃圾收集算法分类](./pictures/jvm垃圾回收/垃圾收集算法.png) @@ -366,11 +372,12 @@ JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引 虽然我们对各个收集器进行比较,但并非要挑选出一个最好的收集器。因为直到现在为止还没有最好的垃圾收集器出现,更加没有万能的垃圾收集器,**我们能做的就是根据具体应用场景选择适合自己的垃圾收集器**。试想一下:如果有一种四海之内、任何场景下都适用的完美收集器存在,那么我们的 HotSpot 虚拟机就不会实现那么多不同的垃圾收集器了。 - ### 4.1 Serial 收集器 + Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 **“单线程”** 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( **"Stop The World"** ),直到它收集结束。 - **新生代采用复制算法,老年代采用标记-整理算法。** +**新生代采用复制算法,老年代采用标记-整理算法。** + ![ Serial 收集器 ](./pictures/jvm垃圾回收/46873026.png) 虚拟机的设计者们当然知道 Stop The World 带来的不良用户体验,所以在后续的垃圾收集器设计中停顿时间在不断缩短(仍然还有停顿,寻找最优秀的垃圾收集器的过程仍然在继续)。 @@ -378,9 +385,11 @@ Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了 但是 Serial 收集器有没有优于其他垃圾收集器的地方呢?当然有,它**简单而高效(与其他收集器的单线程相比)**。Serial 收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial 收集器对于运行在 Client 模式下的虚拟机来说是个不错的选择。 ### 4.2 ParNew 收集器 + **ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。** - **新生代采用复制算法,老年代采用标记-整理算法。** +**新生代采用复制算法,老年代采用标记-整理算法。** + ![ParNew 收集器 ](./pictures/jvm垃圾回收/22018368.png) 它是许多运行在 Server 模式下的虚拟机的首要选择,除了 Serial 收集器外,只有它能与 CMS 收集器(真正意义上的并发收集器,后面会介绍到)配合工作。 @@ -391,13 +400,12 @@ Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了 - **并发(Concurrent)**:指用户线程与垃圾收集线程同时执行(但不一定是并行,可能会交替执行),用户程序在继续运行,而垃圾收集器运行在另一个 CPU 上。 - ### 4.3 Parallel Scavenge 收集器 -Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和ParNew都一样。 **那么它有什么特别之处呢?** +Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它看上去几乎和 ParNew 都一样。 **那么它有什么特别之处呢?** ``` --XX:+UseParallelGC +-XX:+UseParallelGC 使用 Parallel 收集器+ 老年代串行 @@ -407,27 +415,32 @@ Parallel Scavenge 收集器也是使用复制算法的多线程收集器,它 ``` -**Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。** Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。 +**Parallel Scavenge 收集器关注点是吞吐量(高效率的利用 CPU)。CMS 等垃圾收集器的关注点更多的是用户线程的停顿时间(提高用户体验)。所谓吞吐量就是 CPU 中用于运行用户代码的时间与 CPU 总消耗时间的比值。** Parallel Scavenge 收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如果对于收集器运作不太了解,手工优化存在困难的时候,使用 Parallel Scavenge 收集器配合自适应调节策略,把内存管理优化交给虚拟机去完成也是一个不错的选择。 + +**新生代采用复制算法,老年代采用标记-整理算法。** - **新生代采用复制算法,老年代采用标记-整理算法。** ![Parallel Scavenge 收集器 ](./pictures/jvm垃圾回收/parllel-scavenge收集器.png) -**是JDK1.8默认收集器** - 使用java -XX:+PrintCommandLineFlags -version命令查看 +**这是 JDK1.8 默认收集器** + +使用 java -XX:+PrintCommandLineFlags -version 命令查看 ``` --XX:InitialHeapSize=262921408 -XX:MaxHeapSize=4206742528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC +-XX:InitialHeapSize=262921408 -XX:MaxHeapSize=4206742528 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC java version "1.8.0_211" Java(TM) SE Runtime Environment (build 1.8.0_211-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode) ``` -JDK1.8默认使用的是Parallel Scavenge + Parallel Old,如果指定了-XX:+UseParallelGC参数,则默认指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC来禁用该功能 + +JDK1.8 默认使用的是 Parallel Scavenge + Parallel Old,如果指定了-XX:+UseParallelGC 参数,则默认指定了-XX:+UseParallelOldGC,可以使用-XX:-UseParallelOldGC 来禁用该功能 ### 4.4.Serial Old 收集器 + **Serial 收集器的老年代版本**,它同样是一个单线程收集器。它主要有两大用途:一种用途是在 JDK1.5 以及以前的版本中与 Parallel Scavenge 收集器搭配使用,另一种用途是作为 CMS 收集器的后备方案。 ### 4.5 Parallel Old 收集器 - **Parallel Scavenge 收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。 + +**Parallel Scavenge 收集器的老年代版本**。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。 ### 4.6 CMS 收集器 @@ -452,7 +465,6 @@ JDK1.8默认使用的是Parallel Scavenge + Parallel Old,如果指定了-XX:+U ### 4.7 G1 收集器 - **G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.** 被视为 JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。它具备一下特点: @@ -462,7 +474,6 @@ JDK1.8默认使用的是Parallel Scavenge + Parallel Old,如果指定了-XX:+U - **空间整合**:与 CMS 的“标记--清理”算法不同,G1 从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。 - **可预测的停顿**:这是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内。 - G1 收集器的运作大致分为以下几个步骤: - **初始标记** @@ -470,22 +481,18 @@ G1 收集器的运作大致分为以下几个步骤: - **最终标记** - **筛选回收** - **G1 收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的 Region(这也就是它的名字 Garbage-First 的由来)**。这种使用 Region 划分内存空间以及有优先级的区域回收方式,保证了 G1 收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。 -## 参考 - -- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》 -- https://my.oschina.net/hosee/blog/644618 -- - - - - - - +### 4.8 ZGC 收集器 +与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。 +在 ZGC 中出现 Stop The World 的情况会更少! +详情可以看 : [《新一代垃圾回收器 ZGC 的探索与实践》](https://tech.meituan.com/2020/08/06/new-zgc-practice-in-meituan.html) +## 参考 +- 《深入理解 Java 虚拟机:JVM 高级特性与最佳实践(第二版》 +- https://my.oschina.net/hosee/blog/644618 +- diff --git "a/docs/java/multi-thread/synchronized\345\234\250JDK1.6\344\271\213\345\220\216\347\232\204\345\272\225\345\261\202\344\274\230\345\214\226.md" "b/docs/java/multi-thread/synchronized\345\234\250JDK1.6\344\271\213\345\220\216\347\232\204\345\272\225\345\261\202\344\274\230\345\214\226.md" index d8e454de48e..c3776c2da21 100644 --- "a/docs/java/multi-thread/synchronized\345\234\250JDK1.6\344\271\213\345\220\216\347\232\204\345\272\225\345\261\202\344\274\230\345\214\226.md" +++ "b/docs/java/multi-thread/synchronized\345\234\250JDK1.6\344\271\213\345\220\216\347\232\204\345\272\225\345\261\202\344\274\230\345\214\226.md" @@ -55,7 +55,7 @@ JDK1.6 对锁的实现引入了大量的优化来减少锁操作的开销,如: ### 锁粗化 -原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——直在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 +原则上,我们在编写代码的时候,总是推荐将同步块的作用范围限制得尽量小,——只在共享数据的实际作用域才进行同步,这样是为了使得需要同步的操作数量尽可能变小,如果存在锁竞争,那等待线程也能尽快拿到锁。 大部分情况下,上面的原则都是没有问题的,但是如果一系列的连续操作都对同一个对象反复加锁和解锁,那么会带来很多不必要的性能消耗。 diff --git "a/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" "b/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" index d8b1b523aeb..07e0c4c0c0a 100644 --- "a/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" +++ "b/docs/network/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234\347\237\245\350\257\206\346\200\273\347\273\223.md" @@ -216,7 +216,7 @@ 3. **ARP(Address Resolution Protocol)** : 地址解析协议。地址解析协议 ARP 把 IP 地址解析为硬件地址。 4. **ICMP(Internet Control Message Protocol )** :网际控制报文协议 (ICMP 允许主机或路由器报告差错情况和提供有关异常情况的报告)。 5. **子网掩码(subnet mask )** :它是一种用来指明一个 IP 地址的哪些位标识的是主机所在的子网以及哪些位标识的是主机的位掩码。子网掩码不能单独存在,它必须结合 IP 地址一起使用。 -6. **CIDR( Classless Inter-Domain Routing ) **:无分类域间路由选择 (特点是消除了传统的 A 类、B 类和 C 类地址以及划分子网的概念,并使用各种长度的“网络前缀”(network-prefix)来代替分类地址中的网络号和子网号)。 +6. **CIDR( Classless Inter-Domain Routing )**:无分类域间路由选择 (特点是消除了传统的 A 类、B 类和 C 类地址以及划分子网的概念,并使用各种长度的“网络前缀”(network-prefix)来代替分类地址中的网络号和子网号)。 7. **默认路由(default route)** :当在路由表中查不到能到达目的地址的路由时,路由器选择的路由。默认路由还可以减小路由表所占用的空间和搜索路由表所用的时间。 8. **路由选择算法(Virtual Circuit)** :路由选择协议的核心部分。因特网采用自适应的,分层次的路由选择协议。 @@ -247,7 +247,7 @@ ![TCP和UDP](https://img-blog.csdnimg.cn/img_convert/2bd5bf90676c338864807ade87b7bdea.png) -6. **端口(port) ** :端口的目的是为了确认对方机器是那个进程在于自己进行交互,比如 MSN 和 QQ 的端口不同,如果没有端口就可能出现 QQ 进程和 MSN 交互错误。端口又称协议端口号。 +6. **端口(port)** :端口的目的是为了确认对方机器是那个进程在于自己进行交互,比如 MSN 和 QQ 的端口不同,如果没有端口就可能出现 QQ 进程和 MSN 交互错误。端口又称协议端口号。 7. **停止等待协议(stop-and-wait)** :指发送方每发送完一个分组就停止发送,等待对方确认,在收到确认之后在发送下一个分组。 8. **流量控制** : 就是让发送方的发送速率不要太快,既要让接收方来得及接收,也不要使网络发生拥塞。 9. **拥塞控制** :防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。 @@ -345,4 +345,4 @@ HTTP 协议的本质就是一种浏览器与服务器之间约定好的通信格 1. 应用层的常见协议(重点关注 HTTP 协议) 2. 域名系统-从域名解析出 IP 地址 3. 访问一个网站大致的过程 -4. 系统调用和应用编程接口概念 \ No newline at end of file +4. 系统调用和应用编程接口概念