Java性能权威指南-总结5

  • 垃圾收集入门
    • 垃圾收集概述
      • 分代垃圾收集器

垃圾收集入门

很多时候没有机会重写代码,又面临需要提高Java应用性能的压力,这种情况下对垃圾收集器的调优就变得至关重要。

现代JVM的类型繁多,最主流的四个垃圾收集器分别是:Serial收集器(常用于单CPU环境)、Throughput(或者Parallel)收集器、Concurrent收集器(CMS)和G1收集器。虽然它们的性能特征迥异,不过它们也有不少共性。

垃圾收集概述

Java特性之一是不需要显式地管理对象的生命周期:可以在需要时创建对象,对象不再被使用时,会由JVM在后台自动进行回收。这其实是一把双刃剑,如果花费大量的时间来优化Java程序的内存使用,这套机制看起来可能更像个缺点,而非其优点。

简单来说,垃圾收集由两步构成:查找不再使用的对象,以及释放这些对象所管理的内存。JVM从查找不再使用的对象(垃圾对象)入手。有时,这也被称为查找不再有任何对象引用的对象(暗指采用“引用计数”的方式统计对象引用)。然而,这种靠引用计数的方式不太靠谱:假设有一个对象链接列表,列表中的每一个对象(除了头节点)都指向列表中的另一个对象,但是,如果没有任何一个引用指向列表头,这个列表就没人使用,可以被垃圾回收器回收。如果这是一个循环列表(即列表的尾元素反过来又指向了头元素),那么列表中的每一个元素都包含一个引用,即使这个列表内没有任何一个对象实际被使用,因为没有任何一个对象指向这个列表。

所以引用是无法通过计数的方式动态跟踪的;JVM必须定期地扫描堆来查找不再使用的对象。一旦发现垃圾对象,JVM会回收这些对象所持有的内存,把它们分配给需要内存的其他对象。然而,简单地记录空闲内存也无法保证将来有足够的可用内存,有些时候,还必须进行内存整理来防止内存碎片。

假设以下场景,一个程序需要分配大小为1000字节的数组,紧接着又分配一个大小为24字节的数组,并在一个循环中持续进行这样的分配。最终程序会耗尽整个堆,结果如下图所示:

堆内存用尽会触发JVM回收不再使用的数组空间。假设所有大小为24字节的数组都不再被使用,而大小为1000字节的数组还继续使用,这就形成了上图中第二行的场景。虽然堆内部有足够的空闲空间,却找不到任何一个大于24字节的连续空间,除非JVM移动所有大小为1000字节的数组,让它们连续存储,把空闲的空间整合成一块更大的连续空间,供其他的内存分配使用(如上图的第三行)。

深入到这些具体实现似乎太过于琐屑,但垃圾收集的性能就是由这些基本操作所决定的:**找到不再使用的对象、回收它们使用的内存、对堆的内存布局进行压缩整理。**完成这些操作时不同的收集器采用了不同的方法,这也是不同垃圾收集器表现出不同性能特征的原因。

如果垃圾收集进行时,没有任何应用程序线程在运行,那么完成这些操作将是一件轻松愉快的事情。然而实际情况要复杂得多,通常Java程序都启动了大量的线程,垃圾收集器自身往往也是多线程的。接下来的讨论中,从逻辑上将线程分成两组,分别是应用程序线程和处理垃圾收集的线程。垃圾收集器回收对象,或者在内存中移动对象时,必须确保应用程序线程不再继续使用这些对象。 这一点在收集器移动对象时尤其重要:在操作过程中,对象的内存地址会发生变化,因此这个过程中任何应用线程都不应再访问该对象。

所有应用线程都停止运行所产生的停顿被称为时空停顿(stop-the-world)。通常这些停顿对应用的性能影响最大,调优垃圾收集时,尽量减少这种停顿是最为关键的考量因素。

分代垃圾收集器

虽然实现的细节千差万别,但所有的垃圾收集器都遵循了同一个方式,即根据情况将堆划分成不同的代(Generation)。这些代被称为“老年代”(Old Generation或TenuredGeneration)和“新生代”(Young Generation)。新生代又被进一步地划分为不同的区段,分别称为Eden空间和Survivor空间(不过Eden有时会被错误地用于指代整个新生代)。

采用分代机制的原因是很多对象的生存时间非常短。譬如下面这个例子,这是一个计算股价的循环,它将股价与股票均价的差值进行乘方运算,然后将结果加和(作为标准偏差计算的一部分)。

 sum = new BigDecimal(0);for (StockPrice sp: prices.values()) {BigDecimal diff = sp.getClosingPrice().subtract(averagePrice);diff = diff.multiply(diff);sum = sum.add(diff);}

BigDecimal同许多Java类一样是不可变对象:该对象代表的是一个不能修改的数字。运算使用这个对象时,会创建一个新的对象(通常,前一个对象及其值会被丢弃)。这个简单的循环处理一年的股票数据(大约250个循环)时,为了存储循环的中间值,会创建750个BigDecimal对象,只是在这一个循环里。这些对象在循环的下一个周期开始时会被丢弃。在add()以及其他方法内,JDK的库方法会创建更多类似BigDecimal(以及其他的类)的中间对象。最终,在一小段代码中大量的对象被快速地创建和丢弃。

Java中,这种操作是非常普遍的,所以垃圾收集器设计时就特别考虑要处理大量(有时候是大多数)的临时对象。这也是分代设计的初衷之一。新生代是堆的一部分,对象首先在新生代中分配。新生代填满时,垃圾收集器会暂停所有的应用线程,回收新生代空间。不再使用的对象会被回收,仍然在使用的对象会被移动到其他地方。这种操作被称为Minor GC。

采用这种设计有两个性能上的优势。其一,由于新生代仅是堆的一部分,与处理整个堆相比,处理新生代的速度更快。而这意味着应用线程停顿的时间会更短。这意味着应用程序线程会更频繁地发生停顿,因为JVM不再等到整个堆都填满才进行垃圾收集。然而,就目前而言,更短的停顿显然能带来更多的优势,即使发生的频率更高。

第二个优势源于新生代中对象分配的方式。对象分配于Eden空间(占据了新生代空间的绝大多数)。**垃圾收集时,新生代空间被清空,Eden空间中的对象要么被移走,要么被回收;所有的存活对象要么被移动到另一个Survivor空间,要么被移动到老年代。由于所有的对象都被移走,相当于新生代空间在垃圾收集时自动地进行了一次压缩整理。**所有的垃圾收集算法在对新生代进行垃圾回收时都存在“时空停顿”现象。

对象不断地被移动到老年代,最终老年代也会被填满,JVM需要找出老年代中不再使用的对象,并对它们进行回收。这便是垃圾收集算法差异最大的地方。简单的垃圾收集算法直接停掉所有的应用线程,找出不再使用的对象,对其进行回收,接着对堆空间进行整理。 这个过程被称为FulI GC,通常导致应用程序线程长时间的停顿。

另一方面,通过更复杂的计算,还有可能在应用线程运行的同时找出不再使用的对象;CMS和G1收集器就是通过这种方式进行垃圾收集的。由于不需要停止应用线程就能找出不再用的对象,CMS和G1收集器被称为Concurrent垃圾收集器。同时,由于它们将停止应用程序的可能降到了最小,也被称为低停顿(Low-Pause)收集器(有时也称为无停顿收集器,虽然这个叫法相当不确切)。Concurrent收集器也使用各种不同的方法对老年代空间进行压缩。

使用CMS或G1收集器时,应用程序经历的停顿会更少(也更短)。其代价是应用程序会消耗更多的CPU。 CMS和G1收集也可能遭遇长时间的Full GC停顿(尽量避免发生那样的停顿是这些调优算法要考虑的重要方面)。

评估垃圾收集器时,想想需要达到的整体性能目标。每一个决定都需要权衡取舍。如果应用对单个请求的响应时间有要求(譬如Java企业版服务器),应该考虑下面这些因素:

  • 单个请求会受停顿时间的影响——不过其受Full GC长时间停顿的影响更大。如果目标是要尽可能地缩短响应时间,那么选择使用Concurrent收集器更合适。
  • 如果平均响应时间比最大响应时间更重要(譬如90%的响应时间),采用Throughput收集器通常就能满足要求。

使用Concurrent收集器来避免长的停顿时间也有其代价,这会消耗额外的CPU。类似地,为批量应用选择垃圾收集器可以遵循下面的原则:

  • 如果CPU足够强劲,使用Concurrent收集器避免发生Full GC停顿可以让任务运行得更快。
  • 如果CPU有限,那么Concurrent收集器额外的CPU消耗会让批量任务消耗更多的时间。

快速小结

  1. 所有的GC算法都将堆划分成了老年代和新生代。
  2. 所有的GC算法在清理新生代对象时,都使用了“时空停顿”(stop-the-world)方式的垃圾收集方法,通常这是一个能较快完成的操作。

Java性能权威指南-总结5相关推荐

  1. java权威指南电子书下载,Java性能权威指南pdf

    市面上介绍Java的书有很多,但专注于Java性能的并不多,能游刃有余地展示Java性能优化难点的更是凤毛麟角,本书即是其中之一.通过使用JVM和Java平台,以及Java语言和应用程序接口,本书详尽 ...

  2. java高性能反射框架_终于有人把性能优化讲清楚了!阿里架构师推荐的Java性能权威指南可太强了...

    Java给大部分人的感觉就是慢,有严重的性能问题.其实程序慢的问题,与语言无关,与Java无关.Java应用的性能优化也是一个老生常谈的话题,但是只要我们深入的了解性能调优方法,走遍天下都不怕! 大多 ...

  3. java性能权威指南中文_Java性能权威指南读书笔记--之一

    JIT(即时编译) 解释型代码:程序可移植,相同的代码在任何有适当解释器的机器上,都能运行,但是速度慢. 编译型代码:速度快,电视不同CPU平台的代码无法兼容. java则是使用java的编译器先将其 ...

  4. 《Web性能权威指南》笔记

    序言 最近因为过生日,公司可以替每个过生日的员工买本书,我选择了这本<Web性能权威指南>,因为我觉得作为一个Web开发者,没有系统的学习过一本Web相关的书籍,大部分都是Java相关书籍 ...

  5. Java性能优化指南,及唯品会的实战

    来了唯品会一年多,不少时间花在与服务化框架.业务应用的性能的缠斗上. 前几天正好趁着中生代社区的十月十城技术沙龙,把脑海中关于性能优化的记忆全部理了一遍-.讲完回家,又本着认真严谨的态度再理了一遍,终 ...

  6. Web性能权威指南 PDF扫描版​

    Web性能权威指南是谷歌公司高性能团队核心成员的权威之作,堪称实战经验与规范解读完美结合的产物.<Web性能权威指南>目标是涵盖Web开发者技术体系中应该掌握的所有网络及性能优化知识.全书 ...

  7. java每秒执行一次_Java性能权威指南

    [2020年5月15日] 开坑.左右看看,可能这本书更加适合作为休息读物,不要求连续花长时间去理解内容,更加偏实践. 书中示例代码:https://github.com/scottoaks/javap ...

  8. Java性能优化指南(一)

    2015年在大物流项目中,给项目团队做了几次Java性能优化和问题排查的分享,不过效果都不是很好.一直觉得偏向技术实践类的东西,单纯的听和单纯的讲收获都很有限,最好的做法是阅读学习-理解-实践-总结, ...

  9. java ee7权威指南 卷1,JavaEE7权威指南,卷1(原书第5版)中文pdf

    资源名称:Java EE 7权威指南:卷1(原书第5版) 中文pdf 第一部分 引言 第1章 概述 2 第2章 使用教程示例 27 第二部分 平台基础知识 第3章 资源创建 38 第4章 注入 41 ...

最新文章

  1. 把mysql 中的字符gb2312 改为gbk的方法
  2. 引用次数在 19000 次+的,都是什么神仙论文?
  3. QTableView中使用Delegate方式来实现对特定列的文本进行换行
  4. 人工神经网络-2020-第十四周-人工神经网络硬件实现-备课
  5. 自己动手写C语言编译器(5)
  6. docker自动化部署
  7. 各种环境下的渗透测试
  8. [react] 你有使用过loadable组件吗?它帮我们解决了什么问题?
  9. 孜然导航系统 v2.3
  10. 对一次通过CISSP考试的建议
  11. 什么标签用于在表单中构建复选框_基础表单标签及属性
  12. 超清晰的 DNS 原理入门指南 (资源)
  13. 网站防盗链就是那么简单
  14. 洛谷1309 瑞士轮 解题报告
  15. System Verilog 语法1
  16. Easy Mock全解及使用
  17. 分享:刚入行的朋友如何找到程序员工作,并成为优秀游戏程序员?
  18. 【CV】第 16 章:结合计算机视觉和强化学习
  19. 对接阿里云短信服务(附视频教程)
  20. java学习之服务器第28天( --jsp--三个指令--六个动作标签--PageContext域--EL表达式--)

热门文章

  1. mysql 学生信息管理系统
  2. 哈密尔顿回路和哈密尔顿路径
  3. PHP+Mysql—白酒管理系统(前端+后端)
  4. ddr最大工作频率 xc7z020_米尔科技XC7Z020开发板介绍
  5. html中加个有颜色横线,关于html:更改下划线颜色
  6. SPSS计算极值、平均值、中位数、方差、偏度、峰度、变异系数
  7. HTTPS-自己生成数字证书
  8. 丰润达S400来了!5.8G千兆无线网桥“横空出世”
  9. Git clone 与 Git Fork 的不同(Difference between Git Clone and Git Fork)
  10. cocos 龙骨动画导出为spine 格式并播放