最近看了深入理解Java虚拟机第三版,程序础结整理了一些基础结构图,员必算是备基比较全的了,做一下笔记,构图大家一起学习。程序础结
JVM内存结构是员必Java程序员必须掌握的基础。
程序计数器
程序计数器,备基可以看作当前线程所执行的构图字节码的行号指示器 它是线程私有的。Java虚拟机栈
线程私有的程序础结,生命周期与线程相同。员必 每个方法被执行的备基时候都会创建一个"栈帧",用于存储局部变量表(包括参数)、操作数栈、构图动态链接、程序础结方法出口等信息。员必 局部变量表存放各种基本数据类型boolean、备基byte、char、short等本地方法栈
与虚拟机栈基本类似,区别在于虚拟机栈为虚拟机执行的java方法服务,而本地方法栈则是为Native方法服务。Java堆
Java堆是java虚拟机所管理的云南idc服务商内存中最大的一块内存区域,也是被各个线程共享的内存区域,在JVM启动时创建。 其大小通过-Xms和-Xmx参数设置,-Xms为JVM启动时申请的最小内存,-Xmx为JVM可申请的最大内存。方法区
它用于存储虚拟机加载的类信息、常量、静态变量、是各个线程共享的内存区域。-可以通过-XX:PermSize 和 -XX:MaxPermSize 参数限制方法区的大小。方法区是各个线程共享的内存区域,它用于存储已被虚拟机加载的类型信息、常量、云服务器静态变量、即时编译器编译后的代码缓存等数据。
一个Java对象在堆内存中包括对象头、实例数据和补齐填充3个部分:
对象头包括Mark Word(存储哈希码,GC分代年龄等) 和 类型指针(对象指向它的类型元数据的指针),如果是数组对象,还有一个保存数组长度的空间 实例数据是对象真正存储的有效信息,包括了对象的所有成员变量,其大小由各个成员变量的大小共同决定。 对齐填充不是必然存在的,仅仅起占位符的作用。对象是如何跟monitor有关联的呢?
一个Java对象在堆内存中包括对象头,对象头有Mark word,Mark word存储着锁状态,锁指针指向monitor地址。Synchronized的底层跟这相关哦~
Java 线程同步底层就是监视锁Monitor~,如下是Java Monitor的工作机理图:
可达性分析算法是用来判断一个对象是否存活的~
算法的核心思想:
通过一系列称为“GC Roots”的对象作为起始点,从这些节点开始根据引用关系向下搜索,搜索走过的路径称为“引用链”,当一个对象到 GC Roots 没有任何的引用链相连时(从 GC Roots 到这个对象不可达)时,证明此对象不可能再被使用。一个类从被加载到虚拟机内存开始,到卸载出内存为止,这个生命周期经历了七个阶段:加载、验证、准备、解析、初始化、使用、卸载。
加载阶段:
通过一个类的全限定名来获取定义此类的二进制字节流。 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口验证:
验证的目的是确保Class文件的字节流中包含的信息满足约束要求,保证这些代码运行时不会危害虚拟机自身安全 验证阶段有:文件格式校验、元数据校验、字节码校验、符号引用校验。准备
准备阶段是正式为类中定义的变量(静态变量)分配内存并设置类变量初始值的阶段。解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。初始化
到了初始化阶段,才真正开始执行类中定义的Java字节码。双亲委派模型构成
启动类加载器,扩展类加载器,应用程序类加载器,自定义类加载器
双亲委派模型工作过程是
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己去加载。
为什么需要双亲委派模型?
如果没有双亲委派,那么用户是不是可以自己定义一个java.lang.Object的同名类,java.lang.String的同名类,并把它放到ClassPath中,那么类之间的比较结果及类的唯一性将无法保证,因此,双亲委派模型可以防止内存中出现多份同样的字节码。
栈帧是用于支持虚拟机进行方法调用和方法执行背后的数据结构。栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址信息。
局部变量表
是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。 局部变量表的容量以变量槽(Variable Slot)为最小单位。操作数栈
操作数栈,也称操作栈,是一个后入先出栈。 当一个方法刚刚开始执行的时候, 该方法的操作数栈也是空的, 在方法的执行过程中, 会有各种字节码指令往操作数栈中写入和提取内容, 也就是出栈与入栈操作。动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用, 持有引用是为了支持方法调用过程中的动态连接(Dynamic Linking)。方法返回地址
当一个方法开始执行时, 只有两种方式退出这个方法 。一种是执行引擎遇到任意一个方法返回的字节码指令。另外一种退出方式是在方法执行过程中遇到了异常。Java语言定义了6种线程池状态:
新建(New):创建后尚未启动的线程处于这种状态 运行(Running):线程开启 start()方法,会进入该状态。 无限等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,一般 LockSupport::park(),没有设置了Timeoout的 Object::wait()方法,会让线程陷入无限等待状态。 限期等待(Timed Waiting):处于这种状态的线程不会被分配处理器执行时间,在一定时间之后他们会由系统自动唤醒。 sleep()方法会进入该状态~ 阻塞(Blocked):在程序等待进入同步区域的时候,线程将进入这种状态~ 结束(Terminated):已终止线程的线程状态,线程已经结束执行JVM调优是通往高级开发的必经桥梁,所以好好积累JVM参数配置哈~