JVM 相关笔记

对象内存布局

HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头(Header)、实例数据(Instance Data)和对齐填充 (Padding)。

  • 对象头

    • Mark Word
    • MetaData
    • 数组长度
  • 实例数据

    存放类的属性数据信息,包括父类的属性信息

  • 对齐填充

    由于虚拟机要求 对象起始地址必须是8字节的整数倍, 为了字节对齐

JVM整体结构及内存模型

image-20210710124102074

  • JVM内存参数设置

    image-20210710124145327

JVM 内存分配

image-20210710121507179

  • 对象栈上分配

    栈上分配依赖于逃逸分析和标量替换

    JVM通过逃逸分析确定该对象不会被外部访问, 如果不会逃逸可以将该对象在栈上分配内存

  • 新对象在eden区上分配

    • eden 没有足够内存分配时,会发生minor GC
    • 如果新增的对象需要空间超过eden 内存, 会将对象提前移动到老年代中
  • 大对象直接进入老年代

  • 长期存活的对象将进入老年代

  • 对象动态年龄判断

    在S区中,一批对象的总大小>内存大小的50% (-XX:TargetSurvivorRatio可以指定),那么>= 这批对象的年龄最大值的对象就可以直接进入到老年代了。

    对象动态年龄判断机制一般是在minor gc之后触发的

  • 老年代空间分配担保机制

垃圾收集算法

image-20210714220821914

垃圾回收器

image-20210714220732485

Serial收集器 (单线程)

-XX:+UseSerialGC -XX:+UseSerialOldGC

新生代使用复制算法,老年代使用 标记-整理算法

image-20210714221337009

Parallel Scavenge 收集器 (多线程)

-XX:+UseParallelGC(年轻代), -XX:+UseParallelOldGC(老年代)

重点关注 吞吐量

新生代使用复制算法,老年代使用 标记-整理算法

JDK8默认的新生代和老年代收集器


image-20210714221845886

ParNew 收集器

-XX:+UseParNewGC

ParNew收集器其实跟Parallel收集器很类似,区别主要在于它可以和CMS收集器配合使用。

新生代使用复制算法,老年代使用 标记-整理算法

image-20210714222149303

CMS 收集器

CMS(Concurrent Mark Sweep) 以 最短 回收停顿时间(STW)为目标

回收算法: 标记清除算法

  • 初始标记:暂停所有的其他线程(STW),并记录下gc roots直接能引用的对象速度很快
  • **并发标记:**是从GC Roots的直接关联对象开始遍历整个对象图的过程。
  • 重新标记:修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象。 主要用到三色标记增量更新算法(见下面详解)做重新标记。
  • 并发清理: 对未标记的区域做清扫, 期间有新增对象直接标记为黑色。
  • 并发重置: 重置本次GC 过程中标记数据。

image-20210714222721076


  • 在并发标记的过程中, 通过 三色标记 来记录对象的是否遍历访问。

    • 黑色:已全部扫描标记完成
    • 灰色:部分扫描标记完成
    • 白色:未扫描标记
  • 多标-浮动垃圾

  • 漏标-读写屏障

    • 增量更新(IncrementalUpdate)

      增量更新就是当黑色对象插入新的指向白色对象的引用关系时, 就将这个新插入的引用记录下来, 等并发扫描结束之

      后, 再将这些记录过的引用关系中的黑色对象为根, 重新扫描一次。 这可以简化理解为, 黑色对象一旦新插入了指向

      白色对象的引用之后, 它就变回灰色对象了

    • 原始快照(Snapshot At The Beginning,SATB)

      原始快照就是当灰色对象要删除指向白色对象的引用关系时, 就将这个要删除的引用记录下来, 在并发扫描结束之后,

      再将这些记录过的引用关系中的灰色对象为根, 重新扫描一次,这样就能扫描到白色的对象,将白色对象直接标记为黑

      色(目的就是让这种对象在本轮gc清理中能存活下来,待下一轮gc的时候重新扫描,这个对象也有可能是浮动垃圾)

G1收集器

将堆空间分成大小相同的独立区域(Region),默认2048个

G1保留了 年轻代、老年代概念,但是没有物理隔阂了。

image-20210715074200035

-XX:+UseG1GC

G1 (Garbage-First)是一款面向服务器的垃圾收集器, 主要针对配备多颗处理器及大容量内存的机器 . 以极高概率满足GC

停顿时间要求的同时,还具备高吞吐量性能特征.

回收算法:复制算法

  • 初始标记:(STW) 暂停其他所有线程,并记录GC Root 直接引用的对象, 速度很快。
  • 并发标记:CMS并发标记
  • 最终标记:(STW) 同 CMS重新标记
  • 筛选回收:(STW) 对各个Region的回收价值和成本进行排序,根据用户期望的停顿时间 制定回收计划

image-20210715073319331

G1垃圾收集分类

YoungGC

YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数 `-XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的,那么就会触发Young GC

MixedGC

不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有 Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够 的空region能够承载拷贝对象就会触发一次Full GC

Full GC

停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这 个过程是非常耗时的。(Shenandoah优化成多线程收集了)

什么场景适合使用G1
  • 50%以上的堆被存活对象占用

  • 对象分配和晋升的速度变化非常大

  • 垃圾回收时间特别长,超过1秒

  • 8GB以上的堆内存(建议值)

  • 停顿时间是500ms以内

ZGC收集器

-XX:+UseZGC

ZGC是一款JDK 11中新加入的具有实验性质的低延迟垃圾收集器

image-20210717225648881

  • 并发标记(Concurrent Mark):与G1一样,并发标记是遍历对象图做可达性分析的阶段,它的初始标记(Mark Start)和最终标记(Mark End)也会出现短暂停顿,与G1不同的是, ZGC的标记是在指针上而不是在对象 上进行的, 标记阶段会更新染色指针中的Marked 0、 Marked 1标志位。

  • 并发预备重分配(Concurrent Prepare for Relocate):这个阶段需要根据特定的查询条件统计得出本次收集过程要清理哪些Region,将这些Region组成分配集(Relocation Set)。ZGC每次回收都会扫描所有的 Region,用范围更大的扫描成本换取省去G1中记忆集的维护成本。

  • 并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段,这个过程要把重分配集中的存活对象复制到新的Region上,并为重分配集中的每个Region维护一个转发表(Forward Table),记录从旧对象到新对象的转向关系。ZGC收集器能仅从引用上就明确得知一个对象是否处于重分配集之中,如果用户线程此时并发访问了位于重分配集中的对象,这次访问将会被预置的内存屏障(读屏障)所截获,然后立即根据Region上的转发表记录将访问转发到新复制的对象上,并同时修正更新该引用的值,使其直接指向新对象,ZGC将这种行为称为指针的“自愈”(Self-Healing)能力。

1 ZGC的颜色指针因为“自愈”(Self‐Healing)能力,所以只有第一次访问旧对象会变慢, 一旦重分配集中某个Region的存活对象都复制完毕后,

2 这个Region就可以立即释放用于新对象的分配,但是转发表还得留着不能释放掉, 因为可能还有访问在使用这个转发表。

  • 并发重映射(Concurrent Remap):重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用,但是ZGC中对象引用存在“自愈”功能,所以这个重映射操作并不是很迫切。ZGC很巧妙地把并发重映射阶段要做的工作,合并到了下一次垃圾收集循环中的并发标记阶段里去完成,反正它们都是要遍历所有对象的,这样合并就节省了一次遍历对象图的开销。一旦所有指针都被修正之后, 原来记录新旧对象关系的转发表就可以释放掉了。

调优指令与工具

常用指令
  • jps 获取java线程

  • top -HP 获取每个线程,显示你的java进程的内存情况,pid是你的java进程号

    printf “%x\n” 25226 // 需要将java 进程号转化为16进制, jstack中显示的进程号的都是16进制。

  • jmap 查看java内存信息

    jmap -histo pid 查看内存信息

    jmap -heap pid 查看堆信息

  • jstack 看看java线程信息

  • jstat 查看堆内存各部分的使用量,以及加载类的数量

    jstat -gc pid 评估程序内存使用及GC压力整体情况

    jstat -gc pid 1000 10 (每隔1秒执行1次命令,共执行10次)

  • jinfo 查看正在运行的Java应用程序的扩展参数

常用工具
  • Arthas
  • JProfile
  • JVisualVM

Class常量池与运行时常量池

Class 常量池

Class常量池:可以理解为是Class文件中的资源仓库。 Class文件中除了包含类的版本、字段、方法、接口等描述信息外, 还有一项信息就是常量池(constant pool table),用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References)

class 常量池是静态常量池,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息。

运行时常量池

运行时常量池: class 静态常量池被装入内存后,就成了运行时常量池。

  • 运行时常量池位于永久代 (Jdk 1.6 之前)
  • 去永久代后,运行时常量池位于方法区 (Jdk 1.7 之后)
字符串常量池

为提高性能和减少内存开销。

  • 为字符串开辟一个字符串常量池,类似于缓存区

  • 创建字符串常量时,首先查询字符串常量池是否存在该字符串

  • 存在该字符串,返回引用实例,不存在,实例化该字符串并放入池中

字符串常量池的位置:

Jdk1.6及之前: 有永久代, 运行时常量池在永久代,运行时常量池包含字符串常量池

Jdk1.7:有永久代,但已经逐步“去永久代”,字符串常量池从永久代里的运行时常量池分离到

Jdk1.8及之后: 无永久代,运行时常量池在元空间,字符串常量池里依然在

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值