Java性能调优实战
刘超
金山软件西山居技术经理
立即订阅
7535 人已学习
课程目录
已完结 48 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)
01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?
模块二 · Java编程性能调优 (10讲)
03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍
加餐 | 推荐几款常用的性能测试工具
06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理
模块三 · 多线程性能调优 (10讲)
12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?
20 | 答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?
模块四 · JVM性能监测及调优 (6讲)
21 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
22 | 深入JVM即时编译器JIT,优化Java编译
23 | 如何优化垃圾回收机制?
24 | 如何优化JVM内存分配?
25 | 内存持续上升,我该如何排查问题?
26 | 答疑课堂:模块四热点问题解答
模块五 · 设计模式调优 (6讲)
27 | 单例模式:如何创建单一对象优化系统性能?
28 | 原型模式与享元模式:提升系统性能的利器
29 | 如何使用设计模式优化并发编程?
30 | 生产者消费者模式:电商库存设计优化
31 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
32 | 答疑课堂:模块五思考题集锦
模块六 · 数据库性能调优 (8讲)
33 | MySQL调优之SQL语句:如何写出高性能SQL语句?
34 | MySQL调优之事务:高并发场景下的数据库事务调优
35 | MySQL调优之索引:索引的失效与优化
36 | 记一次线上SQL死锁事故:如何避免死锁?
37 | 什么时候需要分表分库?
38 | 电商系统表设计优化案例分析
39 | 数据库参数设置优化,失之毫厘差之千里
40 | 答疑课堂:MySQL中InnoDB的知识点串讲
模块七 · 实战演练场 (4讲)
41 | 如何设计更优的分布式锁?
42 | 电商系统的分布式事务调优
43 | 如何使用缓存优化系统性能?
44 | 记一次双十一抢购性能瓶颈调优
结束语 (1讲)
结束语 | 栉风沐雨,砥砺前行!
Java性能调优实战
登录|注册

23 | 如何优化垃圾回收机制?

刘超 2019-07-13
你好,我是刘超。
我们知道,在 Java 开发中,开发人员是无需过度关注对象的回收与释放的,JVM 的垃圾回收机制可以减轻不少工作量。但完全交由 JVM 回收对象,也会增加回收性能的不确定性。在一些特殊的业务场景下,不合适的垃圾回收算法以及策略,都有可能导致系统性能下降。
面对不同的业务场景,垃圾回收的调优策略也不一样。例如,在对内存要求苛刻的情况下,需要提高对象的回收效率;在 CPU 使用率高的情况下,需要降低高并发时垃圾回收的频率。可以说,垃圾回收的调优是一项必备技能。
这讲我们就把这项技能的学习进行拆分,看看回收(后面简称 GC)的算法有哪些,体现 GC 算法好坏的指标有哪些,又如何根据自己的业务场景对 GC 策略进行调优?

垃圾回收机制

掌握 GC 算法之前,我们需要先弄清楚 3 个问题。第一,回收发生在哪里?第二,对象在什么时候可以被回收?第三,如何回收这些对象?

1. 回收发生在哪里?

JVM 的内存区域中,程序计数器、虚拟机栈和本地方法栈这 3 个区域是线程私有的,随着线程的创建而创建,销毁而销毁;栈中的栈帧随着方法的进入和退出进行入栈和出栈操作,每个栈帧中分配多少内存基本是在类结构确定下来的时候就已知的,因此这三个区域的内存分配和回收都具有确定性。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(21)

  • Jxin
    1.7及前的都还好说,毕竟大部分开发都拜读过<深入理解jvm>。
    回归整体,记得有点模糊了,如果有错误还请老师指正。首先cms在1.9已经被标记为废弃,主要原因在于标记清除下的悬浮内存,导致内存空间碎片化,进而导致fullGC的发生。不过其并行执行垃圾回收的性能还是值得认可的,至少1.9后主推的G1在常规情况下也是不如它的效率好的。接下来,说下G1,拼G1的堆内存结构比较特殊,虽然也有年代划分,但从物理角度上却不一样。G1将整块内存分配成若干个同等大小的reg。新生代(两个sub区加ed区)和老年代各自由不同数量的reg组成。垃圾回收的算法应该算是标记整理。所以其规避了cms内存碎片化的问题,大大降低了fullGC的频率。所以它虽然常态性能略输于cms但却没有cms特殊情况下的极端性能问题,总体更稳定。值得一提的是G1中各代的内存区域里reg间不一定是连续的,所以对于cpu缓存加载机制并不是特别友好,而且大对象占据超过一个reg时还带来内存浪费的问题。所以总的来说1.8可以用G1但得考虑场景,首先这个内存空间要大,保证每个reg尽量大,以减少内存浪费,保守估计8g以上用g1。实际公司很少会去升级jdk版本,大部分都是1.8,好在oracle一些1.9 10 11 12的特性都有以补丁的方式落到1.8。所以1.8还是比较安全实用的,虽然我们公司还是1.7,推不动哈。

    作者回复: 赞,Region这块 Jxin讲解的通俗易懂。

    2019-07-13
    19
  • Liam
    1 minor gc是否会导致stop the world?
    2 major gc什么时候会发生,它和full gc的区别是什么?

    作者回复: Liam提出的这两个问题非常好。
    1、不管什么GC,都会发送stop the world,区别是发生的时间长短。而这个时间跟垃圾收集器又有关系,Serial、PartNew、Parallel Scavenge收集器无论是串行还是并行,都会挂起用户线程,而CMS和G1在并发标记时,是不会挂起用户线程,但其他时候一样会挂起用户线程,stop the world的时间相对来说小很多了。

    2、major gc很多参考资料指的是等价于full gc,我们也可以发现很多性能监测工具中只有minor gc和full gc。
    一般情况下,一次full gc将会对年轻代、老年代以及元空间、堆外内存进行垃圾回收。而触发Full GC的原因有很多:
    a、当年轻代晋升到老年代的对象大小比目前老年代剩余的空间大小还要大时,此时会触发Full GC;
    b、当老年代的空间使用率超过某阈值时,此时会触发Full GC;
    c、当元空间不足时(JDK1.7永久代不足),也会触发Full GC;
    d、当调用System.gc()也会安排一次Full GC;

    2019-07-14
    17
  • QQ怪
    G1与CMS的优势在于以下几点:
    1、并行与并发:G1能够更充分利用多CPU、多核环境运行
    2、分代收集:G1虽然也用了分代概念,但相比其他收集器需要配合不同收集协同工作,但G1收集器能够独立管理整个堆
    3、空间管理:与CMS的标记一清理算法不同,G1从整体上基于标记一整理算法,将整个Java堆划分为多个大小相等的独立区域(Region),这种算法能够在运行过程中不产生内存碎片
    4、可预测的停顿:降低停顿时间是G1和CMS共同目标,但是G1追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定一个长度为M毫秒的时间片段内,消耗在垃圾收集器上的时间不得超过N毫秒。

    作者回复: 赞。

    理解G1中的几个重要概念:Region、SATB、RSet以及Pause Prediction Model,能更好的理解G1相对CMS的一些具体优势在哪里了。


    2019-07-13
    1
    8
  • 别忘微笑
    超哥,一个web应用,多久一次Full GC才算正常呢

    作者回复: 需要根据具体的业务来分析,正常小对象且请求平缓的应用服务中,几天一次较为正常。如果有大量大对象创建或者承受高并发场景的服务,Full GC可能会更频繁。

    2019-07-15
    4
  • 我又不乱来
    超哥,我建议可以分享一下那些对象可以作为gc root的对象,为什么这些对象可以做为gc root对象?

    作者回复: 在Java语言里,可作为GC Root对象的包括如下几种: 1. Java虚拟机栈中的引用的对象 ; 2. 方法区中的类静态属性引用的对象 ; 3. 方法区中的常量引用的对象 ; 4. 本地方法栈中JNI的引用的对象。

    我们知道,垃圾回收一般是回收堆和方法区的对象,而堆中的对象在正常情况下,一般是通过常量、全局变量、静态变量等间接引用堆中的对象,所以这些可以作为GC Root。

    在任何上述的GCRoot中,有引用可以指向时,我们称之为对象可达。


    2019-07-13
    4
  • FelixFly
    在 JDK1.8 环境下,默认使用的是 Parallel Scavenge(年轻代)+Serial Old(老年代)垃圾收集器。老师,这个地方你写错了吧,用jinfo -flags 进程ID打印出-XX:+UseParallelGC是使用的这个,这个在官方文档说的是-XX:+UseParallelGC启用,-XX:+UseParallelOldGC这个会自动启用,应该为Parallel Scavenge(年轻代)+Parallel Old(老年代)垃圾收集器
    官网参数说明(查看的是linux下的)
    -XX:+UseParallelGC
    Enables the use of the parallel scavenge garbage collector (also known as the throughput collector) to improve the performance of your application by leveraging multiple processors.

    By default, this option is disabled and the collector is chosen automatically based on the configuration of the machine and type of the JVM. If it is enabled, then the -XX:+UseParallelOldGC option is automatically enabled, unless you explicitly disable it.
    2019-11-19
    2
    1
  • 发条橙子 。
    超哥 我想问下,相同的方法多次执行,再没有JIT编译的前提下,每一次执行都会进行一次解释执行莫?

    作者回复: 是的

    2019-07-24
    1
  • -W.LI-
    老师好!Serial Old不是标记整理算法么?Serial new是复制吧。我记得年轻代都是采用复制的,老年代除了CMS是标记清除(存在内存碎片)别的好像都是标记整理整理吧。

    作者回复: 是标记整理算法。

    2019-07-13
    1
  • nightmare
    老师看完有两个疑问,第一这么查看minor gc回收之后 eden区存活对象的多少,第二 jmap -heap pid在图中只能看年轻代parallel gc看不到老年代的是什么垃圾回收器 对于提问 cms垃圾回收器还是分老年代和年轻代回收分多个阶段有和程序并行的阶段也有stop the world阶段 回收一整块老年代时间比较久,而 gc把年轻代和老年代也有划分,不过拆成一个region了,对region的回收成本低,而且会判断那些region回收的对象更多,而且cms要经过多次full gc才可能把不用的内存归还给操作系统 而g1只需要一次full gc就可以

    作者回复: 我们可以通过jstat -gc pid interval查看每次GC之后,具体的每一个分区的内存使用率变化情况。我们可以通过查看JVM设置参数来查看具体的垃圾收集器的设置参数,使用的方式有很多,例如jcmd pid VM.flags可以查看到相关的设置参数。

    2019-07-13
    1
  • hogen
    清除(sweep)

    把死亡对象所占据的内存标记为空闲内存,并记录在一个空闲列表(free list)之中。当需要新建对象时,内存管理模块便会从该空闲列表中寻找空闲内存,并划分给新建的对象。

    清除这种回收方式的原理及其简单,但是有两个缺点。一是会造成内存碎片。由于 Java 虚拟机的堆中对象必须是连续分布的,因此可能出现总空闲内存足够,但是无法分配的极端情况。

     复制(copy)

    把内存区域分为两等分,分别用两个指针 from 和 to 来维护,并且只是用 from 指针指向的内存区域来分配内存。当发生垃圾回收时,便把存活的对象复制到 to 指针指向的内存区域中,并且交换 from 指针和 to 指针的内容。

    这种算法虽然实现简单,内存效率高,不易产生碎片,但是最大的问题是可用内存被压缩到了原本的一半。且存活对象增多的话,copy 算法的效率会大大降低。

     压缩(compact)

    把存活的对象聚集到内存区域的起始位置,从而留下一段连续的内存空间。这种做法能够解决内存碎片化的问题,但代价是压缩算法的性能开销。
    2019-12-10
    1
  • dingdongfm
    -XX:+UseParNewGC 1.8中有效。
    2019-11-27
  • godtrue
    很棒,很清晰的讲明白了什么是垃圾?有几种收集垃圾的方式?有几种具体的垃圾收集器?
    请问老师收集垃圾具体是一个什么原理?标记对应的内存可用了,还是将对应的数据都一个个清空了?

    作者回复: 标记可以回收的对象,然后在垃圾回收时将对象回收

    2019-09-11
  • 风轻扬
    老师。我翻阅了<深入理解java虚拟机>。里面提到了,Serial Old的老年代回收算法是:标记整理。不是标记清除。我查阅了oracle官网,看到的答案也是:mark-compact。应该就是标记整理

    作者回复: 是标记和整理的过程

    2019-09-08
  • 风轻扬
    老师。介绍这些垃圾回收器的官方网站,您有吗?

    作者回复: 可以进入Oracle官网查看技术文档。链接中只是一部分
    https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/index.html

    2019-09-06
  • 风轻扬
    老师,我今天监控了一下Idea的启动。然后把Idea启动的gc日志输出到log文件,上传到GC easy了。我年轻代设置的明明是-Xmn2048m,但是GC easy显示的年轻代还是之前的旧值1g,也就是1024。我又用visualVM监控了一下,visualVM显示的就是2048了,这是怎么回事呢?

    作者回复: 可以校验下参数是否配置正确,连接的是否是IDE进程。我们也可以打开gc日志查看具体的信息,不太可能是GC easy的问题。

    2019-09-05
  • 努力奋斗的Pisces
    G1已经没有分代可言了

    作者回复: 对的,在后面的答疑课堂中详细讲到了G1

    2019-08-22
  • K
    第二个问题就是,比如说我指定了:-XX:+UseConcMarkSweepGC,也指定了:-XX:+UseParallelOldGC,那么年轻代、老年代分别是用了什么垃圾回收器呢?麻烦老师解答一下,谢谢!

    作者回复: 我觉得前者会被后者覆盖,可以自己试试,然后通过指令查询相关的生效参数。

    2019-08-04
  • K
    老师好,我有两个问题想问一下。1.比如说看到jvm的参数,-XX:+UseConcMarkSweepGC,这个参数是单独指定了老年代的收集器呢,还是年轻代、老年代都指定了?

    作者回复: 运行XX:+UseConcMarkSweepGC命令,默认会指定年轻代和老年代的垃圾收集器,分别为ParNewGC和ConcMarkSweepGC两种收集器。

    2019-08-04
  • N
    老师您好,公司ES服务器设置最大最小堆内存26个G,G1GC, XX:MaxGCPauseMillis =500,一段时间内old gc 都会稳定在500ms以内,但每天总会有1-2次old gc 时间很长,大概3000ms.请问该如何优化呢?

    作者回复: 设置的并发标记线程数量是多少呢?可以通过-XX:ConcGCThreads尝试适当调整这个数量,为服务器CPU核数的1/4,可以提高并发标记的效率。

    由于JVM 垃圾回收和内存分配这块的调优错综复杂,需要我们再结合服务器上跑的相关的业务以及GC日志逐步调优。

    2019-07-20
  • nightmare
    老师,查看minor gc存活对象的命令是什么呢

    作者回复: 具体存活的对象是在随时变化的,很难追踪,目前只能通过各个区域的大小来分析GC效率。

    2019-07-15
收起评论
21
返回
顶部