为什么要了解虚拟机
- JVM不单单只支持Java语言,也支持其他语言(Scala、Kotlin、Groovy等等)
- 写出更好,更健壮的程序 (区块链1.0:比特币,区块链2.0:以太坊,以太坊中提供了EVM的虚拟机,它的实现和JVM类似,基于栈、生成脚本编译成字节码来执行)
- 提高java应用的性能,更好更快速的排除解决相关问题
Java SE体系架构
虚拟机的发展史
1) Hotspot VM 之前输入SUN公司后面被甲骨文公司收购,Hotspot什么意思:热点代码探测技术,及时编译器(发现最有价值的代码,如果代码用得非常多,就会把这些代码编译成本地代码)。
2) JRockit VM 号称”世界上最快的Java虚拟机” ,最后也被甲骨文公司收购
3) J9 VM 是属于IBM的 目前华为在使用
4) Dalvik VM google的产品
JVM 和 DVM 的区别
- 首要差别
Dalvik: 基于寄存器,编译和运行都会更快些
JVM: 基于栈, 编译和运行都会慢些 - 字节码的区别
Dalvik: 执行.dex格式的字节码,是对.class文件进行压缩后产生的,文件变小
JVM: 执行.class格式的字节码 - 运行环境的区别
Dalvik : 一个应用启动都运行一个单独的虚拟机运行在一个单独的进程中
JVM: 只能运行一个实例, 也就是所有应用都运行在同一个JVM中
未来的Java技术
- 模块化:使用得最多OSGI,应用层面就是微服务,互联网的发展方向
- 混合语言:多个语言都可以运行在JVM中
- 多核并行:CPU从高频次转变为多核心,多核时代。JDK1.7引入了Fork/Join,JDK1.8提出lambda表达式(函数式编程天生适合并行运行)
- 丰富语法:JDK5提出自动装箱、泛型(并发编程讲到)、动态注解等语法。JDK7二进制原生支持。try-catch-finally 至try-with-resource
- 64位:虽然同样的程序64位内存消耗比32位要多一点,但是支持内存大,所以虚拟机都会完全过渡到64位。
- 更强的垃圾回收:JDK11 –ZGC(TB级别内存回收)):有色指针、加载屏障
运行时数据区域
- 定义:Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域
- 类型:程序计数器、虚拟机栈、本地方法栈、Java堆、方法区(运行时常量池)、直接内存
程序的计数器 :指向当前线程正在执行的字节码指令的地址(行号)
- 为什么需要程序计数器
1)Java是多线程的,意味着线程需要切换
2)确保多线程情况下的程序正常执行(线程切换后能在正确的位置继续执行)
栈
- 栈: 栈是一种数据结构,入口和出口只有一个,遵循FILO原则(first in last out :先进后出)
- 为什么JVM要使用栈: 非常符合JAVA中方法间的调用(方法A调用方法B: A方法先开始最后结束,B方法最后开始最先结束)
- **方法递归的问题:**因为一个方法执行的时候放入虚拟机栈中,遵循先进后出的原则,如果方法又递归没有中断递归的判断的条件,最终的结果就是栈溢出错误,因为一直向虚拟机栈中添加方法没有出栈的操作,栈的大小是受限制的,迟早会装不下就溢出了。
虚拟机栈
每个线程私有的,线程在运行时,在执行每个方法的时候都会打包成一个栈帧,存储了局部变量表,操作数栈,动态链接,方法出口等信息,然后放入栈。每个时刻正在执行的当前方法就是虚拟机栈顶的栈桢。方法的执行就对应着栈帧在虚拟机栈中入栈和出栈的过程。
栈的大小缺省为1M,可用参数 –Xss调整大小,例如-Xss256k
- **局部变量表:**顾名思义就是局部变量的表,用于存放我们的局部变量的。首先它是一个32位的长度,主要存放我们的Java的八大基础数据类型,一般32位就可以存放下,如果是64位的就使用高低位占用两个也可以存放下,如果是局部的一些对象,比如我们的Object对象,我们只需要存放它的一个引用地址即可。
- **操作数栈:**存放我们方法执行的操作数的,它就是一个栈,先进后出的栈结构,操作数栈,就是用来操作的,操作的的元素可以是任意的java数据类型,所以我们知道一个方法刚刚开始的时候,这个方法的操作数栈就是空的,操作数栈运行方法是会一直运行入栈/出栈的操作
- **动态连接:**Java语言特性多态(需要类加载、运行时才能确定具体的方法),动态特性(Groovy、JS、动态代理)
- **返回地址:**正常返回(调用程序计数器中的地址作为返回)、异常的话(通过异常处理器表<非栈帧中的>来确定)
本地方法栈
本地方法栈保存的是native方法的信息
- 当一个JVM创建的线程调用native方法后,JVM不再为其在虚拟机栈中创建栈帧,JVM只是简单地动态链接并直接调用native方法
- 虚拟机规范无强制规定,各版本虚拟机本地方法栈自由实现,HotSpot直接把本地方法栈和虚拟机栈合二为一
线程共享的区域
- **方法区:**用于存储已经被虚拟机加载的类信息(类信息,常量(final),静态变量(static),即时编译期编译后的代码)可用以下参数调整方法区大小:
jdk1.7及以前:-XX:PermSize;-XX:MaxPermSize;
jdk1.8以后:-XX:MetaspaceSize; -XX:MaxMetaspaceSize
jdk1.8以后大小就只受本机总内存的限制
如:-XX:MaxMetaspaceSize=3M - Java堆: 保存对象实例(几乎所有) ,数组。
java堆的大小参数设置:
-Xmx 堆区内存可被分配的最大上限
-Xms 堆区内存初始内存分配的大小
直接内存
- **直接内存:**不是虚拟机运行时数据区的一部分,也不是java虚拟机规范中定义的内存区域;
1)如果使用了NIO(同步非阻塞IO,面向缓冲的),这块区域会被频繁使用,在java堆内可以用directByteBuffer对象直接引用并操作;
2)这块内存不受java堆大小限制,但受本机总内存的限制,可以通过MaxDirectMemorySize来设置(默认与堆内存最大值一样),所以也会出现OOM异常;
站在线程的角度来看JVM的运行数据区
深入辨析堆和栈
- 功能:
1)以栈帧的方式存储方法调用的过程,并存储方法调用过程中基本数据类型的变量(int、short、long、byte、float、double、boolean、char等)以及对象的引用变量,其内存分配在栈上,变量出了作用域就会自动释放;
2)而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中; - 线程独享还是共享:
1)栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。
2)堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。 - 空间大小:
1)栈的内存要远远小于堆内存,栈的深度是有限制的,可能发生StackOverFlowError问题。
虚拟机中的对象
- CAS(Compare and swap)比较和交换
CAS操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间会先比较旧值有没有变化,如果没有变化,才交换成新值,否则不进行交换
对象的内存布局
对象的访问定位
虚拟机优化技术——逃逸分析
-
逃逸分析
逃逸分析是目前JVM中比较前沿的优化技术,它不是直接的优化手段而是为其他优化手段提供依据的分析技术,逃逸分析的基本行为就是分析对象动态作用域。 -
几乎所有对象都在堆上分配,有特列情况对象是分配在栈上的。(一个对象如果没有逃逸出方法就可能分配在栈上)
在运行参数设置的VM options中设置:-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB
关闭逃逸分析得到结果:
-
牵涉到的JVM参数: + 是打开 - 是关闭
-XX:+DoEscapeAnalysis:启用逃逸分析(默认打开)
-XX:+EliminateAllocations:标量替换(默认打开)
-XX:+UseTLAB 本地线程分配缓冲(默认打开)