当前位置: 首页 > news >正文

免费搭建个人业务网站图案设计网站有哪些

免费搭建个人业务网站,图案设计网站有哪些,wordpress站点地址灰显,做外贸的阿里巴巴网站是哪个更好前言 Java 相比 C/C 最显著的特点便是引入了自动垃圾回收 (下文统一用 GC 指代自动垃圾回收)#xff0c;它解决了 C/C 最令人头疼的内存管理问题#xff0c;让程序员专注于程序本身#xff0c;不用关心内存回收这些恼人的问题#xff0c;这也是 Java 能大行其道的重要原因之… 前言 Java 相比 C/C 最显著的特点便是引入了自动垃圾回收 (下文统一用 GC 指代自动垃圾回收)它解决了 C/C 最令人头疼的内存管理问题让程序员专注于程序本身不用关心内存回收这些恼人的问题这也是 Java 能大行其道的重要原因之一GC 真正让程序员的生产力得到了释放但是程序员很难感知到它的存在这就好比我们吃完饭后在桌上放下餐盘即走服务员会替你收拾好这些餐盘你不会关心服务员什么时候来收怎么收。有人说既然 GC 已经自动我们完成了清理,不了解 GC 貌似也没啥问题。在大多数情况下确实没问题不过如果涉及到一些性能调优问题排查等深入地了解 GC 还是必不可少的曾经美团通过调整 JVM 相关 GC 参数让服务响应时间 TP90TP99都下降了10ms服务可用性得到了很大的提升所以深入了解 GC 是成为一名优秀 Java 程序员的必修课垃圾回收分上下篇上篇会先讲垃圾回收理论主要包括GC 的几种主要的收集方法标记清除、标记整理、复制算法的原理与特点各自的优劣势为啥会有 Serial CMS, G1 等各式样的回收器各自的优劣势是什么为啥没有一个统一的万能的垃圾回收器新生代为啥要设置成 Eden, S0,S1 这三个区基于什么考虑呢堆外内存不受 GC 控制那该怎么释放呢对象可回收就一定会被回收吗什么是 SafePoint,什么是 Stop The World下篇主要讲垃圾回收的实践主要包括GC 日志格式怎么看主要有哪些发生 OOM 的场景发生 OOM如何定位常用的内存调试工具有哪些本文会从以下几方面来阐述垃圾回收JVM 内存区域如何识别垃圾引用计数法可达性算法垃圾回收主要方法标记清除法复制法标记整理法分代收集算法垃圾回收器对比文字比较多不过也为了便于读者理解加了不少 GC 的动画相信看完会有不少收获JVM 内存区域 要搞懂垃圾回收的机制我们首先要知道垃圾回收主要回收的是哪些数据这些数据主要在哪一块区域所以我们一起来看下 JVM 的内存区域虚拟机栈描述的是方法执行时的内存模型,是线程私有的生命周期与线程相同,每个方法被执行的同时会创建栈桢下文会看到主要保存执行方法时的局部变量表、操作数栈、动态连接和方法返回地址等信息,方法执行时入栈方法执行完出栈出栈就相当于清空了数据入栈出栈的时机很明确所以这块区域不需要进行 GC。本地方法栈与虚拟机栈功能非常类似主要区别在于虚拟机栈为虚拟机执行 Java 方法时服务而本地方法栈为虚拟机执行本地方法时服务的。这块区域也不需要进行 GC程序计数器线程独有的 可以把它看作是当前线程执行的字节码的行号指示器比如如下字节码内容在每个字节码前面都有一个数字行号我们可以认为它就是程序计数器存储的内容记录这些数字指令地址有啥用呢我们知道 Java 虚拟机的多线程是通过线程轮流切换并分配处理器的时间来完成的在任何一个时刻一个处理器只会执行一个线程如果这个线程被分配的时间片执行完了线程被挂起处理器会切换到另外一个线程执行当下次轮到执行被挂起的线程唤醒线程时怎么知道上次执行到哪了呢通过记录在程序计数器中的行号指示器即可知道所以程序计数器的主要作用是记录线程运行时的状态方便线程被唤醒时能从上一次被挂起时的状态继续执行需要注意的是程序计数器是唯一一个在 Java 虚拟机规范中没有规定任何 OOM 情况的区域所以这块区域也不需要进行 GC本地内存线程共享区域Java 8 中本地内存也是我们通常说的堆外内存包含元空间和直接内存,注意到上图中 Java 8 和 Java 8 之前的 JVM 内存区域的区别了吗在 Java 8 之前有个永久代的概念实际上指的是 HotSpot 虚拟机上的永久代它用永久代实现了 JVM 规范定义的方法区功能主要存储类的信息常量静态变量即时编译器编译后代码等这部分由于是在堆中实现的受 GC 的管理不过由于永久代有 -XX:MaxPermSize 的上限所以如果动态生成类将类信息放入永久代或大量地执行 String.intern 将字段串放入永久代中的常量区很容易造成 OOM有人说可以把永久代设置得足够大但很难确定一个合适的大小受类数量常量数量的多少影响很大。所以在 Java 8 中就把方法区的实现移到了本地内存中的元空间中这样方法区就不受 JVM 的控制了,也就不会进行 GC也因此提升了性能发生 GC 会发生 Stop The Word,造成性能受到一定影响后文会提到也就不存在由于永久代限制大小而导致的 OOM 异常了假设总内存1GJVM 被分配内存 100M 理论上元空间可以分配 2G-100M 1.9G空间大小足够也方便在元空间中统一管理。综上所述在 Java 8 以后这一区域也不需要进行 GC        画外音 思考一个问题堆外内存不受 GC控制无法通过 GC 释放内存那该以什么样的形式释放呢总不能只创建不释放吧这样的话内存可能很快就满了这里不做详细阐述请看文末的参考文章堆前面几块数据区域都不进行 GC那只剩下堆了是的这里是 GC 发生的区域对象实例和数组都是在堆上分配的GC 也主要对这两类数据进行回收这块也是我们之后重点需要分析的区域如何识别垃圾 上一节我们详细讲述了 JVM 的内存区域知道了 GC 主要发生在堆那么 GC 该怎么判断堆中的对象实例或数据是不是垃圾呢或者说判断某些数据是否是垃圾的方法有哪些。引用计数法最容易想到的一种方式是引用计数法啥叫引用计数法简单地说就是对象被引用一次在它的对象头上加一次引用次数如果没有被引用引用次数为 0则此对象可回收String ref new String(Java); 以上代码 ref1 引用了右侧定义的对象所以引用次数是 1如果在上述代码后面添加一个 ref null则由于对象没被引用引用次数置为 0由于不被任何变量引用此时即被回收动图如下看起来用引用计数确实没啥问题了不过它无法解决一个主要的问题循环引用啥叫循环引用public class TestRC {TestRC instance;public TestRC(String name) {}public static void main(String[] args) {// 第一步A a new TestRC(a);B b new TestRC(b);// 第二步a.instance b;b.instance a;// 第三步a null;b null;} } 按步骤一步步画图到了第三步虽然 ab 都被置为 null 了但是由于之前它们指向的对象互相指向了对方引用计数都为 1所以无法回收也正是由于无法解决循环引用的问题所以现代虚拟机都不用引用计数法来判断对象是否应该被回收。可达性算法现代虚拟机基本都是采用这种算法来判断对象是否存活可达性算法的原理是以一系列叫做  GC Root  的对象为起点出发引出它们指向的下一个节点再以下个节点为起点引出此节点指向的下一个结点。。。这样通过 GC Root 串成的一条线就叫引用链直到所有的结点都遍历完毕,如果相关对象不在任意一个以 GC Root 为起点的引用链中则这些对象会被判断为「垃圾」,会被 GC 回收。如图示如果用可达性算法即可解决上述循环引用的问题因为从GC Root 出发没有到达 a,b,所以 ab 可回收a, b 对象可回收就一定会被回收吗?并不是对象的 finalize 方法给了对象一次垂死挣扎的机会当对象不可达可回收时当发生GC时会先判断对象是否执行了 finalize 方法如果未执行则会先执行 finalize 方法我们可以在此方法里将当前对象与 GC Roots 关联这样执行 finalize 方法之后GC 会再次判断对象是否可达如果不可达则会被回收如果可达则不回收注意 finalize 方法只会被执行一次如果第一次执行 finalize 方法此对象变成了可达确实不会回收但如果对象再次被 GC则会忽略 finalize 方法对象会被回收这一点切记!那么这些 GC Roots 到底是什么东西呢哪些对象可以作为 GC Root 呢有以下几类虚拟机栈栈帧中的本地变量表中引用的对象方法区中类静态属性引用的对象方法区中常量引用的对象本地方法栈中 JNI即一般说的 Native 方法引用的对象虚拟机栈中引用的对象如下代码所示a 是栈帧中的本地变量当 a null 时由于此时 a 充当了 GC Root 的作用a 与原来指向的实例 new Test() 断开了连接所以对象会被回收。public class Test {public static void main(String[] args) {Test a new Test();a null;} } 方法区中类静态属性引用的对象如下代码所示当栈帧中的本地变量 a null 时由于 a 原来指向的对象与 GC Root (变量 a) 断开了连接所以 a 原来指向的对象会被回收而由于我们给 s 赋值了变量的引用s 在此时是类静态属性引用充当了 GC Root 的作用它指向的对象依然存活!public class Test {public static Test s;public static void main(String[] args) {Test a new Test();a.s new Test();a null;} } 方法区中常量引用的对象如下代码所示常量 s 指向的对象并不会因为 a 指向的对象被回收而回收public class Test {public static final Test s new Test();public static void main(String[] args) {Test a new Test();a null;} } 本地方法栈中 JNI 引用的对象这是简单给不清楚本地方法为何物的童鞋简单解释一下所谓本地方法就是一个 java 调用非 java 代码的接口该方法并非 Java 实现的可能由 C 或 Python等其他语言实现的 Java 通过 JNI 来调用本地方法 而本地方法是以库文件的形式存放的在 WINDOWS 平台上是 DLL 文件形式在 UNIX 机器上是 SO 文件形式。通过调用本地的库文件的内部方法使 JAVA 可以实现和本地机器的紧密联系调用系统级的各接口方法还是不明白见文末参考对本地方法定义与使用有详细介绍。当调用 Java 方法时虚拟机会创建一个栈桢并压入 Java 栈而当它调用的是本地方法时虚拟机会保持 Java 栈不变不会在 Java 栈祯中压入新的祯虚拟机只是简单地动态连接并直接调用指定的本地方法。JNIEXPORT void JNICALL Java_com_pecuyu_jnirefdemo_MainActivity_newStringNative(JNIEnv *env, jobject instancejstring jmsg) { ...// 缓存String的classjclass jc (*env)-FindClass(env, STRING_PATH); } 如上代码所示当 java 调用以上本地方法时jc 会被本地方法栈压入栈中, jc 就是我们说的本地方法栈中 JNI 的对象引用因此只会在此本地方法执行完成后才会被释放。垃圾回收主要方法 上一节我们知道了可以通过可达性算法来识别哪些数据是垃圾那该怎么对这些垃圾进行回收呢。主要有以下几种方式方式标记清除算法复制算法标记整理法标记清除算法步骤很简单先根据可达性算法标记出相应的可回收对象图中黄色部分对可回收的对象进行回收操作起来确实很简单也不用做移动数据的操作那有啥问题呢仔细看上图没错内存碎片假如我们想在上图中的堆中分配一块需要连续内存占用 4M 或 5M 的区域显然是会失败怎么解决呢如果能把上面未使用的 2M 2M1M 内存能连起来就能连成一片可用空间为 5M 的区域即可怎么做呢?复制算法把堆等分成两块区域, A 和 B区域 A 负责分配对象区域 B 不分配, 对区域 A 使用以上所说的标记法把存活的对象标记出来下图有误无需清除然后把区域 A 中存活的对象都复制到区域 B存活对象都依次紧邻排列最后把 A 区对象全部清理掉释放出空间这样就解决了内存碎片的问题了。不过复制算法的缺点很明显比如给堆分配了 500M 内存结果只有 250M 可用空间平白无故减少了一半这肯定是不能接受的另外每次回收也要把存活对象移动到另一半效率低下我们可以想想删除数组元素再把非删除的元素往一端移效率显然堪忧标记整理法前面两步和标记清除法一样不同的是它在标记清除法的基础上添加了一个整理的过程 即将所有的存活对象都往一端移动,紧邻排列如图示再清理掉另一端的所有区域这样的话就解决了内存碎片的问题。但是缺点也很明显每进一次垃圾清除都要频繁地移动存活的对象效率十分低下。分代收集算法分代收集算法整合了以上算法综合了这些算法的优点最大程度避免了它们的缺点所以是现代虚拟机采用的首选算法,与其说它是算法倒不是说它是一种策略因为它是把上述几种算法整合在了一起为啥需要分代收集呢来看一下对象的分配有啥规律如图示纵轴代表已分配的字节而横轴代表程序运行时间由图可知大部分的对象都很短命都在很短的时间内都被回收了IBM 专业研究表明一般来说98% 的对象都是朝生夕死的经过一次 Minor GC 后就会被回收所以分代收集算法根据对象存活周期的不同将堆分成新生代和老生代Java8以前还有个永久代,默认比例为 1 : 2新生代又分为 Eden 区 from Survivor 区简称S0to Survivor 区(简称 S1),三者的比例为 8: 1 : 1这样就可以根据新老生代的特点选择最合适的垃圾回收算法我们把新生代发生的 GC 称为 Young GC也叫 Minor GC,老年代发生的 GC 称为 Old GC也称为 Full GC。画外音思考一下新生代为啥要分这么多区那么分代垃圾收集是怎么工作的呢我们一起来看看分代收集工作原理1、对象在新生代的分配与回收由以上的分析可知大部分对象在很短的时间内都会被回收对象一般分配在 Eden 区当 Eden 区将满时触发 Minor GC我们之前怎么说来着大部分对象在短时间内都会被回收, 所以经过 Minor GC 后只有少部分对象会存活它们会被移到 S0 区这就是为啥空间大小  Eden: S0: S1 8:1:1, Eden 区远大于 S0,S1 的原因因为在 Eden 区触发的 Minor GC 把大部对象接近98%都回收了,只留下少量存活的对象此时把它们移到 S0 或 S1 绰绰有余同时对象年龄加一对象的年龄即发生 Minor GC 的次数最后把 Eden 区对象全部清理以释放出空间,动图如下当触发下一次 Minor GC 时会把 Eden 区的存活对象和 S0或S1 中的存活对象S0 或 S1 中的存活对象经过每次 Minor GC 都可能被回收一起移到 S1Eden 和 S0 的存活对象年龄1, 同时清空 Eden 和 S0 的空间。若再触发下一次 Minor GC则重复上一步只不过此时变成了 从 EdenS1 区将存活对象复制到 S0 区,每次垃圾回收, S0, S1 角色互换都是从 Eden ,S0(或S1) 将存活对象移动到 S1(或S0)。也就是说在 Eden 区的垃圾回收我们采用的是复制算法因为在 Eden 区分配的对象大部分在 Minor GC 后都消亡了只剩下极少部分存活对象这也是为啥 Eden:S0:S1 默认为 8:1:1 的原因S0,S1 区域也比较小所以最大限度地降低了复制算法造成的对象频繁拷贝带来的开销。2、对象何时晋升老年代当对象的年龄达到了我们设定的阈值则会从S0或S1晋升到老年代如图示年龄阈值设置为 15 当发生下一次 Minor GC 时S0 中有个对象年龄达到 15达到我们的设定阈值晋升到老年代大对象 当某个对象分配需要大量的连续内存时此时对象的创建不会分配在 Eden 区会直接分配在老年代因为如果把大对象分配在 Eden 区, Minor GC 后再移动到 S0,S1 会有很大的开销对象比较大复制会比较慢也占空间也很快会占满 S0,S1 区所以干脆就直接移到老年代.还有一种情况也会让对象晋升到老年代即在 S0或S1 区相同年龄的对象大小之和大于 S0或S1空间一半以上时则年龄大于等于该年龄的对象也会晋升到老年代。3、空间分配担保在发生 MinorGC 之前虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间如果大于那么Minor GC 可以确保是安全的,如果不大于那么虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。如果允许那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小如果大于则进行 Minor GC否则可能进行一次 Full GC。4、Stop The World如果老年代满了会触发 Full GC, Full GC 会同时回收新生代和老年代即对整个堆进行GC它会导致 Stop The World简称 STW,造成挺大的性能开销。什么是 STW 所谓的 STW, 即在 GCminor GC 或 Full GC期间只有垃圾回收器线程在工作其他工作线程则被挂起。画外音为啥在垃圾收集期间其他工作线程会被挂起想象一下你一边在收垃圾另外一群人一边丢垃圾垃圾能收拾干净吗。一般 Full GC 会导致工作线程停顿时间过长因为Full GC 会清理整个堆中的不可用对象一般要花较长的时间如果在此 server 收到了很多请求则会被拒绝服务所以我们要尽量减少 Full GCMinor GC 也会造成 STW,但只会触发轻微的 STW,因为 Eden 区的对象大部分都被回收了只有极少数存活对象会通过复制算法转移到 S0 或 S1 区所以相对还好。现在我们应该明白把新生代设置成 Eden, S0S1区或者给对象设置年龄阈值或者默认把新生代与老年代的空间大小设置成 1:2 都是为了尽可能地避免对象过早地进入老年代尽可能晚地触发 Full GC。想想新生代如果只设置 Eden 会发生什么后果就是每经过一次 Minor GC存活对象会过早地进入老年代那么老年代很快就会装满很快会触发 Full GC而对象其实在经过两三次的 Minor GC 后大部分都会消亡所以有了 S0,S1的缓冲只有少数的对象会进入老年代老年代大小也就不会这么快地增长也就避免了过早地触发 Full GC。由于 Full GC或Minor GC 会影响性能所以我们要在一个合适的时间点发起 GC这个时间点被称为 Safe Point这个时间点的选定既不能太少以让 GC 时间太长导致程序过长时间卡顿也不能过于频繁以至于过分增大运行时的负荷。一般当线程在这个时间点上状态是可以确定的如确定 GC Root 的信息等可以使 JVM 开始安全地 GC。Safe Point 主要指的是以下特定位置循环的末尾方法返回前调用方法的 call 之后抛出异常的位置 另外需要注意的是由于新生代的特点大部分对象经过 Minor GC后会消亡 Minor GC 用的是复制算法而在老生代由于对象比较多占用的空间较大使用复制算法会有较大开销复制算法在对象存活率较高时要进行多次复制操作同时浪费一半空间所以根据老生代特点在老年代进行的 GC 一般采用的是标记整理法来进行回收。垃圾收集器种类 如果说收集算法是内存回收的方法论那么垃圾收集器就是内存回收的具体实现。Java 虚拟机规范并没有规定垃圾收集器应该如何实现因此一般来说不同厂商不同版本的虚拟机提供的垃圾收集器实现可能会有差别一般会给出参数来让用户根据应用的特点来组合各个年代使用的收集器主要有以下垃圾收集器在新生代工作的垃圾回收器Serial, ParNew, ParallelScavenge在老年代工作的垃圾回收器CMSSerial Old, Parallel Old同时在新老生代工作的垃圾回收器G1图片中的垃圾收集器如果存在连线则代表它们之间可以配合使用接下来我们来看看各个垃圾收集器的具体功能。新生代收集器Serial 收集器Serial 收集器是工作在新生代的单线程的垃圾收集器单线程意味着它只会使用一个 CPU 或一个收集线程来完成垃圾回收不仅如此还记得我们上文提到的 STW 了吗它在进行垃圾收集时其他用户线程会暂停直到垃圾收集结束也就是说在 GC 期间此时的应用不可用。看起来单线程垃圾收集器不太实用不过我们需要知道的任何技术的使用都不能脱离场景在 Client 模式下它简单有效与其他收集器的单线程比对于限定单个 CPU 的环境来说Serial 单线程模式无需与其他线程交互减少了开销专心做 GC 能将其单线程的优势发挥到极致另外在用户的桌面应用场景分配给虚拟机的内存一般不会很大收集几十甚至一两百兆仅是新生代的内存桌面应用基本不会再大了STW 时间可以控制在一百多毫秒内只要不是频繁发生这点停顿是可以接受的所以对于运行在 Client 模式下的虚拟机Serial 收集器是新生代的默认收集器ParNew 收集器ParNew 收集器是 Serial 收集器的多线程版本除了使用多线程其他像收集算法,STW,对象分配规则回收策略与 Serial 收集器完成一样在底层上这两种收集器也共用了相当多的代码它的垃圾收集过程如下ParNew 主要工作在 Server 模式我们知道服务端如果接收的请求多了响应时间就很重要了多线程可以让垃圾回收得更快也就是减少了 STW 时间能提升响应时间所以是许多运行在 Server 模式下的虚拟机的首选新生代收集器另一个与性能无关的原因是因为除了 Serial  收集器只有它能与 CMS 收集器配合工作CMS 是一个划时代的垃圾收集器是真正意义上的并发收集器它第一次实现了垃圾收集线程与用户线程基本上同时工作它采用的是传统的 GC 收集器代码框架与 Serial,ParNew 共用一套代码框架所以能与这两者一起配合工作而后文提到的 Parallel Scavenge 与 G1 收集器没有使用传统的 GC 收集器代码框架而是另起炉灶独立实现的另外一些收集器则只是共用了部分的框架代码,所以无法与 CMS 收集器一起配合工作。在多 CPU 的情况下由于 ParNew 的多线程回收特性毫无疑问垃圾收集会更快也能有效地减少 STW 的时间提升应用的响应速度。Parallel Scavenge 收集器Parallel Scavenge 收集器也是一个使用复制算法多线程工作于新生代的垃圾收集器看起来功能和 ParNew 收集器一样它有啥特别之处吗关注点不同CMS 等垃圾收集器关注的是尽可能缩短垃圾收集时用户线程的停顿时间而 Parallel Scavenge 目标是达到一个可控制的吞吐量吞吐量 运行用户代码时间 / 运行用户代码时间垃圾收集时间也就是说 CMS 等垃圾收集器更适合用到与用户交互的程序因为停顿时间越短用户体验越好而 Parallel Scavenge 收集器关注的是吞吐量所以更适合做后台运算等不需要太多用户交互的任务。Parallel Scavenge 收集器提供了两个参数来精确控制吞吐量分别是控制最大垃圾收集时间的 -XX:MaxGCPauseMillis 参数及直接设置吞吐量大小的 -XX:GCTimeRatio默认99%除了以上两个参数还可以用 Parallel Scavenge 收集器提供的第三个参数 -XX:UseAdaptiveSizePolicy开启这个参数后就不需要手工指定新生代大小,Eden 与 Survivor 比例SurvivorRatio等细节只需要设置好基本的堆大小-Xmx 设置最大堆,以及最大垃圾收集时间与吞吐量大小虚拟机就会根据当前系统运行情况收集监控信息动态调整这些参数以尽可能地达到我们设定的最大垃圾收集时间或吞吐量大小这两个指标。自适应策略也是 Parallel Scavenge  与 ParNew 的重要区别老年代收集器Serial Old 收集器上文我们知道 Serial 收集器是工作于新生代的单线程收集器与之相对地Serial Old 是工作于老年代的单线程收集器此收集器的主要意义在于给 Client 模式下的虚拟机使用如果在 Server 模式下则它还有两大用途一种是在 JDK 1.5 及之前的版本中与 Parallel Scavenge 配合使用另一种是作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用后文讲述,它与 Serial 收集器配合使用示意图如下Parallel Old 收集器Parallel Old 是相对于 Parallel Scavenge 收集器的老年代版本使用多线程和标记整理法两者组合示意图如下,这两者的组合由于都是多线程收集器真正实现了「吞吐量优先」的目标CMS 收集器CMS 收集器是以实现最短 STW 时间为目标的收集器如果应用很重视服务的响应速度希望给用户最好的体验则 CMS 收集器是个很不错的选择我们之前说老年代主要用标记整理法而 CMS 虽然工作于老年代但采用的是标记清除法主要有以下四个步骤初始标记并发标记重新标记并发清除从图中可以的看到初始标记和重新标记两个阶段会发生 STW造成用户线程挂起不过初始标记仅标记 GC Roots 能关联的对象速度很快并发标记是进行 GC Roots  Tracing 的过程重新标记是为了修正并发标记期间因用户线程继续运行而导致标记产生变动的那一部分对象的标记记录这一阶段停顿时间一般比初始标记阶段稍长但远比并发标记时间短。整个过程中耗时最长的是并发标记和标记清理不过这两个阶段用户线程都可工作所以不影响应用的正常使用所以总体上看可以认为 CMS 收集器的内存回收过程是与用户线程一起并发执行的。但是 CMS 收集器远达不到完美的程度主要有以下三个缺点CMS 收集器对 CPU 资源非常敏感  原因也可以理解比如本来我本来可以有 10 个用户线程处理请求现在却要分出 3 个作为回收线程吞吐量下降了30%CMS 默认启动的回收线程数是 CPU数量3/ 4, 如果 CPU 数量只有一两个那吞吐量就直接下降 50%,显然是不可接受的CMS 无法处理浮动垃圾Floating Garbage,可能出现 「Concurrent Mode Failure」而导致另一次 Full GC 的产生由于在并发清理阶段用户线程还在运行所以清理的同时新的垃圾也在不断出现这部分垃圾只能在下一次 GC 时再清理掉即浮云垃圾同时在垃圾收集阶段用户线程也要继续运行就需要预留足够多的空间要确保用户线程正常执行这就意味着 CMS 收集器不能像其他收集器一样等老年代满了再使用JDK 1.5 默认当老年代使用了68%空间后就会被激活当然这个比例可以通过 -XX:CMSInitiatingOccupancyFraction 来设置但是如果设置地太高很容易导致在 CMS 运行期间预留的内存无法满足程序要求会导致 Concurrent Mode Failure 失败这时会启用 Serial Old 收集器来重新进行老年代的收集而我们知道 Serial Old 收集器是单线程收集器这样就会导致 STW 更长了。CMS 采用的是标记清除法上文我们已经提到这种方法会产生大量的内存碎片这样会给大内存分配带来很大的麻烦如果无法找到足够大的连续空间来分配对象将会触发 Full GC这会影响应用的性能。当然我们可以开启 -XX:UseCMSCompactAtFullCollection默认是开启的用于在 CMS 收集器顶不住要进行 FullGC 时开启内存碎片的合并整理过程内存整理会导致 STW停顿时间会变长还可以用另一个参数 -XX:CMSFullGCsBeforeCompation 用来设置执行多少次不压缩的 Full GC 后跟着带来一次带压缩的。G1Garbage First 收集器G1 收集器是面向服务端的垃圾收集器被称为驾驭一切的垃圾回收器主要有以下几个特点像 CMS 收集器一样能与应用程序线程并发执行。整理空闲空间更快。需要 GC 停顿时间更好预测。不会像 CMS 那样牺牲大量的吞吐性能。不需要更大的 Java Heap与 CMS 相比它在以下两个方面表现更出色运作期间不会产生内存碎片G1 从整体上看采用的是标记-整理法局部两个 Region上看是基于复制算法实现的两个算法都不会产生内存碎片收集后提供规整的可用内存这样有利于程序的长时间运行。在 STW 上建立了可预测的停顿时间模型用户可以指定期望停顿时间G1 会将停顿时间控制在用户设定的停顿时间以内。为什么G1能建立可预测的停顿模型呢主要原因在于 G1 对堆空间的分配与传统的垃圾收集器不一器传统的内存分配就像我们前文所述是连续的分成新生代老年代新生代又分 Eden,S0,S1,如下而 G1 各代的存储地址不是连续的每一代都使用了 n 个不连续的大小相同的 Region每个Region占有一块连续的虚拟内存地址如图示除了和传统的新老生代幸存区的空间区别Region还多了一个H它代表Humongous这表示这些Region存储的是巨大对象humongous objectH-obj即大小大于等于region一半的对象这样超大对象就直接分配到了老年代防止了反复拷贝移动。那么 G1 分配成这样有啥好处呢传统的收集器如果发生 Full GC 是对整个堆进行全区域的垃圾收集而分配成各个 Region 的话方便 G1 跟踪各个 Region 里垃圾堆积的价值大小回收所获得的空间大小及回收所需经验值这样根据价值大小维护一个优先列表根据允许的收集时间优先收集回收价值最大的 Region,也就避免了整个老年代的回收也就减少了 STW 造成的停顿时间。同时由于只收集部分 Region,可就做到了 STW 时间的可控。G1 收集器的工作步骤如下初始标记并发标记最终标记筛选回收可以看到整体过程与 CMS 收集器非常类似筛选阶段会根据各个 Region 的回收价值和成本进行排序根据用户期望的 GC 停顿时间来制定回收计划。总结 本文简述了垃圾回收的原理与垃圾收集器的种类相信大家对开头提的一些问题应该有了更深刻的认识在生产环境中我们要根据不同的场景来选择垃圾收集器组合如果是运行在桌面环境处于 Client 模式的则用 Serial Serial Old 收集器绰绰有余如果需要响应时间快用户体验好的则用 ParNew CMS 的搭配模式即使是号称是「驾驭一切」的 G1也需要根据吞吐量等要求适当调整相应的 JVM 参数没有最牛的技术只有最合适的使用场景切记理论有了下一篇我们会进入手动操作环节我们会一起来动手操作一些 demo做一些实验来验证我们看到的一些现象比如对象一般分配在新生代什么情况下会直接到老年代该怎么实验发生了OOM该用哪些工具调试呢等等敬请期待参考堆外内存的回收机制分析 https://www.jianshu.com/p/35cf0f348275 java调用本地方法--jni简介 https://blog.csdn.net/w1992wishes/article/details/80283403 咱们从头到尾说一次 Java 垃圾回收 https://mp.weixin.qq.com/s/pR7U1OTwsNSg5fRyWafucA 深入理解 Java 虚拟机 Java Hotspot G1 GC的一些关键技术 https://tech.meituan.com/2016/09/23/g1.html 往期推荐 千万不要这样写代码9种常见的OOM场景演示腾讯推出高性能 RPC 开发框架Java中竟有18种队列45张图安排关注我每天陪你进步一点点
http://mrfarshtey.net/news/4572/

相关文章:

  • 申请网址的网站怎么做网站赚流量
  • 英文网站建设运营短视频运营基础知识
  • 注册网站显示lp或设备超限怎么办wordpress制作数据排行榜
  • 网站开发毕设论文建设网站免费模板
  • 深圳哪里可以做网站网页界面设计的要求是什么
  • 佛山优化企业网站排名平台十大互联网培训机构
  • 网站建设服务费的税率公司网站建设设计如何收费
  • 网站开发个人技能建设工程安全管理网站
  • 建一个pc网站需要多少钱如何查看网站名称
  • 东莞市建设工程网站wordpress 手机版域名
  • 做那个的网站谁有网页游戏排行榜第一
  • 网站运营的主要内容如何做局域网网站建设
  • 丹东做网站哪家好soho网站建设教程
  • 做网站设计公司价格中国楼市最新消息2022
  • 做银行流水网站专业建设计划
  • 不买域名怎么做网站wordpress内容采集
  • 专业的设计网站起重机网站怎么做
  • 网站整体设计意图及其功能crm系统软件排名
  • 地名网站安全建设方案什么样企业需要网站建设
  • 淘宝网网站设计分析网站色彩的搭配原则有哪些
  • 河南网站建设平台门店管理系统免费版
  • 内乡微网站开发网站建设ssc源码修复
  • 重要新闻事件seo排名优化哪里好
  • 网站建设公司首选瑞昌市建设局网站
  • 网站模板炫酷苏州工程网站建设
  • 珠海建设局网站网站集约化建设的总体情况
  • 怎么用editplus做网站做软装设计找图有什么好的网站
  • 怎么修改网站关键词天津专门做企业网站公司
  • 网络推广营销策略新人学会seo
  • 网站对联代码div设计素材网站飘