没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
类加载:
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中
分配。对于该阶段有以下几点需要注意:
2. 从概念上讲,类变量所使用的内存都应当在 方法区 中进行分配。不过有一点需要
注意的是:JDK 7 之前,HotSpot 使用永久代来实现方法区的时候,实现是完全符
合这种逻辑概念的。 而在 JDK 7 及之后,HotSpot 已经把原本放在永久代的字符
串常量池、静态变量等移动到堆中,这个时候类变量则会随着 Class 对象一起存放
在 Java 堆中。相关阅读:《深入理解 Java 虚拟机(第 3 版)》勘误#75
注意:字节码文件中初始化方法有两种,非静态资源初始化的<init>和静态资源初始化的
<clinit>,类构造器方法<clinit>()不同于类的构造器,这些方法都是字节码文件中只能给
JVM 识别的特殊方法。
3.3.3 虚拟机栈的生命周期
对于栈来说,不存在垃圾回收。只要程序运行结束,栈的空间自然就会释放了。栈的生命
周期和所处的线程是一致的。
这里补充一句:8 种基本类型的变量+对象的引用变量+实例方法都是在栈里面分配内存。
3.3.5 局部变量的复用
局部变量表用于存放方法参数和方法内部所定义的局部变量。它的容量是以 Slot 为最小单
位,一个 slot 可以存放 32 位以内的数据类型。
虚拟机通过索引定位的方式使用局部变量表,范围为[0,局部变量表的 slot 的数量]。方法
中的参数就会按一定顺序排列在这个局部变量表中,至于怎么排的我们可以先不关心。而
为了节省栈帧空间,这些 slot 是可以复用的,当方法执行位置超过了某个变量,那么这个
变量的 slot 可以被其它变量复用。当然如果需要复用,那我们的垃圾回收自然就不会去动
这些内存。
4.5 小总结
根据实际事情调整新生代和幸存代的大小,官方推荐新生代占 java 堆的 3/8,幸存代占新
生代的 1/10
在 OOM 时,记得 Dump 出堆,确保可以排查现场问题,通过下面命令你可以输出一
个.dump 文件,这个文件可以使用 VisualVM 或者 Java 自带的 Java VisualVM 工具。
-Xmx20m -Xms5m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=你要输出的日志
路径
一般我们也可以通过编写脚本的方式来让 OOM 出现时给我们报个信,可以通过发送邮件
或者重启程序等来解决。
tips:如果堆空间没有用完也抛出了 OOM,有可能是永久区导致的。堆空间实际占用非常
少,但是永久区溢出一样抛出 OOM。
案例二 请求高峰期发生 GC,导致服务可用性下降 ===>
即使用
CMSScavengeBeforeRemark
参数
,
强制
Remark
前进行一次
Minor GC
,从而降低
Remark
阶
段的时间
!
确定目标
GC 日志显示,高峰期 CMS 在重标记(Remark)阶段耗时 1.39s。Remark 阶段是 Stop-The-
World(以下简称为 STW)的,即在执行垃圾回收时,Java 应用程序中除了垃圾回收器线
程之外其他所有线程都被挂起,意味着在此期间,用户正常工作的线程全部被暂停下来,
这是低延时服务不能接受的。本次优化目标是降低 Remark 时间。
优化
解决问题前,先回顾一下 CMS 的四个主要阶段,以及各个阶段的工作内容。下图展示了
CMS 各个阶段可以标记的对象,用不同颜色区分。 1. Init-mark 初始标记(STW) ,该阶段进
行可达性分析,标记 GC ROOT 能直接关联到的对象,所以很快。 2. Concurrent-mark 并发
标记,由前阶段标记过的绿色对象出发,所有可到达的对象都在本阶段中标记。 3.
Remark 重标记(STW) ,暂停所有用户线程,重新扫描堆中的对象,进行可达性分析,标记
活着的对象。因为并发标记阶段是和用户线程并发执行的过程,所以该过程中可能有用户
线程修改某些活跃对象的字段,指向了一个未标记过的对象,如下图中红色对象在并发标
记开始时不可达,但是并行期间引用发生变化,变为对象可达,这个阶段需要重新标记出
此类对象,防止在下一阶段被清理掉,这个过程也是需要 STW 的。特别需要注意一点,这
个阶段是以新生代中对象为根来判断对象是否存活的。 4. 并发清理,进行并发的垃圾清
理。
可见,Remark 阶段主要是通过扫描堆来判断对象是否存活。那么准确判断对象是否存活,
需要扫描哪些对象?CMS 对老年代做回收,Remark 阶段仅扫描老年代是否可行?结论是不
可行,原因如下:
如果仅扫描老年代中对象,即以老年代中对象为根,判断对象是否存在引用,上图中,对
象 A 因为引用存在新生代中,它在 Remark 阶段就不会被修正标记为可达,GC 时会被错误
回收。 新生代对象持有老年代中对象的引用,这种情况称为“跨代引用”。因它的存在,
Remark 阶段必须扫描整个堆来判断对象是否存活,包括图中灰色的不可达对象。
灰色对象已经不可达,但仍然需要扫描的原因:新生代 GC 和老年代的 GC 是各自分开独立
进行的,只有 Minor GC 时才会使用根搜索算法,标记新生代对象是否可达,也就是说虽然
一些对象已经不可达,但在 Minor GC 发生前不会被标记为不可达,CMS 也无法辨认哪些对
象存活,只能全堆扫描(新生代+老年代)。由此可见堆中对象的数目影响了 Remark 阶段
耗时。 分析 GC 日志可以得出同样的规律,Remark 耗时>500ms 时,新生代使用率都在
75%以上。这样降低 Remark 阶段耗时问题转换成如何减少新生代对象数量。
新生代中对象的特点是“朝生夕灭”,这样如果 Remark 前执行一次 Minor GC,大部分对象
就会被回收。CMS 就采用了这样的方式,在 Remark 前增加了一个可中断的并发预清理
(CMS-concurrent-abortable-preclean),该阶段主要工作仍然是并发标记对象是否存活,只
是这个过程可被中断。此阶段在 Eden 区使用超过 2M 时启动,当然 2M 是默认的阈值,可
以通过参数修改。如果此阶段执行时等到了 Minor GC,那么上述灰色对象将被回收,
Reamark 阶段需要扫描的对象就少了。
除此之外 CMS 为了避免这个阶段没有等到 Minor GC 而陷入无限等待,提供了参数
CMSMaxAbortablePrecleanTime ,默认为 5s,含义是如果可中断的预清理执行超过 5s,不
管发没发生 Minor GC,都会中止此阶段,进入 Remark。 根据 GC 日志红色标记 2 处显
示,可中断的并发预清理执行了 5.35s,超过了设置的 5s 被中断,期间没有等到 Minor
GC ,所以 Remark 时新生代中仍然有很多对象。
对于这种情况,CMS 提供 CMSScavengeBeforeRemark 参数,用来保证 Remark 前强制进行
一次 Minor GC。
优化结果
经过增加 CMSScavengeBeforeRemark 参数,单次执行时间>200
ms 的 GC 停顿消失,从监控上观察,GCtime 和业务波动保持一致,不再有明显的毛刺。
小结
通过案例分析了解到,由于跨代引用的存在,CMS 在 Remark 阶段必须扫描整个堆,同时
为了避免扫描时新生代有很多对象,增加了可中断的预清理阶段用来等待 Minor GC 的发
生。只是该阶段有时间限制,如果超时等不到 Minor GC,Remark 时新生代仍然有很多对
象,我们的调优策略是,通过参数强制 Remark 前进行一次 Minor GC,从而降低 Remark
阶段的时间。 ===> 即使用 CMSScavengeBeforeRemark 参数 !!!
更多思考
案例中只涉及老年代 GC,其实新生代 GC 存在同样的问题,即老年代可能持有新生代对象
引用,所以 Minor GC 时也必须扫描老年代。
JVM 是如何避免 Minor GC 时扫描全堆的? 经过统计信息显示,老年代持有新生代对象引
用的情况不足 1%,根据这一特性 JVM 引入了卡表(card table)来实现这一目的。如下图
所示:
卡表的具体策略是将老年代的空间分成大小为 512B 的若干张卡(card)。卡表本身是单字
节数组,数组中的每个元素对应着一张卡,当发生老年代引用新生代时,虚拟机将该卡对
应的卡表元素设置为适当的值。如上图所示,卡表 3 被标记为脏(卡表还有另外的作用,
标识并发标记阶段哪些块被修改过),之后 Minor GC 时通过扫描卡表就可以很快的识别哪
些卡中存在老年代指向新生代的引用。这样虚拟机通过空间换时间的方式,避免了全堆扫
描。
总结来说,CMS 的设计聚焦在获取最短的时延,为此它“不遗余力”地做了很多工作,包
括尽量让应用程序和 GC 线程并发、增加可中断的并发预清理阶段、引入卡表等,虽然这
些操作牺牲了一定吞吐量但获得了更短的回收停顿时间。
案例三 发生 Stop-The-World 的 GC
确定目标
GC 日志如下图(在 GC 日志中,Full GC 是用来说明这次垃圾回收的停顿类型,代表 STW 类
型的 GC,并不特指老年代 GC),根据 GC 日志可知本次 Full GC 耗时 1.23s。这个在线服务
同样要求低时延高可用。本次优化目标是降低单次 STW 回收停顿时间,提高可用性。
优化
首先,什么时候可能会触发 STW 的 Full GC 呢? 1. Perm 空间不足; 2. CMS GC 时出现
promotion failed 和 concurrent mode failure(concurrent mode failure 发生的原因一般是 CMS
正在进行,但是由于老年代空间不足,需要尽快回收老年代里面的不再被使用的对象,这
时停止所有的线程,同时终止 CMS,直接进行 Serial Old GC); 3. 统计得到的 Young GC 晋
升到老年代的平均大小大于老年代的剩余空间; 4. 主动触发 Full GC(执行 jmap -histo:live
[pid])来避免碎片问题。
然后,我们来逐一分析一下: - 排除原因 2:如果是原因 2 中两种情况,日志中会有特殊
标识,目前没有。 - 排除原因 3:根据 GC 日志,当时老年代使用量仅为 20%,也不存在
大于 2G 的大对象产生。 - 排除原因 4:因为当时没有相关命令执行。 - 锁定原因 1:根
据日志发现 Full GC 后,Perm 区变大了,推断是由于永久代空间不足容量扩展导致的。
找到原因后解决方法有两种: 1. 通过把-XX:PermSize 参数和-XX:MaxPermSize 设置成一
样,强制虚拟机在启动的时候就把永久代的容量固定下来,避免运行时自动扩容。 2. CMS
剩余239页未读,继续阅读
资源评论
qq_40849626
- 粉丝: 6
- 资源: 15
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功