摘要: gc
程序运行期间会使用到的运行时数据区
每一个虚拟机线程都有自己的PC寄存器,保存Java虚拟机正在执行的字节码指令的地址,如果该方法是 native 的,那 PC 寄存器的值是 undefined
线程私有的,栈与线程同时创建,生命周期和线程是一样的。虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧 ,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从开始调用到执行完成都对应着一个栈帧在虚拟机栈中入栈到出栈的过程
用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接、方法返回值和异常分派
栈帧随着方法调用而创建,随着方法结束而销毁,无论方法是正常完成还是异常完成
栈帧的存储空间分配在Java虚拟机栈
每一个栈帧都有自己的局部变量表、操作数栈和指向当前方法所属的类的运行时常量池的引用
栈帧容量的大小仅仅取决于Java虚拟机的实现和方法调用时可被分配的内存
一条线程只有正在执行某个方法的栈帧是活动的,叫做当前栈帧,对应的方法叫当前方法,定义这个方法的类叫当前类。对局部变量表和操作数栈的各种操作,通常指的是当前栈帧进行的操作
栈帧是线程本地私有的数据,不可能在一个栈帧之中引用另外一条线程的栈帧 如果当前方法调用了其他方法,或者当前方法执行结束,那这个方法的栈帧就不再是当前栈帧了。当一个新的方法被调用,则会新建一个栈帧并成为当前栈帧,当方法返回时会将结果(当前新的栈帧)返回给上一个栈帧,当前栈帧丢弃,上一个栈帧重新成为当前栈帧。
长度由编译期决定,存储于类和接口的二进制表示之中,既通过方法的Code属性保存及提供给栈帧使用
当方法被调用时候,参数将会传递至从0开始的连续的局部变量表里。如果是实例方法被调用则第0个局部变量一定是this
局部变量使用索引来进行定位访问,0-max long和double这种需要两个局部变量的类型,索引取最小的那个局部变量。
同局部变量表,长度由编译期决定,存储于类和接口的二进制表示之中,既通过方法的Code属性保存及提供给栈帧使用
操作数栈所属的栈帧在刚刚被创建的时候,操作数栈是空的。
Java虚拟机提供一些字节码指令来从局部变量表或者对象实例的字段中复制常量或变量值到操作数栈中,也提供了一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用的时候,操作数栈也用来准备调用方法的参数以及接收方法返回结果,例子参考初识jvm指令执行流程
一个long或者double类型的数据会占用两个单位的栈深度,其他数据类型则会占用一个单位深度
在Class文件里面,描述一个方法调用了其他方法,或者访问其成员变量是通过符号引用来表示的,动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用
类加载的过程中将要解析掉尚未被解析的符号引用,并且将变量访问转化为访问这些变量的存储结构所在的运行时内存位置的正确偏移量
由于动态链接的存在,通过晚期绑定(Late Binding)使用的其他类的方法和变量在发生变化时,将不会对调用它们的方法构成影响
可供各条线程共享的运行时内存区域,也是供所有类实例和数组对象分配内存的区域
Java堆在虚拟机启动的时候就被创建,它存储了被自动内存管理系统所管理的各种对象,这些受管理的对象无需,也无法显式地被销毁
可供各条线程共享的运行时内存区域
方法区在虚拟机启动的时候被创建,存储了每一个类的结构信息,例如运行时常量池(存放编译器生成的各种字面量和符号引用)、字段和方法数据、构造函数和普通方法的字节码内容、还包括一些在类、实例、接口初始化时用到的特殊方法
虽然方法区是堆的逻辑组成部分,但是简单的虚拟机实现可以选择在这个区域不实现垃圾收集,在 JDK1.7 中是 Perm Space , 在 JDK1.8 中是Meta Space
每一个类或接口的常量池的运行时表示形式,它包括了若干种不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用
每一个运行时常量池都分配在Java虚拟机的方法区之中,在类和接口被加载到虚拟机后,对应的运行时常量池就被创建出来
如果支持本地方法栈,则会在线程创建的时候按线程分配
堆区 又被划分为 新生代(Young) 和 老年代(Old)
Young 新生代,又被划分为 Eden区 和 S区 ,默认情况下JVM采取的是一种动态分配的策略(-XX:+UsePSAdaptiveSurvivorSizePolicy
),根据对象生成的速率,以及S区使用情况动态调整Eden区和S区的比例,也可以使用参数固定此比例(-XX:SurvivorRatio
) ,比例越低浪费的堆空间就越高(因为S区有一个区域一直为空,S区大了就会浪费一部分空间),至于为什么会有S0与S1,可以参考下面的可达性分析清除方式,这里不过多解释
Metaspace 存放 Class 、Package 、Method 、Field 、字节码 、常量池 、符号引用等等
CCS 压缩类空间,只有启用压缩类短指针的时候才会存在。出现的原因:在堆里面分配每一个对象都会有一个指向自己 Class 的指针,64位的虚拟机每个指针长度是64位的,考虑性能原因我们可以把这个指针使用短指针来引用,使用32位的指针,如果使用短指针,其所引用的Class文件则会存放到CCS区,在 JVM 中是默认开启的 UseCompressedClassPointers
,默认1G大小,可以使用 CompressedClassSpaceSize
设置大小
CodeCache 存放JIT即时编译代码 、Native代码,可以使用 -XX:InitialCodeCacheSize
-XX:ReservedCodeCacheSize
设置初始和最大大小
需要注意的是在我们调用 new 指令时,它会在Eden区划出一块作为存储对象的内存,由于堆空间是共享的(参考上文中 Jvm的内存结构),所以在划空间时候是需要进行同步的,JVM的解决办法是为每一个线程分配一段连续的内存作为线程私有的TLAB(Thread Local Allocation Buffer) ,并且只允许该线程拥有该部分内存,该技术对应参数(
-XX:+UseTLAB
,默认开启)。 同样在线程申请内存时候需要加锁,线程主要维护两个指针,一个指向TLAB空余内存的起始Adress,一个指向TLAB末尾。而new的时候便可以通过bump the pointer
来实现,即将第一个指向起始位置的指针加上请求的字节数,当加上字节数后的值大于指向末尾的指针的值,则当前线程重新申请新的TLAB。当Eden区耗尽则会触发minor GC
无法被程序引用的在堆上已分配的内存空间称为垃圾
引用计数方式会为每个已分配的内存单元设置计数器,当计数器减少为0的时候意味着再无法被引用,将立即执行释放内存的动作。
引用计数 使用弱引用来解决循环引用带来的问题,弱引用不会影响计数器状态的引用,即使循环引用也不会阻止其被清除
基本思想是 标记-清除(mark-and-sweep) ,每隔一段时间或堆空间不足时候才进行一次垃圾回收,每次回收先将所有堆上分配的内存单元标记为 “不可到达” ,然后根据 GC Root 开始扫描,把可达到的内存单元标记为 “可以到达” 。最后回收标记为 “不可到达” 的内存单元。
GC Root: 类加载器、已启动且未停止的Thread、虚拟机栈的本地变量表、static成员、常量引用、本地方法栈的变量
标记-清除 并不会受循环引用的影响,如A与B循环依赖 但是当AB都 不可达 时则会将AB都进行回收。标记-清除垃圾回收同样也提供了弱引用,原因是为了解决可能是人为造成的内存泄露(无意长时间持有了对已经不需要的对象的引用,如private static修饰的类变量)
现代垃圾回收器会综合上述几种清除方式,根据不同场景选出最合适的方式,例如JVM中会频繁进行Eden区回收,此时则采用复制方式,这样的原因是理想情况下清除垃圾后Eden区的对象基本都死亡了,需要复制的数据很少,使用复制算法效果很好。
如果老年代中的对象引用新生代的对象,在使用可达性分析标记存活对象的时候我们就需要扫描老年代。为了不做耗时较久的全堆扫描,HotSpot 的解决方案是 Card Table
,该技术将堆划分为一个个大小为512字节的 Card
,然后维护了一个 Table
,用来存储每张 Card
的一个标识位,这个标识位代表是否可能存在指向新生代对象的引用,若存在则认为这是一个脏的 Card
。则在新生代 Minor GC 时则不需要扫描整个老年代,而是寻找脏的 Card
将其中的对象加入到 Minor GC 的 GC Root 中,完成扫描则将所有标识位清空
-XX:PretenureSizeThreshold
)设置大对象的临界值,默认0,表示没有最大值-XX:MaxTenuringThreshold
设置 ),则会进入老年代-XX:TargetSurvivorRatio
设置比例)的平均复制次数取一个最小值,达到这个值则会进入老年代可通过参数 (-XX:+PrintTenuringDistribution) 打印 Minor GC 后存活对象的年龄分布情况
-XX:+UseSerialGC -XX:+UseSerialOldGC
多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态。
-XX:+UseParallelGC -XX:+UseParallelOldGC
-XX:ParallelGCThreads=<N> # 多少GC线程 默认CPU>8 N=5/8 CPU<8 N=CPU
JVM会自动调整堆分区的大小来满足以上三个参数条件,当不够了就会变大,变化的比例同样可以通过JVM参数设置:
-XX:YoungGenerationSizeIncrement=<Y>
年轻代适应下大小百分比,默认值20-XX:TenuredGenerationSizeIncrement=<T>
老年代适应下大小百分比,默认值20-XX:AdaptiveSizeDecrementScaleFactor=<D>
变小的百分比,默认4用户线程与垃圾收集线程同时执行(但不一定是并行的,可能会交替执行),垃圾收集线程在执行的时候不会停顿用户程序的运行。适合对响应时间有要求的场景。本文只是简单介绍,之后会单独出对应文章
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseG1GC
核心是 标记-清除 ,优点是停顿时间最短,缺点是内存碎片,适用于Web
- **初始标记(STW)** 仅标记 GC Root 直接引用的对象
- **并发标记** 从 GC Root 出发,标记可达对象
- **重新标记** 标记 **并发标记** 过程中,变更的对象
- **并发清除** 清除无用对象
- 并发标记、清理过程、work thread 在运行,申请老年代可能失败
- 失败后会 **降级** (临时启动 Serial Old 收集器)
缺点:
-XX:ConcGCThreads # 并发的GC线程数 default: 0(jdk8)
-XX:UseCMSCompactAtFullCollection # FullGC之后做压缩 default: true
-XX:CMSFullGCsBeforeCompaction # 多少次FullGC之后压缩一次 default: 0
-XX:CMSInitiatingOccupancyFraction # Old区占用百分比后触发FullGC 该参数必须配合UseCMSInitiatingOccupancyOnly使用才有效 default: -1,负值表示使用CMSTriggerRatio->default: 80
-XX:+UseCMSInitiatingOccupancyOnly # 是否开启CMSInitiatingOccupancyFraction占用率 default: false
-XX:+CMSScavengeBeforeRemark # FullGC之前先做YGC 减少gc roots扫描的对象数,从而提高remark的效率 default: false
-XX:+CMSClassUnloadingEnabled # 是否开启类卸载 如果开启 在full gc是会顺带扫描回收metaSpace/PermGen default: true(jdk8)
Region G1中的块的概念,
SATB Snapshot At The Beginning 通过Root Tracing 得到的,GC开始时候存活对象的快照
RSet 记录了其他 Region 中的对象引用本 Region 中对象的关系,属于 points-into 结构(谁引用了我的对象)
Mixed GC时机
- `InitiatingHeapOccupancyPercent` 堆占有率达到这个数值则触发 **Global Concurrent Marking** ,默认 45
- `G1HeapWastePercent` 在 **Global Concurrent Marking** 结束之后,可以知道 Region 有多少空间要被回收,在每次YGC之后和再次发生 **Mixed GC** 之前,会检查垃圾占比是否达到此参数,达到了下次才会发生 **Mixed GC** ,默认 5(jdk8)
- `G1MixedGCLiveThresholdPercent` Old 区的 region 被回收时候的存活对象占比需要达到多少,默认值 85
- `G1MixedGCCountTarget` 一次 **Global Concurrent Marking** 之后,最多执行 **Mixed GC** 的次数,默认值 8
- `G1OldCSetRegionThresholdPercent` 一次 **Mixed GC** 最多能回收Old区的 Region ,默认值 10
-XX:G1HeapRegionSize=n # region的大小,1-32M,2048个 default: 0
-XX:MaxGCPauseMillis=200 # 最大停顿时间
-XX:G1NewSizePercent # Young区的最小占比 default: 5
-XX:G1MaxNewSizePercent # Young区的最大占比 default: 60
-XX:G1ReservePercent # 保留空间防止to space溢出 default: 10
-XX:ParallelGCThreads=n # SWT线程数 default: 2(jdk linux)
-XX:ConcGCThreads=n # 并发线程数=1/4*并行 default: 0
G1中避免使用-Xmn、-XX:NewRatio等显式设置Young区的大小,会覆盖停顿时间目标
实线的代表可以搭配使用的,例如 Old 区使用 CMS ,Young 区则可以使用 Serial、ParNew
虚线的代表 CMS 可以退化为 SerialOld(可以通过压缩减少碎片、内存使用率增长较快则降低触发FullGC 阈值来避免退化)
-XX:+PrintGCDetails # 打印GC日志详情
-XX:+PrintGCTimeStamps # 打印时间戳
-XX:+PrintGCDateStamps # 打印日期戳
-Xloggc:${PRO_NAME}/logs/gc.log # GC日志文件路径
-XX:+PrintHeapAtGC # 在每次GC的前后打印堆的使用量
-XX:+PrintTenuringDistribution # 发生GC时候打印Young区 复制年龄信息
1. {Heap before GC invocations=40 (full 0):
1.1. par new generation total 120192K, used 116952K [0x00000000f0000000, 0x00000000f8000000, 0x00000000f8000000)
1.2. eden space 109312K, 100% used [0x00000000f0000000, 0x00000000f6ac0000, 0x00000000f6ac0000)
1.3. from space 10880K, 70% used [0x00000000f6ac0000, 0x00000000f7236058, 0x00000000f7560000)
1.4. to space 10880K, 0% used [0x00000000f7560000, 0x00000000f7560000, 0x00000000f8000000)
1.5. concurrent mark-sweep generation total 131072K, used 103011K [0x00000000f8000000, 0x0000000100000000, 0x0000000100000000)
1.6. Metaspace used 92140K, capacity 94694K, committed 95104K, reserved 1132544K
1.7. class space used 10753K, capacity 11309K, committed 11392K, reserved 1048576K
2. 2019-01-24T13:37:49.590+0800: 33.245: [GC (Allocation Failure) 2019-01-24T13:37:49.590+0800: 33.245: [ParNew
Desired survivor size 5570560 bytes, new threshold 1 (max 15)
- age 1: 7451168 bytes, 7451168 total
: 116952K->8689K(120192K), 0.0155957 secs] 219963K->115232K(251264K), 0.0156544 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]
3. Heap after GC invocations=41 (full 0):
3.1. par new generation total 120192K, used 8689K [0x00000000f0000000, 0x00000000f8000000, 0x00000000f8000000)
3.2. eden space 109312K, 0% used [0x00000000f0000000, 0x00000000f0000000, 0x00000000f6ac0000)
3.3. from space 10880K, 79% used [0x00000000f7560000, 0x00000000f7ddc480, 0x00000000f8000000)
3.4. to space 10880K, 0% used [0x00000000f6ac0000, 0x00000000f6ac0000, 0x00000000f7560000)
3.5. concurrent mark-sweep generation total 131072K, used 106543K [0x00000000f8000000, 0x0000000100000000, 0x0000000100000000)
3.6. Metaspace used 92140K, capacity 94694K, committed 95104K, reserved 1132544K
3.7. class space used 10753K, capacity 11309K, committed 11392K, reserved 1048576K
}
4. 2019-01-24T13:37:49.606+0800: 33.261: [GC (CMS Initial Mark) [1 CMS-initial-mark: 106543K(131072K)] 117412K(251264K), 0.0029414 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
4.1. 2019-01-24T13:37:49.609+0800: 33.264: [CMS-concurrent-mark-start]
4.2. 2019-01-24T13:37:49.707+0800: 33.362: [CMS-concurrent-mark: 0.096/0.098 secs] [Times: user=0.19 sys=0.00, real=0.10 secs]
4.3. 2019-01-24T13:37:49.707+0800: 33.362: [CMS-concurrent-preclean-start]
4.4. 2019-01-24T13:37:49.710+0800: 33.365: [CMS-concurrent-preclean: 0.003/0.003 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
4.5. 2019-01-24T13:37:49.710+0800: 33.365: [CMS-concurrent-abortable-preclean-start]
# ... 省略 before after gc detail
4.6. 2019-01-24T13:37:50.004+0800: 33.659: [GC (Allocation Failure) 2019-01-24T13:37:50.004+0800: 33.659: [ParNew
Desired survivor size 5570560 bytes, new threshold 1 (max 15)
- age 1: 5574000 bytes, 5574000 total
: 118001K->6415K(120192K), 0.0127596 secs] 224544K->114652K(251264K), 0.0128342 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
4.7. 2019-01-24T13:37:50.336+0800: 33.991: [CMS-concurrent-abortable-preclean: 0.411/0.626 secs] [Times: user=1.02 sys=0.09, real=0.63 secs]
4.8. 2019-01-24T13:37:50.336+0800: 33.991: [GC (CMS Final Remark) [YG occupancy: 105816 K (120192 K)]2019-01-24T13:37:50.336+0800: 33.991: [Rescan (parallel) , 0.0214420 secs]2019-01-24T13:37:50.357+0800: 34.013: [weak refs processing, 0.0013584 secs]2019-01-24T13:37:50.359+0800: 34.014: [class unloading, 0.0203511 secs]2019-01-24T13:37:50.379+0800: 34.035: [scrub symbol table, 0.0189360 secs]2019-01-24T13:37:50.398+0800: 34.054: [scrub string table, 0.0010303 secs][1 CMS-remark: 108237K(131072K)] 214053K(251264K), 0.0639900 secs] [Times: user=0.08 sys=0.00, real=0.06 secs]
4.9. 2019-01-24T13:37:50.400+0800: 34.055: [CMS-concurrent-sweep-start]
# ...
5.0. 2019-01-24T13:37:50.443+0800: 34.098: [CMS-concurrent-reset-start]
# 中括号的内容是内存地址
# 1. GC前堆的占用情况
# 1.1. 年轻代总大小 120192K,已用 116952K (f8000000-f0000000 = 134217728/1024 = 131072-total(120192)=10880),即年轻代中的s区实际上是有一个大小为10880的空间是浪费的
# 1.2. eden区使用情况,使用率100%,大小 109312K(f6ac0000-f0000000=111935488/1024=109312)
# 1.3. s-from区使用情况,使用率70%,大小 10880K,(f7236058-f6ac0000=7823448/1024 = 7640) /10880=70%
# 1.4. s-to区使用情况,使用率0%,大小 10880K
# 1.5. 老年代使用情况,大小 131072K,使用 103011K,占比78.6%
# 1.6. 元数据空间使用情况(持久代小 94694K,使用 92140K
# 1.7. 元数据空间中类占用的空间情况,大小 11309K,使用 10753K
# 2. 新生代回收,从109312K减少至10880K,新分配了20192k 整个内存空间从109312K减少至19172K,总内存空间分配了251264K,耗时0.032s左右,年龄是1;[Times: user=0.05 sys=0.02, real=0.03 secs] 可以看到user+sys(进程实际消耗的CPU时间)>real(调用从开始到结束的实际持续时间),说明我们是多个cpu执行
# 3. GC后堆的占用情况 invocations=41 指的是已经进行41次gc
# 3.1. 年轻代总大小 120192K,已用 8689K
# 3.2. eden区使用情况,使用率0%,大小 109312K
# 3.3. s-from区使用情况,使用率79%,大小 10880K,(f7ddc480-f7560000=8897664/1024 = 8689) /10880=79.8%
# 3.4. s-to区使用情况,使用率0%,大小 10880K
# 3.5. 老年代使用情况,大小 131072K,使用 106543K,占比81.3%
# 4. 由-XX:CMSInitiatingOccupancyFraction参数默认80%可知,old区占比已经大于阈值,将要进行full-gc,此阶段是初始标记阶段,只是标记一下GC Roots能直接关联的对象,是STW的
# 4.1&4.2. 并发标记阶段,和用户线程并发执行,主要作用是标记可达的对象
# 4.3&4.4. 预清理阶段
# 4.5&4.7. 此阶段的目的是使cms gc更加可控一些,作用也是执行一些预清理,以减少Rescan阶段造成应用暂停的时间
# 4.6. 先执行一次ParNew GC
# 4.8. 重新扫描标记阶段因用户线程继续运作而导致的变动对象,是STW的
# 4.9. 并发清理
# 5.0. 重置线程参数