1.Java 虚拟机运行时数据区图
JVM内存结构是java必须掌握的基础
程序技术器
- 程序技术器,可以看作当前线程所执行字节码的行号指示器
- 线程私有
JAVA虚拟机栈
- 线程私有的,生命周期和线程相同
- 每个方法被执行时创建一个栈帧,用于存储局部变量表(包括参数),操作数栈,动态链接,方法出口等信息
- 局部变量表存放各种基本数据变量类型boolean,byte,char,short等
本地方法栈
- 与虚拟机栈基本类似,区别在于虚拟机栈是在虚拟机执行java方法服务,而本地发放栈则是Native发放服务。
Java堆
- java堆是java虚拟机管理内存中最大的内存区域,也是被各线程共享的一块内存区域,在JVM启动时创建
- 其大小通过-Xms和-Xmx参数设置,-Xms为JVM启动时申请的最小内存,-Xmx为JVM可申请的最大内存。
发法区
- 用于存储虚拟机加载的类信息,常量,静态变量,是各个线程共享的内存区域
2.堆的默认分配图
- Java堆=老年代+新年代
- 新生代=Eden+s0+s1
- 新生代和老年代默认比例的值为1:2
3.方法区
方法区是各个线程共享的内存区域,用来存储已经被虚拟机加载后的类信息,常量,静态变量,还有即时编译后的代码缓存
4.对象的内存布局图
一个Java对象在堆内存中包括对象头,实例数据和不起填充
- 对象头包括Mark Word(存储哈希码,GC分代年龄等)和类型指针(对象指向它的类型元数据指针),如果是一个数据对象,还有一个保存数组长度的空间
- 实例数据是对象存储的有效信息,包括了对象的所有成员变量,大小由各个成员变量大小决定
- 对齐填充不是必然存在,仅仅起到占位作用
5.对象头的Mark Word图
Mark Word 用于存储对象自身的运行时的数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。
6.对象与Monitor关联结构图
一个java对象在堆中存储一个Java对象在堆内存中包括对象头,对象头有Mark word,Mark word存储着锁状态,锁指针指向monitor地址。这其实是synchronized底层实现的
7.java Monitor工作原理
- 想要获取monitor的线程,首先会进入Entry_List队列
- 当某个线程获取对象的monitor后,进入owner区域,设置为当前线程,同时计数器count加1
- 如果线程调用了wait()方法,则会进入_WaitSet队列。它会释放monitor锁,即将owner赋值为null,count自减1,进入WaitSet队列阻塞等待
- 如果其他线程调用notify和notifyAll,会唤醒waitSet中的某个线程,该线程再次尝试获取monitors锁,成功进入Owner区域
- 同步方法执行完毕了,线程退出临界区,会将monitor的owner设为null,并且释放监视锁
8.创建一个对象内存分配流程图
- 对象一般是在Eden区生成
- 如果Eden区满了,就会触发Young Gc
- 触发Young Gc的时候,Eden区实现清除,没有被引用啊的对象直接被删除
- 依然存活的对象,会被送到suivivor区域,Survivor=s0+s1
- 每次Young Gc时,存活的对象复制到未使用的那块suivivor区,当前正在使用的另一块Survivor区完全清除,接着交换两块Survivor区的使用状态
- 如果Young Gc 要移送的对象大于Survivor区上限,对象直接进入老年代
- 一个对象不能一直呆在新年代,如果它经过多次GC,依然活着,次数超过阈值,直接进入老年代
9.可达性分析算法判断对象存活
算法的思想:
- 同一系列称为”GC ROOTS“的对象作为起始点,从这些街道开始根据引用关系向下搜索,搜索走过的路径称为”引用链“,当一个对象到GC Roots 没有任何的引用链,证明此对象不可能被使用
10.标记-清除算法示意图
- 标记-清除算法是最基础的垃圾收集算法。
- 算法分为两个阶段,标记和清除。
- 首先标记出需要回收的对象,标记完成后,统一回收掉被标记的对象。
- 当然可以反过来,先标记存活的对象,统一回收未被标记的对象。
- 标记-清除 两个缺点是,执行效率不稳定和内存空间的碎片化问题~
11.标记-复制算法示意图
- 1969年 Fenichel提出“半区复制”,将内存容量划分对等两块,每次只使用一块。当这一块内存用完,将还存活的对象复制到另外一块,然后把已使用过的内存空间一次清理掉~
- 1989年,Andrew Appel提出“Appel式回收”,把新生代划分为较大的Eden和两块较小的Survivor空间。每次分配内存只使用Eden和其中一块Survivor空间。发生垃圾收集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上。Eden和Survivor比例是8:1~
- “半区复制”缺点是浪费可用空间,并且,如果对象存活率高的话,复制次数就会变多,效率也会降低。
12.标记-整理算法示意图
- 1974年,Edward 提出“标记-整理”算法,标记过程跟“标记-清除”算法一样,接着让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存~
- 标记-清除算法和标记整理算法本质差异是:前者是一种非移动式的回收算法,后者是移动式的回收算法。
- 是否移动存活对象都存在优缺点,移动虽然内存回收复杂,但是从程序吞吐量来看,更划算;不移动时内存分配更复杂,但是垃圾收集的停顿时间会更短,所以看收集器取舍问题~
- Parallel Scavenge收集器是基于标记-整理算法的,因为关注吞吐。CMS收集器是基于标记-清除算法的,因为它关注的是延迟。
13.垃圾收集器组合图
- 新生代收集器:Serial、ParNew、Parallel Scavenge
- 老年代收集器:CMS、Serial Old、Parallel Old
- 混合收集器:G1
14.类的生存周期
一个类从被加载到虚拟机内存开始,到卸载出内存为止。
加载阶段:
- 通过一个类的权限定名来获取二进制字节流
- 将字节流所代表的静态存储结构转化为方法区运行时数据结构
- 在内存中生成一个代表这类的java.lang.class对象,作为方法区这个类的各种数据访问入口
验证:
- 确保Class字节流中包含的信息满足约束要求,保证这些代码在运行时不会危害虚拟机自身安全
- 验证阶段有:文件格式,元数据,字节码,符号引用校验
准备:
- 正式为类中定义变量分配内存并设置初始值的阶段
解析:
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
初始化:
- 真正执行列中的字节码
15.栈帧概念结构图
栈帧是用于支持虚拟机进行方法调用和方法执行背后的数据结构。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址信息。
局部变量表
- 是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。
- 局部变量表的容量以变量槽(Variable Slot)为最小单位。
操作数栈
- 操作数栈,也称操作栈,是一个后入先出栈。
- 当一个方法刚刚开始执行的时候, 该方法的操作数栈也是空的, 在方法的执行过程中, 会有各种字节码指令往操作数栈中写入和提取内容, 也就是出栈与入栈操作。
动态连接
- 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。
方法返回地址
- 当一个方法开始执行时, 只有两种方式退出这个方法 。一种是执行引擎遇到任意一个方法返回的字节码指令。另外一种退出方式是在方法执行过程中遇到了异常。