一. 什么是JVM的性能
1. 性能优化的两个目标:
- 吞吐量 : 总共的gc时间
- 单次停顿时间 : 因为gc导致服务无响应的时间 (单次的gc时间)
一般情况下, "代"的大小选择就是在吞吐量和停顿次数上做均衡. 把年轻代的size设置的很大, 可以增加吞吐量; 相反把年轻代的size设置的很小, 可以降低单次gc的停顿时间
2. 什么是垃圾自动回收
垃圾回收程序一直在监视着heap(堆内存), 并且把正在使用或被引用的对象, 和不再使用或不被引用的对象区分开来.一个正在使用或被引用的对象, 说明程序中存在指向该对象的指针; 如果不存在任何指向该对象的指针, 则说明该对象已无用. 所以, 对于不再使用或不被引用的对象占用的内存, 可以被回收. 下面展示这个步骤
二. 垃圾回收的几个基本做法
Step 1: 标记
- gc收集器在此步区分开正在使用, 和未被使用的内存片
- 为了区分哪些对象在使用, 哪些没有在使用, 所有对象都要被scan(扫描)一次. 这种操作很消耗时间
- 如下图, 正在被引用的对象用蓝色标出, 没有引用的对象用金色标出
marking1.png
- 如下图, 正在被引用的对象用蓝色标出, 没有引用的对象用金色标出
Step 2: Normal Deletion
该步骤将删除未被引用的对象(金色的), 使得memory allocator(内存分配器)持有指向free space的指针,下次分配对象时用这些free space空间来产生对象
Step 2a: Deletion with Compacting
为了提高表现, 也可以在deletion后, compact(压缩)剩余的内存空间. 通过将存在引用的对象聚合在一起, 这将使得新内存的分配更容易更快速
![](https://img.haomeiwen.com/i14865762/ddf45ffed436e05f.png)
三. 为什么分代收集
3.1 早期jvm的做法
- 早期的jvm, gc收集器必须标记并压缩内存中的所有对象. 这种操作并不高效, 原因是随着越来越多的对象被生成, gc的时间也越来越长
- 经过经验性的总结, 发现对象多是短生命周期的, 时间越久, 对象的存活率就越低
- 如下图, x轴表示随时间增长, 共分配了多少内存; y轴表示随时间增长, 存活的对象有多大
earlyjvm.png
3.2 JVM的代
如上经验性分析可用于提高gc的表现力. 对象根据存活能力被分成青年带,老年代,永久带
-
青年代(The Young Generation):
- 新生成的对象在此产生, 年轻代充满后, 引起 minor collection . 年轻代上的对象有很高的死亡率. 每次 minor collection 后幸存的对象都会增长一个age, 直到 age 到达一个阈值, 该对象会提升到老年代.
- 可以对minor collection进行优化, 其消耗的时间主要和存活对象的数量成正比(一个充满了死亡对象的年轻代收集的很快)
- 所有的'minor gc'都是'stop the world'事件
因为如果gc的过程还有新对象产生在内存里, 这些新对象很快又有大部分变为"无引用"对象, 导致gc混乱. minor gc耗时多长时间, 系统就停止运行多长时间 - 为什么青年带要分区?(eden,s0,s1)
新生代分区是因为青年带的收集策略一般选择"复制清除", 基于"复制"的清除算法可以避免内存中产生大量空隙, 而且因为青年带90%的对象都会死亡, 复制的开销并不会很大
-
老年代(The Tenured Generation):
- 每一次minor collection, 都会有一些存活对象从年轻代晋升到年老代; 最终, 年老代也被充满, 导致需要进行major collection
- major collection在整个 heap 上进行对象收集. 因为 major collection 收集的对象数量远比 minor collection的多, 因此也比minor collection更费时
- 老年代的gc通常很慢, 所以对于'高响应' (responsiveness)的应用, 应该保证老年代的垃圾集合最小化
- 老年代gc的速度, 和选择的老年代gc收集器有很大关系
-
永久代
- 永久代包含JVM描述类和方法的meta信息
- java se的类和方法存在这个代中
- 当JVM不在使用某个类时, 就会引发gc, 永久代的gc会触发'full gc' - 所有代进行gc
四. 年轻代collection的一般流程
现在, 已经讲明jvm为什么要分代(避免scan整个内存), 是时候讲这些空间之间如何交互了.下图这展示了object allocation和ageing process的过程
4.1 新对象的分配
新对象被分配在eden区, 2个survivor区在jvm启动时是空的
4.2 当eden区空间满时, 触发minor gc:
- 存在引用的对象移动到第一个survivor空间(s0,其age从0变成1), 不存在引用的对象被从eden区删除 (因此minor gc后,eden区为空)
- 如下图, 存在引用的对象蓝色标记, 不存在引用的对象黄色标记, 对象上的数字表示age
pro1.png
4.3 下一次minor gc
同样的事情发生在eden区, 未引用的对象从eden区中删除, 但是存在引用的对象会被移动到第二个survivor空间(s1),该对象的age从0变为1; 此外, 上次minor gc移动到s0的对象, 将被移动到s1, 且其age增加1. 一旦所有对象都移动到s1, s0和eden区都会变成空的, 但是请注意, survivor区的对象的age不一样
![](https://img.haomeiwen.com/i14865762/efbf5ecfea1c6622.png)
4.4 再下次minor gc
同样的处理流程触发, 但survivor空间切换, 存在引用的对象会被移动到s0, s1和eden区会被清空
4.5 对象提升
如下图所示, 每次monor gc,当存活对象的age到达一个阈值后(图中的阈值为8), 幸存对象将从青年代被提升到老年代(被称为promotion)
![](https://img.haomeiwen.com/i14865762/fc38f2060dad8c0c.png)
4.6 随着minor gc的不断发生, 越来越多的对象被提升(promotion)到老年代
4.7 触发 major collection
随着青年代不断的执行上述整个流程, 最终触发老年代的major gc. 老年代中, 未被引用的对象将被清除, 存在引用的对象将被压缩到老年代的一端(clean up and compact)
![](https://img.haomeiwen.com/i14865762/e080640d65b01bd9.png)
六. 默认收集器
-
java -XX:+PrintCommandLineFlags -version
: 查看当前gc是哪种 - Default garbage collectors:
- Java 7 - Parallel GC
- Java 8 - Parallel GC
- Java 9 - G1 GC
- Java 10 - G1 GC
- JVM启动参数
网友评论