# java-study **Repository Path**: benben/java-study ## Basic Information - **Project Name**: java-study - **Description**: java 源码,设计模式,算法 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2020-08-06 - **Last Updated**: 2025-03-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # JVM ## JVM内存结构 - 虚拟机栈:线程私有。每个线程在创建时都会创建一个虚拟机栈。每个被执行的方法对应一个栈帧。方法开始执行时进栈,方法执行完出栈。存储的数据类型:局部变量表、操作数栈、动态链接、方法返回地址等。动态链接:指向运行时常量池的方法引用,运行时确定。静态链接:编译时确定。 - 本地方法栈:线程私有。调用本地方法时创建。 - 程序计数器:线程私有。存储指向下一条指令的地址,即将要执行的指令代码地址。 - 元空间:共享本地内存。存放类的元数据。 - 堆:共享JVM内存。存放对象,包括常量池,常量,静态变量等。由新生代和老年代组成,默认比例1:2。新生代由伊甸园和幸存区组成,默认比例8:1:1。 ## 垃圾回收算法 - 标记复制:将内存分为大小相同的两块,每次使用其中一块。当使用完后,就将还存活的对象复制到另一块去,然后把使用的空间清理。存活对象少时效率高,损失一半空间。 - 标记清除:标记存活对象,回收未被标记的对象。存在空间碎片。 - 标记整理:标记存活对象,让存活对象向一端移动,清理掉端边界以外的内存。 ## gc roots 可以用做GC Roots的对象包括: 1. 活动线程 2. 虚拟机栈中的局部变量引用 3. 本地方法栈中的引用 4. 静态变量引用、静态常量引用 ## java对象内存 - 对象头:markWord 和 classPoint(类型指针,指向类型的元数据)、数组长度(如果是数组)。markWord 存放对象 hashcode、分代年龄,锁状态,GC标记,线程ID等。 - 对象数据:对象信息。 - padding : 对象大小必须是 8 字节的整数倍,padding 用来补全对象大小。 ## 垃圾回收器 **ParNew**:多线程收集垃圾。工作时STW。新生代采用复制算法,老年代采用标记-整理算法。 **CMS**:并发标记清除,使用标记-清除算法,支持jvm参数设置,gc完成后整理空间碎片。GC有四个阶段: - 初始标记:STW,记录gc roots能直接引用的对象。 - 并发标记:不STW,从gc roots的直接关联对象开始遍历整个对象图。 - 重新标记:STW,修正并发标记期间因为用户线程运行导致标记产生变动的部分对象的标记记录。主要用到三色标记里的增量更新算法。 - 并发清理:不STW,对未标记的区域做清扫。这个阶段如果有新增对象,会被标记为黑色不做任何处理。 **G1**:将堆划分为2048个大小相等的独立区域,也可以指定Region的大小。保留了年轻代和老年代的概念,但不再物理隔离。默认年轻代堆的占比是 5%,最多不超过 60%,年轻代中Eden和Survivor的比例默认是8:1:1。当一个对象对象的大小超过了Region的一半时,会放入Humongous区。GC有四个阶段: - 初始标记:STW,记录gc roots 能直接引用的对象。 - 并发标记:不STW,从gc roots的直接关联对象开始遍历整个对象图。 - 最终标记:STW,修正并发标记时的漏标。 - 筛选回收:STW,根据用户期望的停顿时间,按回收价值和成本排序,在有限的时间内回收更多的区域。主要用复制算法,将一个Region中存活的对象复制到另一个region中,几乎不会有太多的内存碎片。 **ZGC**:没有分代概念,采用分区模型。以页为单位进行划分,分为三种页面: - 小型Region:容量固定2M,用于存放小于256kb的对象。 - 中型Region:容量固定32M,存放大于256kb,但小于4M的对象。 - 大型Region:容量不固定,可以动态变化,必须是2M的整数倍,用于存放4M或以上的大对象,最小容量可以低至4M。 采用标记-复制算法。GC有六个阶段: - 初始标记:STW。记录gc roots 能直接引用的对象。 - 并发标记:不STW。从GC roots出发,开始对堆中的对象进行可达性分析,找出存活对象。有漏标问题。 - 再标记:STW。重新标记那些在并发标记阶段发生变化的对象。 - 并发转移准备:不STW。确定要清理的Region,将这些Region组成重分配集。为每一个要清理的Region创建并维护一个转发表。 - 初始转移:STW。转移初始标记阶段存活的对象,同时做对象重定向。 - 并发转移:不STW。转移并发标记阶段存活的对象。 ``` 读屏障:解决GC线程和用户线程并发过程中,保证数据的准确性。 转发表:解决对象移动之后,让指向旧地址的引用更新为指向新地址。对象在Region之间移动时,在转发表中保存旧地址和对应的新地址。 染色指针:解决了在垃圾回收过程中,存活对象移动到新Region时,对该存活对象的标记。ZGC只能在64位系统中运行,64位对象地址,其中低42位为真实的对象地址,可以支持4T的内存空间,43-46位是ZGC使用的标志位,用于对指针染色,其余高位未使用。 ``` ## 三色标记 在GC时,通过设置对象的对象头中的标志位,来表示三个颜色:黑色、灰色、白色。 - 黑色:表示对象已经被垃圾回收器访问过,且这对象的所有引用都已经扫描过。是存活对象。 - 灰色:表示对象已经被垃圾回收器访问过,但这个对象上至少存在一个引用还没有被扫描。 - 白色:表示对象尚未被垃圾回收器访问过。在可达性分析开始阶段,所有对象都是白色,在结束阶段,仍是白色的对象,代表不可达。 ## 并发标记时的问题 ### 多标-浮动垃圾 在初始标记阶段,不是垃圾,由于用户线程运行,在并发标记阶段变成垃圾,本轮GC不回收这部分内存。在并发标记阶段产生的新对象,都当成是黑色,不会清楚。 ### 漏标-读写屏障 通过增量更新和原始快照解决。虚拟机的记录操作都是通过写屏障实现。 - 增量更新:当黑色对象插入新的白色对象引用时,记录此引用,黑色对象变成灰色对象,下阶段重新扫描。 - 原始快照:当灰色对象要删除白色对象时,记录此引用,下阶段重新扫描此灰色对象。 ## jvm命令 ``` jdk8推荐使用:新生代ParNew + 老年代CMS -Xms128M -Xmx128M -Xmn64M -Xss1M -XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC jdk11推荐使用:G1 -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=32M -XX:MaxMetaspaceSize=32M -XX:+UseG1GC -XX:MaxGCPauseMillis=100 jdk17推荐使用:ZGC -Xms4g -Xmx4g -XX:+UseZGC --------------------------------------------------------------------------- -XX:+PrintGCDetails : 打印GC的详细信息 -XX:+PrintGCDateStamps :打印GC的时间戳 ---------------------------------------------------------------------- jmap : 查看内存信息 jmap -histo pid jmap -heap pid jmap -dump:format=b,file=xxx.hprof pid jhsdb jmap --heap --pid pid (jdk17) jstack : 查看进程 jstack pid jinfo : 查看 jvm 参数 jinfo -flags pid jinfo -sysprops pid jstat : 查看gc jstat -gc pid ```