目录

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之后,不能进入偏向锁状态
对象怎么定位
访问对象两种方式--句柄和直接指针_进阶的科技花园~-优快云博客_句柄和直接指针
- 句柄池
- 直接指针
对象怎么分配

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.youkuaiyun.com/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【浮动垃圾】
浮动垃圾是所有并发/增量标记算法(包括增量更新和SATB)的固有特性: 是并发 GC 为了减少停顿时间(STW)所付出的代价——牺牲部分回收的即时性换取应用吞吐量。浮动垃圾会在下一次 GC 中被回收。
三色标记法的局限:在并发标记阶段,之前被标记为黑色(已扫描)的对象突然变为不可达,但由于黑色对象不会被重新扫描,这些对象就成为浮动垃圾,这些垃圾在重新标记阶段也不会被识别为不可达,以为重新标记关注的是新增的引用,而不是减少的引用,这些垃圾最终不会被此次GC回收
重新标记阶段的局限:重新标记阶段,如果a新增了到b的引用,则将a由黑色标为灰色,再次扫描a,如果a比较靠近root,那会比较耗时,这是他的缺点。重新标记阶段主要处理"漏标"(存活对象未被标记)问题,比如重新扫描a时就会把b由白色变成其他色,b就不会删除了。但是"错标"(已标记对象变为垃圾)问题解决不了,比如并发标记阶段a引用b,b已经标为黑色,但用户线程断开了a到b的引用,到重新标记阶段b是黑色的,不会被关注,在这次GC就不会被回收,成为浮动垃圾
标记与清理的时间差:CMS进行清理时,应用程序可能继续运行并生成新的垃圾对象,这些新对象无法在当前GC周期中被清理
Concurrent Mode Failure 产生:
1.并发收集器无法在老年代用完之前完成垃圾对象的回收
并发清理阶段会产生新的垃圾,这些垃圾就是浮动垃圾,如果老年代满了但是这些浮动垃圾还没来得及清理,这时请出SO单线程回收
2.老年代中无法满足升代所需的内存,产生PromotionFailed问题,则触发STW,用户线程全停止,专门进行垃圾回收
CMS设计之初只为应付最多10G的内存,32G内存当old区满了,新生代年龄升级要往老年代放的时候,老年代满了
这是CMS会召唤SO进行单线程的回收,由于内存过大,导致这个STW过程很慢
解决方案:
降低触发CMS的阈值,保持老年代有足够的空间 –XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,尽早触发CMS清理,让CMS保持老年代足够的空间G1通过记录引用变化(STAB)和Remembered Set来调整对象着色,避免浮动垃圾
算法
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%)






本文详细探讨了Java对象的创建过程,包括类加载、内存布局、对象头的组成以及对象在内存中的存储。同时,讲解了运行时数据区的结构,如方法区、栈帧和垃圾回收机制,涉及不同垃圾回收算法如标记-清除、拷贝和压缩。此外,还讨论了对象分配、栈上分配策略以及垃圾回收器的组合,如PS、CMS等,并分析了GC相关问题和优化策略。
1582

被折叠的 条评论
为什么被折叠?



