JVM笔记二

大佬JVM笔记转载留念



  • 线程共享。
  • 有垃圾回收机制。
  • 栈管运行,堆管存储。
  • 会出现OOM内存溢出现象。

堆内存诊断

  • jps工具
    • 查看当前系统中有哪些java进程。
  • jmap工具
    • 查看堆内存占用情况, jmap - heap 进程id。
  • jconsole工具
    • 简易图形界面的,多功能的监测工具,可以连续监测。
  • jvistualvm工具
    • 高级图形界面的,多功能的监测工具,可以连续监测。

方法区

  • 线程共享
  • 包含运行时常量池
  • 存储信息:
    • 成员变量
    • 方法数据
    • 成员方法以及构造方法的代码部分
    • 运行时常量池
  • 会有内存溢出现象
  • 方法区结构如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JwcOUGSx-1631863909696)(http://47.98.150.21/upload/2020/3/image-0402a2e372aa4bce8c0fd8e3089c3eea.png “方法区结构”)]

  • 在jvm规范中,方法区是堆的一部分,但是实际上各有差异,jdk1.8之前,hotSpot有方法区,jdk1.8之后,方法区变为了元空间,而且存储在本地内存。

方法区内存溢出

  • 1.8 以前会导致永久代(方法区概念的实现)内存溢出
    • 设置永久代大小:-XX:MaxPermSize=8m
  • 1.8 之后会导致元空间内存溢出
    • 设置元空间大小:-XX:MaxMetaspaceSize=8m
  • 场景:
    • Spring
    • Mybatis
      都会产生大量的类,从而造成方法区内存溢出。

对象头信息

在这里插入图片描述

常量池

  • 二进制字节码包括:类基本信息、常量池、类方法定义、虚拟机指令等
  • StringTable在运行时常量池中,且底层是哈希表,并且使用懒加载方式(用到才在串池创建)。串池中存储的是字符串的引用,真正的字符串对象是在堆中。

StringTable的特性

  • 常量池中的字符串仅是符号,第一次用到时才变为对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是 StringBuilder (1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
    • 1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
    • 1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回

StringTable的调优

  • 调整hash表中桶子个数,-XX:StringTableSize=桶个数(最少1009个)
  • 虑字符串是否入池

直接内存

  • 常见于NIO操作中,用于数据缓冲
  • 分配回收成本高,但读写能力强
  • 不受JVM内存回收管理,而是受Unsafe类的freeMemory方法管理,一旦ByteBuffer对象被回收,就会触发freeMemory方法来释放直接内存
  • 操作系统和java代码共享的一块儿内存区域,比传统io在效率上大大提高
  • -XX:+DisableExplicitGC 显式(个人理解即是人为调用)的System.gc()显式的垃圾回收 FULL GC(回收新生代和老年代),被禁用。
  • 所以,只是为了管理直接内存,推荐调用unsafe对象的freeMemory()

垃圾回收GC

如何判断垃圾可以被回收

  • 引用计数法:

    • 如果两个对象互相引用,计数器都为1,即使他们都没有被使用,都不会被清理。当计数为0时,才会被回收。
  • 可达性分析算法:

    • Java虚拟机中的垃圾回收器采用可达性分析来探索所有的对象
    • 扫描堆中的对象,判断是否能根据GC Root的引用链找到该对象,找不到则回收。
  • 五种引用:

    • 强引用:指向某一对象的所有强引用都断开,该对象才能被回收。

    • 软引用:仅有软引用引用该对象时,在垃圾回收后,内存仍不足时会再次出发垃圾回收,回收软引用对象,可以配合引用队列来释放软引用自身。

    • 弱引用:仅有弱引用引用该对象时,在垃圾回收时,无论内存是否充足,都会回收弱引用对象。可以配合引用队列来释放弱引用自身。

    • 虚引用:必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存。

    • 终结器引用(不推荐):无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再由 Finalizer 线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次 GC 时才能回收被引用对象。

垃圾回收算法

  • 标记清除
    • 速度较快
    • 会造成内存碎片化

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Py6A27D-1631863909697)(http://47.98.150.21/upload/2020/3/image-467ea48c827947d99130701448d2af0d.png)]

  • 标记整理
    • 速度慢
    • 不会有内存碎片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XJ3QSQSG-1631863909698)(http://47.98.150.21/upload/2020/3/image-3cf83bb2c39341e4b997a81facb45480.png)]

  • 复制
    • 不会有内存碎片
    • 需要占用双倍内存空间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kteMdD8l-1631863909698)(http://47.98.150.21/upload/2020/3/image-e20d4fcfc76048c8a17a2648213560f7.png)]

  • 总结:
    • 复制算法回收速度与存活对象数量相关,数量越少,速度越快,如果存活对象过多,速度可能会比标记整理速度要慢。
    • 实际JVM垃圾回收是根据情况配合三种算法使用,因为三种算法都有其实用的场景。

分代垃圾回收

堆内存被划分为新生代和老年代。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bv0nfZyN-1631863909699)(http://47.98.150.21/upload/2020/3/image-7733e793f8594b83a13f9d9568cfa4e3.png)]

  • 对象首先分配在Eden区内;

  • 对于长时间使用的对象,将其划分到老年代;

  • 对于用完就丢弃的对象,将其划分到新生代;

  • 在新生代空间不足时,触发Minor GC,将Eden和From中存活的对象复制到To中,存活的对象年龄加1,并且交换To和From(也就是说,to一直是空的);

    • Minor GC触发 STOP THE WORLD(STW),暂停其他用户线程,直到垃圾回收完毕,才回复线程运行。
    • 当对象寿命超过阈值时,会晋升至老年代,最大阈值为15(4bit)。也有没到15就去老年代的情况。
    • 设置-XXPretenureSizeThreshold=3145728(3M)大对象会直接进入老年代。
  • 当老年代空间不足,尝试Minor GC,空间仍不足,触发Full GC,触发 STW,时间相比于Minor GC会长的多,因为新生代和老年代回收算法不同,且老年代中对象多,回收较为复杂。

相关VM参数如图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wCUQufe5-1631863909700)(http://47.98.150.21/upload/2020/3/image-c62a2caab83e44b58b26eaa36715d888.png “VM参数”)]

垃圾回收器

  • 串行

    • 单线程
    • 堆内存较小,适用于个人电脑
  • 吞吐量优先

    • 多线程
    • 堆内存大,多核cpu
    • 单位时间内STW时间最小 0.2+0.2 = 0.4
  • 响应时间优先

    • 多线程
    • 堆内存大,多核cpu
    • 尽可能STW单次时间减小 0.1+0.1+0.1+0.1+0.1=0.5
  • 串行垃圾回收器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zKsbqcgi-1631863909700)(http://47.98.150.21/upload/2020/3/image-0369a8ef96fc42e58a44199b034dcb06.png)]

其中新生代使用Serial,老年代使用SerialOld垃圾回收器。

Serial垃圾回收器使用的是复制算法,新生代中对象变化很大,存活对象相对于其他分区比较少,使用复制算法能够有效地提高效率。
SerialOld使用的是标记整理算法,老年代存活的都是些价值很高的对象,回收的对象数量很少,整理耗用很小,复制存活对象会消耗更多的时间,且会耗用多一倍的内存空间(老年代本身空间就已经较大)。
  • 吞吐量优先回收器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iwZe1IsM-1631863909701)(http://47.98.150.21/upload/2020/3/image-6e7f12514cbc424f8a45fcc53a6bf658.png)]

  • 响应时间优先回收器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UOpdNN1B-1631863909701)(http://47.98.150.21/upload/2020/3/image-fe161ba291cf48199fa071741878d1e6.png)]

G1

  • 定义: Garbage First,jdk9后默认垃圾收集器。

  • 适用场景:

    • 同时注重吞吐量和低延迟,默认暂停目标时200ms。
    • 超大堆内存会将内存划分为多个相等的Region。
    • 整体上使用标记-整理算法,两个区域使用复制算法。
  • 相关JVM参数:

    • -XX:UseG1GC
    • -XX:G1HeapRegionSize=size
    • -XX:MaxGCPauseMillis=time
  • G1垃圾回收

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-umd2XcUS-1631863909702)(http://47.98.150.21/upload/2020/3/image-4934882cb0594ea49d7407a9c6aaef54.png)]

  • Young Collection

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1zpxI6e-1631863909703)(http://47.98.150.21/upload/2020/3/image-b6232f40e5f34b01b6d32f80704d9e8f.png)]

  • Young Collection + CM

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5khIAPDt-1631863909706)(http://47.98.150.21/upload/2020/3/image-fa37e99c763a49319e60166be9d56c50.png)]

  • MixedCollection

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGJP4DKr-1631863909707)(http://47.98.150.21/upload/2020/3/image-f3055121ecc24ff79ff3e0bb601921ee.png)]

  • 跨代引用

    • 老年代对象引用了新生代对象,跨代引用。
    • 老年代中红色区域为卡表中的脏卡,新生代对象通过Remmember Set记录对应脏卡。(实为标记堆中的根对象,方便做可达性分析)
    • 在引用变更时,post-write barrier更新脏卡指令放入dirty card queue队列中,等待线程执行。
    • current refinement threads 更新 Remmember Set。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QE9IU07N-1631863909708)(http://47.98.150.21/upload/2020/3/image-d5aaeee01f3a4295a55c2fac81d08ec4.png)]

FULL GC
  • SerialGC

    • 新生代内存不足minor GC
    • 老年代内存不足Full GC
  • ParallelGC

    • 新生代内存不足minor GC
    • 老年代内存不足Full GC
  • CMS

    • 新生代内存不足minor GC
    • 老年代内存不足
      • 并发失败会触发FULL GC
  • G1

    • 新生代内存不足minor GC
    • 老年代内存不足
      • 老年代占用达到阈值时,若垃圾回收大于垃圾产生速度,继续并发回收
      • 反之,会发生FULL GC

GC调优

查看虚拟机运行参数

“jdk下bin目录下java命令的绝对地址” -XX:+PrintFlagFinal -version | findstr “GC”

调优领域
  • 内存
  • 锁竞争
  • cpu占用
  • io
调优目标
  • 低延迟
  • 高吞吐量
新生代是否越大越好
  • 官方建议(1/4-1/2)堆内存的大小
  • 新生代过小,会频繁触发Minor GC
  • 新生代过大,老年代相应的变小,FULL GC 门槛变低
  • 在新生代变大的过程中,吞吐量首先是增长的,之后会下降。
  • 新生代对象大多数都是用过即死,存活的对象极少,在Minor GC时最费时间的是复制算法,在新生代内存增大后,新生代的复制算法也不会受太多影响。
  • 建议扩大的程度:新生代能够容纳所有【并发量*(请求/响应这个过程产生的内存)】的数据。
幸存区大到能保留(当前活跃对象以及需要晋升的对象)
  • 这样能保证用不着的垃圾对象下次就能被回收
  • 如果新存区不够存放,那么对象就会被转移到老年代,回收时间就会增加
晋升阈值配置得当,使长时间存活对象尽快晋升
  • -XX:MaxTenuringThreshold=threshold 配置阈值
  • -XX:+PrintTenuringDistribution 打印晋升细节
老年代调优

以cms为例。

  • 老年代内存越大越好
  • 先尝试不要做调优,如果没有FULL GC,那么已经满足需求,否则先尝试新生代调优
  • 观察发生FULL GC时老年代的内存占用,将老年代的内存提高1/4-1/3
  • -XX:CMSInitialOccupancyFraction = percent 推荐设置为70-80(值越低,触发老年代回收越早)
案例
  • FULL GC和Minor GC频繁?

    首先分析可能原因,先做新生代调优,新生代内存过小,对象放不下直接进入老年代,不仅Minor GC频繁,老年代中也会存入大量垃圾对象,FULL GC 也会频繁发生。

    所以,适当增大新生代内存大小,使得Eden可以存下新生的多个对象,不会过于频繁触发Minor GC,幸存区存下幸存对象能够使幸存对象在新存区中保存,不会过早进入老年代,老年代内存占用减轻。

  • 请求高峰期发生FULL GC,单次暂停时间过长(CMS)?

    请求高峰,并发用户很多,产生的新对象很多,堆中对象数目较大。CMS中重新标记会扫描整个堆内存来标记对象,所以会耗用大量时间。

    调优:-XX:+CMSScavengeBeforeRemark 设置在重新标记前对新生代进行一次垃圾清理,会大大减少重新标记的时间。

  • 老年代充裕情况下,发生FULL GC(cms jdk1.7)?

    在jdk1.7及以前,方法区以永久代方式实现,永久代内存不足时,也会发生FULL GC ,增加jdk1.8及以后,利用元空间实现,使用的是系统内存,内存充裕,很少会触发FULL GC。

对象存储信息定位

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值