JVM 深入理解
程序计数器 线程私有 Java 虚拟机栈 线程私有 每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息 局部变量表 存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址) 本地方法栈 Native 方法服务 Java 堆 线程共享 存放对象实例和数组。内部会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)。可以位于物理上不连续的空间,但是逻辑上要连续 方法区 共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

运行时常量池
属于方法区一部分,用于存放编译期生成的各种字面量和符号引用。编译器和运行期(String 的 intern() )都可以将常量放入池中。内存有限,无法申请时抛出 OutOfMemoryError。
直接内存
非虚拟机运行时数据区的部分
-----------
对象的创建
1 首先当我们 new class对象时 会先编译成字节码.class 文件 (loading) app ext bootstrap 1.1首先会逐层询问 该类是否被父类加载过 直到顶级父类, 1.2如果有父类加载过此类,那么该类就由此父类全盘委托 1.3如果没有父类加载过此类 那么就由app加载该类 2(linking) 2.1 verification 校验 加载的类是否符合规范 是否以cafebaby 开头的class文件 2.2 preperation 准备阶段 将该类的静态变量初始化默认值 (例如:static int i=8 但是在此阶段为i=0) 2.3 resultion 解析阶段 将符号引用转换为直接引用 3(initlizing) 3.1 将静态变量 附初始值 此时 static int i=8 静态代码块 4 为新创建的对象 申请内存空间
4.1 首先判断该对象是否是普通对象
4.1.1 如果是普通对象
对象头-markword(对象标记 8字节)
对象头-classPointer(对象指针 普通对象默认开启了指针压缩【-XX:+UseCompressedClassPointers】 原8字节 压缩后4字节)
data(实例数据) 具体看成员变量类型而定 JVM 默认开启普通对象指针压缩 -XX:+UseCompressedOops(压缩普通对象指针--》成员变量指针 例如String name 会被压缩 因为默认开启了oops)如果是基本数据类型不压缩 引用类型8字节压缩为4字节
padding(对其) 8的倍数 如果前面不足8的倍数 padding 自动补齐(这里为什么会使8字节对齐? 因为L1缓存行大小为8字节 会出现缓存行污染 影响加载效率)
4.1.2 如果是数组对象
markword(对象头 8字节)
classPointer(指针 4字节)
数组长度arrLength(4字节)
数组数据arrStore(具体看数组类型)
padding 的倍数 如果前面不足8的倍数 padding 自动补齐
Object o=new Object(); 在内存中占? 普通对象 8+4+0+(4)=16 如果不开启指针压缩 8+8+0+0=16 也是16字节
1 此时 对象的引用 存储在栈内存中
2 new Object() 在堆中创建
3 Object 类的元信息 存放在方法区
 
这里的类型指针 是指向方法区的Person类指针
 
markword 上面说了 是8字节 也就是64位
我们在并发情况下需要解决线程安全问题的话要加锁 其实这个加锁 就是对对象加锁
在markword中有不同的标记状态
001 无锁
101 偏向锁
00 轻量级锁
10 重量级锁
11 GC 标记
 
4.2 markword 主要包含信息?
4.3 首先 对象会尝试在栈上分配 (线程私有 对象小 默认开启 栈上分配和TLAB 如果关闭这两个 那么运行效率会大打折扣--》因为对象并发创建 会争抢Eden区的资源而多消耗时间
栈上分配会 将Eden区的1% 划分为线程独有区域 每次当创建对象 会有限向TLAB 先分配 这样多线程时候就不会竞争Eden区空间 提高创建对象效率)
关闭逃逸分析
关闭标量替换
关闭TLAB
关闭这三个参数 那么创建对象的效率明显变低(默认是开启状态)
因为栈上分配 肯定比堆上分配 要快很多 而且 栈一旦弹出 整个生命周期结束 不需要垃圾回收
TLAB 不需要和其他线程争抢Eden区资源 效率高
4.2 如果栈上 无法分配 那么判断对象的大小 4.2.1 如果对象特别大 那么直接进入老年代(只能通过FullGC回收) 4.2.2 否则 进入TLAB (TLAB 也属于Eden区) 执行YoungGC 如果存活 进入s1 然后进行YongGC 进入s2 如果年龄到达15 或者 满足动态年龄判断 那么就进入老年代
youngGC:复制算法
FullGC: MC或者MS 算
对象何时进入老年代?


动态年龄:如果Eden+s1对象在yongGC时拷贝对象到s2时超过s2的50%那么 年龄最大的对象会直接进入老年代(old区)
--
JVM 虚拟机配置
java -XX:+PrintCommandLineFlags -version
---
常见的垃圾回收器
serial +serialOld STW ---》stop the world 线程停顿时间(单线程) 清理垃圾 safe Point---》 安全点停止
parallelScavenge +parallelOld(PS+PO) STW-->多线程清理垃圾
ParNew+CMS
CMS : concurrent mark sweap 并发回收(老年代 FullGC 算法)
1 初始标记 标记根可达对象
2 并发标记 在程序运行过程中与根可达对象关联的对象 进行标记
3 重新标记 在并发标记中 产生新的对象 重新标记
4 并发清理 并发清理中产生的垃圾 为浮动垃圾 为下一次垃圾回收的回收对象
使用CMS 会出现的问题
1 Memory Fragmentation(内存碎片化)
CMS对应内存不要超过32G 一旦老年代产生很多碎片 从年轻代过来的对象无法放入老年代 此时 CMS 会 让serialOld来清理垃圾(单线程 标记压缩垃圾 速度非常慢)
2 Floating Garbage(浮动垃圾)
如果出现 Concurrent Mode Failure 或者 PromotionFailed
解决方案: 降低触发CMS的阈值 保持老年代有足够空间
-XX:CMSInitiatingOccupancyFraction92%(原68%) 触发CMS FullGC
CMS 采用的算法 为三色标记算法 +incremental Update
G1 采用算法为 三色标记+SATB
ZGC colorPointer (颜色指针)
常见的垃圾回收算法:
1 标记清除-位置不连续 产生碎片 效率偏低(两遍扫描)
2 拷贝算法-没有碎片,浪费空间
3标记压缩-没有碎片,效率偏低(两遍扫描 指针需要调整)
 
                     
                    
                 
                    
                

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号