什么是JVM
-
JVM是java虚拟机(Java Virtual Machine)。java虚拟机是啥呢,就是跑java程序(.class文件)的虚拟机。
-
JVM最大的特点是跨平台,一次编译,多处运行。编译成.class文件后,可以在windows、linux等服务器运行。
-
如何实现的跨平台呢?jdk有linux的、windows的,32位的、64位的。通过jdk对系统的适配,实现跨平台。
-
JVM里都有啥?jvm包含类装载子系统、运行时数据区(常说的jvm内存结构)、字节码执行引擎。
-
类加载器子系统负责加载程序中的类型(类和接口)。执行引擎负责执行被加载类中包含的指令。虚拟机跑起来,当然需要内存,我们称为:运行时数据区(Runtime Data Area)
-
JVM内存结构中都有啥?多个线程公有的部分:堆、方法区(里面包含常量池)。线程私有的部分:栈、本地方法栈、程序计数器。
-
还有个概念是java内存模型(JMM)
下图不是java内存模型,是java内存结构。java内存结构是真实存在的,而java内存模型
JDK、JRE与JVM
上面简单介绍了JVM,可咱们平时接触的实物是JDK、JRE。他们之间有什么关系呢?
- JDK:JDK(Java Development Kit) 是 Java语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和lib合起来就称为jre。
- JRE:JRE(Java Runtime Environment,Java运行环境),包含JVM标准实现及Java核心类库。JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器)。
- JVM:JVM是Java Virtual Machine(Java虚拟机)的缩写,JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。
综上所述:jdk包含jre,jre包含jvm。
下面是官网对jdk、jre、jvm介绍的图片。图片取自
https://docs.oracle.com/javase/8/docs/index.html
从java文件到class文件
平时写程序写的是.java文件,这是程序源码,如何部署运行在服务器上呢?
- 通过开发工具如eclipse、idea,编写.java文件。
- 然后开发工具会编译成.class文件,或者通过javac命令将.java文件编译成.class文件,生成jar包或者war包。
- 服务器上安装jdk,如linux系统安装linux对应的jdk。
- 服务器上通过Tomcat等中间件,运行war包或者jar包。
这里还有java编译器的内容,以后学习
java运行时数据区
- java堆。线程公有
- 方法区。线程公有
- java虚拟机栈。线程私有
- 本地方法栈。线程私有
- 程序计数器。线程私有
java堆
堆是java虚拟机管理的最大一块内存,堆线程公有的一块内存,在虚拟机启动时创建。该区域是存放对象的实例,所有的对象都在堆上分配内存。
java堆是垃圾回收器主要工作的区域。
对可以按照可扩展来实现(通过-Xmx 和-Xms 来控制)
当队中没有内存可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。
方法区
方法区是线程公有的区域,存放已经被虚拟机加载的类信息、常量、静态变量、即时编译的代码等数据。
java虚拟机栈
java虚拟机栈是线程私有的,它的生命周期与线程相同。
java虚拟机栈是描述java方法执行的内存模型。每个方法在执行的同时,会创建一个栈帧,用于存储局部变量表、操作数栈、动态链表、方法出口等信息。每个方法从调用到执行完成的过程,对应着栈帧在虚拟机栈中入栈到出栈的过程。
虚拟机栈中存储的数据类型如下:
- 局部变量表,存放的是编译器可知的各种基本数据类型boolean、byte、char、short、int、float、long、double,对象引用
- 操作数栈
- 动态链表
- 方法出口
- 等等
每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。
通常所说的栈,一般是指在虚拟机栈中的局部变量部分。
局部变量所需内存在编译期间完成分配,
如果线程请求的栈深度大于虚拟机所允许的深度,则StackOverflowError。
如果虚拟机栈可以动态扩展,扩展到无法申请足够的内存,则OutOfMemoryError。
每个线程都分配一个栈内存区域。
每个方法对应一个栈帧。
本地方法栈
与java虚拟机栈类似,主要是为虚拟机使用到的native方法服务,同样是线程私有的
程序计数器
程序计数器是一块儿较小的内存空间,可以看成是当前线程所执行字节码行号指示器。
因为java的多线程是通过多个线程流转切换并分配处理器执行时间来实现的,因此在任意一个时刻,一个处理器只会执行一个线程的指令,因此为了线程切换后能恢复到指定位置,每个线程都有个独立的程序计数器,可以看出程序计数器是线程私有的。
若执行java方法,程序计数器记录的是当前指令的地址;
若执行native方法,记录的是空;
多个线程cpu调度时,会线程之间切换,如线程1切换到线程2,线程2再切换到线程1。当切换到线程2时就会根据程序计数器中记录的当前指令,继续执行
java内存结构总结
- 每个线程有独立的程序计数器、虚拟机栈、本地方法栈。
- 如果执行本地方法,会将该本地方法压入到本地方法栈中,并以动态链接的形式链接到本地方法中,程序计数器中存储的是undefined;如果执行的是java方法,会将java方法压入到java虚拟机栈中,程序计数器中存储的是方法的返回地址。
- 虚拟机栈中存有每个方法的栈帧,调用一个方法则压入一个栈帧。
- 栈帧中有局部变量表、操作数栈、动态链表、方法出口。
- 局部变量表中存储方法的参数变量若为引用类型,则存储的是针,指针指向堆中的对象。
- 堆中的对象指向方法区中的类信息,说明这个对象是什么类型。
- 堆中的对象包括对象头、实例数据、对齐填充(后面详解)
- 知道了java的内存结构,如何优化?(后面详解)
类加载子系统
- 类加载子系统是负责从文件系统或者网络中加载class文件到jvm内存中,class文件在文件开头有特定的文件标识。
- ClassIoader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
- 加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射)
字节码执行引擎
执行引擎是 Java 虚拟机最核心的组成部分之一。“虚拟机” 是一个相对于 “物理机” 的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、硬件、指令集和操作系统层面上的,而虚拟机的执行引擎则是由自己实现的,因此可以自行制定指令集与执行引擎的结构体系,并且能够执行哪些不被硬件直接支持的指令集格式。
在不同的虚拟机实现里面,执行引擎在执行Java代码的时候可能会有解释执行(通过解释器执行)和编译器执行(通过即时编译器产生本地代码执行)两种选择,所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。
每个字节码指令都由一个1字节的操作码和附加的操作数组成。