目录
1.JVM简介
2.JVM运行流程
JVM运行时数据区
栈和程序计数器
堆
元数据区
3.JVM类加载
1.类加载过程
1.加载
2.验证
3.准备
4.解析
5.初始化
2.双亲委派模型
JVM,Java Virtual Machine的简称,java虚拟机.
虚拟机指的是通过软件模拟的具有完整硬件功能的,运行在一个完全隔离的环境中的完整计算机系统.
常见的虚拟机:JVM,VM wave,VirtualBox
JVM是通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他寄存器都进行了裁剪.是一台被定制过的现实中不存在的计算机
当前最流行的JVM是HotSpot VM,HotSpot指的就是他的热点代码探测技术,能通过计数器找到最具编译价值的代码,触发即时编译或栈上替换;通过编译器与解释器协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡.
JVM是Java运行的基础,也是实现一次编译到处运行的关键,我们看看JVM是如何执行的
程序执行前需要先把Java代码转换成字节码,也就是class文件
JVM首先需要把字节码通过一定的方式 类加载器(Class Loader)把文件加载到内存中 运行时数据区(Runtime Data Area).
字节码文件是JVM的一套指令规范集,不能直接交由底层操作系统执行,需要特定的命令解析器 执行引擎(Execution Engine)将字节码翻译成底层系统指令再交由CPU执行.而这个过程中需要调用其它语言的接口 本地库接口(Native Interfere)来实现整个程序的功能,这就是四个主要组成部分的职责和功能
看图理解
总结:JVM主要通过4个部分来执行Java程序,分别是:1.类加载器2.运行时数据区3.执行引擎4.本地库接口
接下来看看JVM运行时数据区
JVM运行时数据区也叫内存布局,当JVM启动的时候,会申请到很大的内存区域,JVM也是应用程序,需要从操作系统申请内存的,然后会根据需要,将整个空间分成若干个部分,每个部分有不同的功能作用
具体划分
Native Method Stacks表示的是JVM内部的C++代码,就是给调用native方法(JVM内部的方法)准备的栈空间
JVM Stacks 是JVM中的一个特定空间,对于JVM虚拟机栈,这里存储的是 方法 之间的调用关系,对于本地方法栈存储的是native方法之间的调用关系
栈是线程私有的(不是很严谨,例如主线程的局部变量,其它线程通过变量捕获是能访问到这个局部变量的)每个线程都会有自己的栈空间,线程是一个独立的执行流,可以使用jconsole查看java进程内部的情况,就可以看到所有线程,点击线程就能看到线程调用栈的情况
整个栈空间内部,也包含了很多元素,每个元素表示一个方法,把每个元素叫做"栈帧",这一个栈帧里会包含这个方法的入口地址,方法参数,返回地址,局部变量,栈上的内存是调用一个方法,就会创建栈帧,方法执行结束,就会销毁栈帧,出栈了.
栈上的空间是有限的,所以无限递归会出现栈溢出!栈的空间是可以通过参数设置的
注意:这里的栈和数据结构中的先进后出的栈是不同的 一个术语表示的含义是不同的
此处的栈也是后进先出的,数据结构栈是更广义的栈!
Program Counter Register是程序计数器,是一块很小的空间,记录的是当前线程执行到那个指令来了,是线程私有的
是整个JVM空间最大的区域,是线程共享的.
程序中创建的所有的对象都保存在堆中
常见的JVM参数设置:-Xms10m 最小启动内存是针对堆的,-Xmx10m 最大运行内存也是针对堆的
ms是memory start简称,mx是memory max简称
堆中分为两个区域:新生代和老生代,新生代放新建的对象,当经过一定的GC次数之后还存活的对象会放入老生代.新生代分为三个区域,s0s1eden
垃圾回收时会将endn中存活的对象放到一个未使用的s中,并把当前的endn和正在使用的s给清除掉
作用:存储被虚拟机加载的类信息,常量,静态变量,即时编译的代码等数据,Java虚拟机规范中叫做"方法区",jdk7叫永久代,jdk8叫元空间
是线程共享的,一个进程只有一个元数据区,多个线程公用
考点:给一段代码,问你某个变量在哪个区域上
原则:
1.局部变量在栈上
2.普通成员变量在堆上
3.静态成员变量在元数据区/方法区上
类加载过程就是.class文件,从硬盘被加载到内存(元数据区)中的过程
类加载过程:加载=>验证=>准备=>解析=>初始化
(验证=>准备=>解析)是连接阶段
加载是整个类加载过程中的一个阶段,和类加载不同.一个是loading,一个是class loading
加载阶段,Java虚拟机要做三件事:
1.通过一个类的全限定名获取定义此类的二进制字节流
2.将字节流代表的静态存储结构转化为方法区的运行时数据结构
3.在内存中生成一个代表这个类的Java.lang.Class对象,作为方法区这个类的各种数据的访问入口
简单来说就是找到.class文件并读取内容
是连接阶段的第一步,检查.class文件中的字节流包含的信息是否符合java虚拟机规范.运行后会不会对虚拟机产生危害等
验证选项:文件格式,字节码验证,符号引用验证
正式给类对象分配内存并设置类变量初始值,静态成员设为0值
Java虚拟机将常量池内的符号引用替换为直接引用的过程,也就是初始化常量的过程
字符串常量在保存时得有一个引用来存空间的起始地址,类加载之前,字符串常量是在.class中的,此时这个引用记录的并非是字符串常量的真正地址,而是在文件中的偏移量或者占位符(符号引用00)类加载之后,才真正把字符串常量放到内存中,此时才有"内存地址",这个引用才能真正赋值成指定的内存地址,就是符号引用=>直接引用
执行Java程序代码,对类对象内容进行初始化,运行类构造器,执行静态代码块
类加载是一个懒汉模式的过程,只有用到了某个类,才会加载,一旦加载之后,就不用重复加载了
站在Java虚拟机的角度,存在两种不同的类加载器:一种是启动类加载器(BootstrapClassLoader),这个类加载器使用C++实现,是虚拟机自身的一部分,另一种是其它所有的类加载器,都由java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoder
站在Java开发人员角度,自jdk1.2以后,Java一直保持着三层类加载器,双亲委的类加载架构器
那么什么是双亲委派模型呢?
简单来说就是当一个类加载器收到了类加载的请求,不会首先去尝试去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求都应该传输到最顶层的启动类加载器中,只有当父类加载器返回自己无法完成此请求(它的所搜范围中没找到所需加载的类)时,子加载器才会尝试自己去完成加载
BootstrapClassLoader:负责加载标准库中的类(Java规范,要求提供的哪些类)
ExtensionClassLoader:负责加载JVM扩展库中的类(规范之外的由JVM厂商/组织提供的类,提供额外的功能)
ApplicationClassLoader:负责加载用户提供的第三方库/用户项目代码 中的类
这三个类存在着父子关系,不是继承中的关系,是相当于每个类加载器有一个parent属性,指向自己的父 类加载器
这三个都是JVM自带的类加载器,用户也是可以定义类加载器的,用户定义的类加载器,加入到上述流程后,和三个自带的配合使用
双亲委派模型只是用来找到.class文件,JVM最核心的还是解析.class文件
双亲委派模型的优点:
1.避免类重复加载:如果A类和B类都有一个父类C类,那么A启动时就会将C类加载,那么B启动时就不需要再启动C类了
2.安全性:保证了Java核心API不被篡改,如果没有双亲委派模型,每个类加载器加载自己就会出现一些问题,如果我们编写了一个java.lang.Object类,就会出现多个不同的Object类,而有些类又是用户自己提供的,因此安全性得不到保障了(按照上述类加载流程,我们并不会先加载到自己写的类,而是先加载到顶层的类,再向下进行)