前言最近看了深入理解Java虚拟机第三版,整理了一些基础结构图,算是比较全的了,做一下笔记,大家一起学习。 1.Java虚拟机运行时数据区图JVM内存结构是Java程序员必须掌握的基础。 程序计数器 Java虚拟机栈 本地方法栈 Java堆 方法区 2. 堆的默认分配图3.方法区结构图方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。 4.对象的内存布局图一个Java对象在堆内存中包括对象头、实例数据和补齐填充3个部分: 5.对象头的Mark Word图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.可达性分析算法判定对象存活可达性分析算法是用来判断一个对象是否存活的~ 算法的核心思想: 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.垃圾收集器组合图14.类的生命周期图一个类从被加载到虚拟机内存开始,到卸载出内存为止,这个生命周期经历了七个阶段:加载、验证、准备、解析、初始化、使用、卸载。 加载阶段: 通过一个类的全限定名来获取定义此类的二进制字节流。 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
验证: 准备 解析 初始化 15.类加载器双亲委派模型图双亲委派模型构成 启动类加载器,扩展类加载器,应用程序类加载器,自定义类加载器 双亲委派模型工作过程是 如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。 为什么需要双亲委派模型? 如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证,因此,双亲委派模型可以防止内存中出现多份同样的字节码。 16.栈帧概念结构图栈帧是用于支持虚拟机进行方法调用和方法执行背后的数据结构。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址信息。 局部变量表 操作数栈 操作数栈,也称操作栈,是一个后入先出栈。 当一个方法刚刚开始执行的时候, 该方法的操作数栈也是空的, 在方法的执行过程中, 会有各种字节码指令往操作数栈中写入和提取内容, 也就是出栈与入栈操作。
动态连接 方法返回地址 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参数配置哈~ 个人公众号
|