目录
4.3 对象的创建过程
>class loading:
1. 将class文件load到内存
>class linking:
1. Verification 验证文件是否符合JVM规定 CAFE BABE
2. Preparation 静态成员变量赋默认值
3. Resolution 将类、方法、属性等符号引用解析为直接引用,常量池中的各种符号引用解析为指针、偏移量等内存地址的直接引用
>class initializing:
1.调用类初始化代码 <clinit>,给静态成员变量赋初始值 (<clinit>是静态语句块,<init>是构造方法)
>开始new对象:
>申请对象内存
>对象成员变量赋默认值 (t=0)
>调用构造方法<init>
1.成员变量按顺序赋初始值 (t=8)
2.执行构造方法语句
2.1 初始化super
2.2 执行自己的构造方法语句
4.4 对象在内存中的存储布局
1. 观察虚拟机配置
java -XX:+PrintCommandLineFlags -version
C:\Users\Administrator>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=400886336
-XX:MaxHeapSize=6414181376
-XX:+PrintCommandLineFlags
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
java version "1.8.0_131"
Java(TM) SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)
2.1 普通对象
1. 对象头:markword 8字节
2. ClassPointer指针:指向xx.class文件在内存中的地址
若虚拟机开启参数-XX:+UseCompressedClassPointers则占4字节,不开启为8字节
3. 实例数据:如对象的成员变量int m=8中的m,String s = "xx",s指向另一个地址
1. 基本类型:如int占4字节,byte占1字节
2. 引用类型:如String或其他对象类型,若开启-XX:+UseCompressedOops 为4字节 不开启为8字节
Oops: Ordinary Object Pointers
4. Padding对齐,为保证对象总大小是8的倍数,进行位数补齐
2.2 数组对象
1. 对象头:markword 8字节
2. ClassPointer指针同上 4/8字节
3. 数组长度:4字节
4. 数组数据
5. 对齐 8的倍数
public class T03_SizeOfAnObject {
public static void main(String[] args) {
System.out.println(ObjectSizeAgent.sizeOf(new Object()));//8+4+对齐4=16
System.out.println(ObjectSizeAgent.sizeOf(new int[] {}));//8+4+4=16
System.out.println(ObjectSizeAgent.sizeOf(new P()));// 32
}
//一个Object占多少个字节
// -XX:+UseCompressedClassPointers -XX:+UseCompressedOops
// Oops = ordinary object pointers
private static class P {
//8 _markword
//4 _class pointer
int id; //4
String name; //4
int age; //4
byte b1; //1
byte b2; //1
Object o; //4
byte b3; //1
}
}
4.6 对象头
至少包括3位的锁信息,一位是否偏向锁,2位的锁标志位,GC的标记(分带年龄)
看对象的状态来真正分配这64位
1. hashcode部分
31位hashcode->system.identityHashCode(...)
按原始内容计算的hashcode,重写过的hashcode方法计算的结果不会放在这里
如果对象没有重写hashcode方法,那么默认是调用os::random产生hashcode,可以调用System.identityHashCode获取
os::random产生hashcode的规则为next_random=(16807seed)mod(2*31-1),因此可以使用31位存储;另外一旦生成了hashcode,JVM会将其记录在markword中
当调用为重写的hashcode方法以及System.identityHashCode的时候
GC年龄默认为15,原因是分带年龄占4位,最多到15
当一个对象计算过identityHashCode之后,不能进入偏向锁状态
对象怎么定位
访问对象两种方式--句柄和直接指针_进阶的科技花园~-CSDN博客_句柄和直接指针
- 句柄池
- 直接指针
对象怎么分配
4.8 运行时数据区
方法区
方法区存储class文件
1. Perm Space (<1.8)
字符串常量位于PermSpace
FGC不会清理
大小启动的时候指定,不能变
2. Meta Space (>=1.8)
字符串常量位于堆
会触发FGC清理
不设定的话,最大就是物理内存
4.9 栈帧
1. Frame - 每个方法对应一个栈帧
1. Local Variable Table 局部变量表
2. Operand Stack 操作数栈
对于long的处理(store and load),多数虚拟机的实现都是原子的
jls 17.7,没必要加volatile
3. Dynamic Linking 动态链接
https://blog.csdn.net/qq_41813060/article/details/88379473
jvms 2.6.3
4. return address 返回地址
a() -> b(),方法a调用了方法b, b方法的返回值放在什么地方
栈溢出
虚拟机栈中存储每个方法的栈帧,当方法结束后这个栈帧就释放了,如果方法一直不结束就会一直占用虚拟机栈空间
字节码分析
int i = 8:
bipush 8: byte 8有符号扩展为int 8,压入main方法的操作数栈
istore_1: 将int 8从操作数栈出栈,将局部变量表下标1位置赋值为8
i = i++:
iload_1: 将局部变量表1位置的数取出,压入操作数栈[即完成读取i操作,i=8]
iincr 1 by 1: 将局部变量表1位置的数+1[即完成i++操作,局部变量表中i=9]
istore_1:将8弹出操作数栈,并赋给局部变量表1位置,最终局部变量表i=8
i = ++i:
iincr 1 by 1: 将局部变量表1位置的数+1[即完成i++操作,局部变量表中i=9]
iload_1: 将局部变量表1位置的数取出,压入操作数栈[即完成读取i操作,i=9]
istore_1:将9弹出操作数栈,并赋给局部变量表1位置,最终局部变量表i=9
5.1 invoke指令
1. InvokeStatic
2. InvokeVirtual
3. InvokeInterface
4. InovkeSpecial
可以直接定位,不需要多态的方法
private 方法 , 构造方法
5. InvokeDynamic
JVM最难的指令
lambda表达式或者反射或者其他动态语言scala kotlin,或者CGLib ASM,动态产生的class,会用到的指令
5.3 垃圾
如何找到垃圾
1. 引用计数RC(reference count),不能解决循环引用的垃圾团
2. 根可达算法RS(root searching)
5.4 垃圾清除算法
1. 标记-清除
适合老年代回收
2. 拷贝
适合新生代回收
3. 标记压缩
5.5 堆内逻辑分区
5.6 栈上分配
5.7 对象何时进入老年代
常见的垃圾回收器
JDK诞生时产生Serial
-> 为了提高回收效率诞生了PS
-> 为了缩短stop the world的时长, 诞生并发回收器CMS
CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,
但是CMS毛病较多,因此目前没有任何一个JDK版本默认是CMS
-> 为了配合CMS,诞生了PN
组合1:S+SO:不常用已被淘汰
组合2:PS+PO:生产环境默认组合
10G内存 PS+PO停顿时间
组合3:PN+CMS
PN vs PS
CMS
CMS的问题
1. Memory Fragmentation【内存碎片化】
内存碎片化可通过设置压缩来解决,代价是这个压缩过程会损失响应时间
-XX:+UseCMSCompactAtFullCollection 每次FGC时都进行压缩
-XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩
2. loating Garbage【浮动垃圾】
Concurrent Mode Failure 产生:
1.并发收集器无法在老年代用完之前完成垃圾对象的回收
并发清理阶段会产生新的垃圾,这些垃圾就是浮动垃圾,若果老年代满了但是这些浮动垃圾还没来得及清理,这时请出SO单线程回收
2.老年代中无法满足升代所需的内存,产生PromotionFailed问题,则触发STW,用户线程全停止,专门进行垃圾回收
CMS设计之初只为应付最多10G的内存,32G内存当old区满了,新生代年龄升级要往老年代放的时候,老年代满了
这是CMS会召唤SO进行单线程的回收,由于内存过大,导致这个STW过程很慢
解决方案:
降低触发CMS的阈值,保持老年代有足够的空间 –XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,尽早触发CMS清理,让CMS保持老年代足够的空间
算法
Card Table
由于做YGC时,需要扫描整个OLD区,效率非常低,所以JVM设计了CardTable, 如果一个OLD区CardTable中有对象指向Y区,就将它设为Dirty,下次扫描时,只需要扫描Dirty Card 在结构上,Card Table用BitMap来实现
并发标记算法-三色标记算法
G1
吞吐量方面,G1比PS降低10%-15%,但响应时间/暂停时间可以降低到200ms
追求响应时间用G1,追求吞吐量用PS
CollectionSet
RememberedSet
MixedGC
如果G1产生FGC,你应该做什么?
1. 扩内存
2. 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
3. 降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)