前言

最近看了深入理解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文件格式图

  • u1、u2、u4、u8 分别代表1个字节、2个字节、4个字节和8个字节的无符号数
  • 表是由多个无符号数或者其他表作为数据项构成的复合数据类型
  • 每个Class文件的头四个字节被称为魔数(记得以前校招面试,面试官问过我什么叫魔数。。。)
  • minor和major version表示次版本号,主版本号
  • 紧接着主次版本号之后,是常量池入口,常量池可以比喻为Class文件里的资源仓库~

20.JVM参数思维导图

JVM调优是通往高级开发的必经桥梁,所以好好积累JVM参数配置哈~

个人公众号

  • 如果你是个爱学习的好孩子,可以关注我公众号,一起学习讨论。
  • 如果你觉得本文有哪些不正确的地方,可以评论,也可以关注我公众号,私聊我,大家一起学习进步哈。

Java程序员必备基础结构图的更多相关文章

  1. Java程序员必备基础:JDK 5-15都有哪些经典新特性

    前言 JDK 15发布啦~ 我们一起回顾JDK 5-15 的新特性吧,大家一起学习哈~ 本文已经收录到github ❝ https://github.com/whx123/JavaHome ❞ 「公众 ...

  2. Java程序员必备基础:内部类解析

    前言 整理了一下内部类的相关知识,算是比较全,比较基础的,希望大家一起学习进步. 一.什么是内部类? 在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类.内部类本身就是类的一个属性 ...

  3. Java程序员必备的一些流程图

    Java程序员必备的一些流程图 转自https://juejin.im/post/5d214639e51d4550bf1ae8df 前言: 整理了一些Java基础流程图/架构图,做一下笔记,大家一起学 ...

  4. Java程序员必备的 15框开发工具

    15款Java程序员必备的开发工具 如果你是一名Web开发人员,那么用膝盖想也知道你的职业生涯大部分将使用Java而度过.这是一款商业级的编程语言,我们没有办法不接触它. 对于Java,有两种截然不同 ...

  5. Java 程序员必备的 15 个框架,前 3 个地位无可动摇!

    Java 程序员方向太多,且不说移动开发.大数据.区块链.人工智能这些,大部分 Java 程序员都是 Java Web/后端开发.那作为一名 Java Web 开发程序员必须需要熟悉哪些框架呢? 今天 ...

  6. Android java程序员必备技能,集合与数组中遍历元素,增强for循环的使用详解及代码

    Android java程序员必备技能,集合与数组中遍历元素, 增强for循环的使用详解及代码 作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985 For ...

  7. 干货收藏 | Java 程序员必备的一些流程图

    阅读本文大概需要 6 分钟. 转载自:https://juejin.im/post/5d214639e51d4550bf1ae8df 1.Spring 的生命周期 Spring 作为当前 Java 最 ...

  8. Java 程序员必备的一些流程图

    1.spring的生命周期 2.TCP三次握手,四次挥手 3.线程池执行流程图 4.JVM内存结构 5.Java内存模型 6.springMVC执行流程图 7.JDBC执行流程 8.spring cl ...

  9. 程序员必备基础:Git 命令全方位学习

    前言 掌握Git命令是每位程序员必备的基础,之前一直是用smartGit工具,直到看到大佬们都是在用Git命令操作的,回想一下,发现有些Git命令我都忘记了,于是写了这篇博文,复习一下~ https: ...

随机推荐

  1. SWUST OJ1065 无向图的连通分量计算

    无向图的连通分量计算 5000(ms) 10000(kb) 2555 / 5521 假设无向图G采用邻接矩阵存储,编写一个算法求连通分量的个数. 输入 第一行为一个整数n,表示顶点的个数(顶点编号为0 ...

  2. 1012 The Best Rank (25 分)

    To evaluate the performance of our first year CS majored students, we consider their grades of three ...

  3. 关于$f(x)=\int_0^x\left|\sin\frac1t\right|\text dt$求导的问题

    首先,我们考虑\(f(x)\)在\(\mathbb R\)上都是定义的.根据定义,显然有\(f(0)=0\):其次,对于\(x\neq0\),不妨先设\(x\gt0\),则有在\(t\rightarr ...

  4. spring中BeanPostProcessor之二:CommonAnnotationBeanPostProcessor(01)

    在上篇博客中分享了InstantiationAwareBeanPostProcessor接口中的四个方法,分别对其进行了详细的介绍,在文末留下了一个问题,那就是postProcessPropertie ...

  5. MyBatis(二):基础CRUD

    本文是按照狂神说的教学视频学习的笔记,强力推荐,教学深入浅出1便就懂!b站搜索狂神说即可 https://space.bilibili.com/95256449?spm_id_from=333.788 ...

  6. 2018蓝桥杯省赛(C/C++ C组)

    因进考场不让带优盘,顾想不起有些啥题了,静待更新吧! 再次强调C++最新标准,main函数必须指定返回类型为int,且返回值最好为0(人走的多了就是路了,有人偏返回999那也没办法) 1.大概是小明给 ...

  7. python 入门 之 Json 序列化

    开发网站,离不了Json 但是一般情况,不支持python的其它对象,怎么办? 有办法:Json 序列化!!! 总体来说,需要序列化的数据类型为 字典,类,嵌套类. 下面是我做的一个demo,都包含了 ...

  8. 一、华为模拟器eNSP下载与安装教程

    简单介绍一下 eNSP: eNSP是一款由华为提供的免费的图形化网络仿真工具平台,它将完美呈现真实设备实景(包括华为最新的ARG3路由器和X7系列的交换机),支持大型网络模拟,让你有机会在没有真实设备 ...

  9. Go语言 可变参数

    最近与同事讨论时,提到Go语言的可变参数,之前没有总结过相关知识点,今天我们介绍一下Go语言的可变参数. 可变参数(Variable Parameters):参数数量可变的函数称之为可变参数函数,主要 ...

  10. Tomorrow - 地形生成(1)

    原理很简单,请不要喷. 效果展示  种子输入框  种子为12345的地形  种子为23456的地形 代码展示 globalvar map random_set_seed(real(get_string ...