一、JVM是什么
JVM全名叫“Java Virtual Machine”,中文名叫“爪哇虚拟机”,是java和java系(如Scala、Kotlin)语言实现平台无关性的关键角色。牛逼但也不玄乎,归根结底它只是一个软件而已,也就是运行于操作系统上的一个应用程序,与即时通讯软件、游戏这些应用程序没有本质区别。但还有一点要说,JVM是一个概念,或者说是一类软件。比如“即时通讯软件”包括QQ、微信、网易泡泡、飞信等等,“游戏软件”包括阴阳师、王者荣耀、和平精英等等。“JVM类软件”包括Oracle Hotspot(Oracle公司持有的Hotspot虚拟机)、Azul Systems Zing(Azul Systems公司基于Hotspot开发的主打低延迟的虚拟机)、Alibaba AJVM(阿里巴巴公司基于OpenJDK开发了自己的JDK,我们姑且称其中的JVM为“Alibaba JVM”,简称“AJVM”)等等。即:
即时通讯软件:
- QQ、微信、网易泡泡、飞信、......
游戏软件:
- 阴阳师、王者荣耀、和平精英、......
JVM类软件:
- Hotspot、Zing、AJVM、......
它们宏观上所处的位置大概是这样的,我顺便标明了Hotspot的作用。
二、JVM的结构是什么样的?
既然是个应用软件,那自然应该有很多内部模块和处理逻辑了,下面我们先看一下它大概长什么样?
这是Hotspot 在jdk1.8中结构的示意图,虽然配色有点野,可是结构该是对的(哪里不对可以评论教教我)。
我们可以看到,它内部大概分为了五部分:类装载器、运行时数据区、本地方法库、本地方法接口、执行引擎。下面逐一说明他们的作用及结构。
1.类装载器
功能
类装载器是用来装载Class文件的,具体的方式有
- 从本地系统中直接加载
- 通过网络下载.class文件
- 从zip,jar等归档文件中加载.class文件
- 从专有数据库中提取.class文件
- 将Java源文件动态编译为.class文件
加载流程
类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。其中的验证、准备、解析三个阶段也被称为连接阶段。
加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)
2.运行时数据区
程序运行需要一定的内存空间用来存储诸如程序的代码、类名、方法名、变量名、对象、对象的引用等,这个内存空间就被称为运行时方法区。根据存储的对象的不同,运行时方法区被分为了五个区域。分别是方法区、Java栈、堆、本地方法栈、程序计数器。
2.1 方法区(元空间):
方法区主要存放已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(比如spring 使用IOC或者AOP创建bean时,或者使用cglib,反射的形式动态生成class信息等)。它是线程共享的,需要考虑线程安全问题。
方法区在jdk1.8中的实现,叫做“元空间”。它被设计放在操作系统的内存之中。
ClassLoader
用来存放类加载器信息。
Class
用来存放类信息。
常量池
用来存放程序运行中的常量。
而在jdk1.8之前的jdk,如jdk1.6中,方法区的实现叫做“永久代”。
2.2 堆
英文名叫heap,作用是存储对象实体,即各种类的实例化对象。它是线程共享的,需要考虑线程安全问题。它是GC的主战场。
新生代
包含伊甸园区,幸存区。幸存区有两块,他们交替作为from区和to区。
老年代
存放从新生代幸存区晋升过来的大龄对象。当新生代连续空间不多的时候,也会有体积很大的对象在此处诞生。
StringTable
串池,当创建一个字符串变量时,会先从串池中搜索是否存在该字符串,若有,则直接引用,若没有,则创建该字符串并将其添加到串池,并引用。它的本质是一个HashTable,大小是固定的不可扩容。
- 常量池中的字符串仅是符号,第一次用到时才变为对象。
- 利用串池的机制,来避免重复创建字符串对象。
- 字符串变量的拼接原理是StringBuilder(1.8)。
- 字符串常量的拼接原理是编译期优化。
- 可以使用intern方法,主动将串池中还没有的字符串对象放入串池
- 1.8 将这个字符串对象尝试放入串池,如果有则并不放入,若没有则放入,会把串池中的对象返回。
- 1.6 将这个字符串对象尝试放入串池,如果有则并不放入,若没有则复制一份放入,会把串池中的对象返回。
动态拼接的字符串,不会放在串池,只会存在于堆中。除非主动调用intern方法。
(StringTable是从jdk1.7开始,从原来的方法区常量池挪到堆中的)
2.3 本地方法库
本地方法:在java中,存在一些由其他语言实现的方法,比如c++/c,它们通常是用来控制底层资源的方法。它们通常由native修饰。
本地方法库就是用来存放这些所需的本地方法的地方。
2.4 本地方法接口
程序在调用本地方法时,是通过其接口来调用的。这些端口就存放在这里。
2.5 执行引擎
即基于代码逻辑和数据进行运算的地方。是程序的实际执行者。
说明:
jdk1.7的更新:StringTable的移动
jdk1.8的更新:方法区的重新实现
GC:内容过多,需要单独开一篇来讲