前言
最近看了深刻理解Java虚拟机第三版,整理了一些基础结构图,算是比较全的了,作一下笔记,你们一块儿学习。java
1.Java虚拟机运行时数据区图
JVM内存结构是Java程序员必须掌握的基础。
程序计数器程序员
- 程序计数器,能够看做当前线程所执行的字节码的行号指示器
- 它是线程私有的。
Java虚拟机栈面试
- 线程私有的,生命周期与线程相同。
- 每一个方法被执行的时候都会建立一个"栈帧",用于存储局部变量表(包括参数)、操做数栈、动态连接、方法出口等信息。
- 局部变量表存放各类基本数据类型boolean、byte、char、short等
本地方法栈算法
- 与虚拟机栈基本相似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。
Java堆数组
- Java堆是java虚拟机所管理的内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时建立。
- 其大小经过-Xms和-Xmx参数设置,-Xms为JVM启动时申请的最小内存,-Xmx为JVM可申请的最大内存。
方法区缓存
- 它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。 -能够经过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。
2. 堆的默认分配图
- Java堆 = 老年代 + 新生代
- 新生代 = Eden + S0 + S1
- 新生代与老年代默认比例的值为 1:2 ,能够经过参数 –XX:NewRatio 配置。
- 默认的,Eden : from : to = 8 : 1 : 1 ,能够经过参数–XX:SurvivorRatio 来设定
3.方法区结构图
方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。安全
4.对象的内存布局图
一个Java对象在堆内存中包括对象头、实例数据和补齐填充3个部分:数据结构
- 对象头包括Mark Word(存储哈希码,GC分代年龄等) 和 类型指针(对象指向它的类型元数据的指针),若是是数组对象,还有一个保存数组长度的空间
- 实例数据是对象真正存储的有效信息,包括了对象的全部成员变量,其大小由各个成员变量的大小共同决定。
- 对齐填充不是必然存在的,仅仅起占位符的做用。
5.对象头的Mark Word图
- Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等。
- 在32位的HotSpot虚拟机中,若是对象处于未被锁定的状态下,那么Mark Word的32bit空间里的25位用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,表示非偏向锁。
6.对象与Monitor关联结构图
对象是如何跟monitor有关联的呢?布局
一个Java对象在堆内存中包括对象头,对象头有Mark word,Mark word存储着锁状态,锁指针指向monitor地址。这实际上是Synchronized的底层哦~学习
7.Java Monitor的工做机理图:
Java 线程同步底层就是监视锁Monitor~,以下是Java Monitor的工做机理图:
- 想要获取monitor的线程,首先会进入_EntryList队列。
- 当某个线程获取到对象的monitor后,进入_Owner区域,设置为当前线程,同时计数器_count加1。
- 若是线程调用了wait()方法,则会进入_WaitSet队列。它会释放monitor锁,即将_owner赋值为null,_count自减1,进入_WaitSet队列阻塞等待。
- 若是其余线程调用 notify() / notifyAll() ,会唤醒_WaitSet中的某个线程,该线程再次尝试获取monitor锁,成功即进入_Owner区域。
- 同步方法执行完毕了,线程退出临界区,会将monitor的owner设为null,并释放监视锁。 。
8.建立一个对象内存分配流程图
- 对象通常是在Eden区生成。
- 若是Eden区填满,就会触发Young GC。
- 触发Young GC的时候,Eden区实现清除,没有被引用的对象直接被清除。
- 依然存活的对象,会被送到Survivor区,Survivor =S0+S1.
- 每次Young GC时,存活的对象复制到未使用的那块Survivor 区,当前正在使用的另一块Survivor 区彻底清除,接着交换两块Survivor 区的使用状态。
- 若是Young GC要移送的对象大于Survivor区上限,对象直接进入老年代。
- 一个对象不可能一直呆在新生代,若是它通过屡次GC,依然活着,次数超过-XX:MaxTenuringThreshold的阀值,它直接进入老年代。简言之就是,对象经历屡次滚滚长江,红尘世事,终于成为长者(进入老年代)
9.可达性分析算法断定对象存活
可达性分析算法是用来判断一个对象是否存活的~
算法的核心思想:
- 经过一系列称为“GC Roots”的对象做为起始点,从这些节点开始根据引用关系向下搜索,搜索走过的路径称为“引用链”,当一个对象到 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文件的字节流中包含的信息知足约束要求,保证这些代码运行时不会危害虚拟机自身安全
- 验证阶段有:文件格式校验、元数据校验、字节码校验、符号引用校验。
准备
- 准备阶段是正式为类中定义的变量(静态变量)分配内存并设置类变量初始值的阶段。
解析
- 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
初始化
- 到了初始化阶段,才真正开始执行类中定义的Java字节码。
15.类加载器双亲委派模型图
双亲委派模型构成
启动类加载器,扩展类加载器,应用程序类加载器,自定义类加载器
双亲委派模型工做过程是
若是一个类加载器收到类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器完成。每一个类加载器都是如此,只有当父加载器在本身的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试本身去加载。
为何须要双亲委派模型?
若是没有双亲委派,那么用户是否是能够本身定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的惟一性将没法保证,所以,双亲委派模型能够防止内存中出现多份一样的字节码。
16.栈帧概念结构图
栈帧是用于支持虚拟机进行方法调用和方法执行背后的数据结构。栈帧存储了方法的局部变量表、操做数栈、动态链接和方法返回地址信息。
局部变量表
- 是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。
- 局部变量表的容量以变量槽(Variable Slot)为最小单位。
操做数栈
- 操做数栈,也称操做栈,是一个后入先出栈。
- 当一个方法刚刚开始执行的时候, 该方法的操做数栈也是空的, 在方法的执行过程当中, 会有各类字节码指令往操做数栈中写入和提取内容, 也就是出栈与入栈操做。
动态链接
- 每一个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有引用是为了支持方法调用过程当中的动态链接(Dynamic Linking)。
方法返回地址
- 当一个方法开始执行时, 只有两种方式退出这个方法 。一种是执行引擎遇到任意一个方法返回的字节码指令。另一种退出方式是在方法执行过程当中遇到了异常。
17.Java内存模型图
- Java内存模型规定了全部的变量都存储在主内存中
- 每条线程还有本身的工做内存
- 线程的工做内存中保存了该线程中是用到的变量的主内存副本拷贝
- 线程对变量的全部操做都必须在工做内存中进行,而不能直接读写主内存。
- 不一样的线程之间也没法直接访问对方工做内存中的变量,线程间变量的传递均须要本身的工做内存和主存之间进行数据同步进行。
18.线程状态转换关系图
Java语言定义了6种线程池状态:
- 新建(New):建立后还没有启动的线程处于这种状态
- 运行(Running):线程开启
start()
方法,会进入该状态。
- 无限等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,通常
LockSupport::park()
,没有设置了Timeoout的Object::wait()
方法,会让线程陷入无限等待状态。
- 限期等待(Timed Waiting):处于这种状态的线程不会被分配处理器执行时间,在必定时间以后他们会由系统自动唤醒。
sleep()
方法会进入该状态~
- 阻塞(Blocked):在程序等待进入同步区域的时候,线程将进入这种状态~
- 结束(Terminated):已终止线程的线程状态,线程已经结束执行
19. Class文件格式图
- u一、u二、u四、u8 分别表明1个字节、2个字节、4个字节和8个字节的无符号数
- 表是由多个无符号数或者其余表做为数据项构成的复合数据类型
- 每一个Class文件的头四个字节被称为魔数(记得之前校招面试,面试官问过我什么叫魔数。。。)
- minor和major version表示次版本号,主版本号
- 紧接着主次版本号以后,是常量池入口,常量池能够比喻为Class文件里的资源仓库~
20.JVM参数思惟导图
JVM调优是通往高级开发的必经桥梁,因此好好积累JVM参数配置哈~
我的公众号
- 若是你是个爱学习的好孩子,能够关注我公众号,一块儿学习讨论。
- 若是你以为本文有哪些不正确的地方,能够评论,也能够关注我公众号,私聊我,你们一块儿学习进步哈。