JVM-Java虚拟机
JVM与Java体系结构
TIOBE语言热度排行榜
JVM通过JSR-292规范基本实现在Java虚拟机平台上运行非Java语言编写的程序。
虚拟机分为系统虚拟机(一个可以运行完整操作系统的软件平台)和程序虚拟机(专门为执行单个计算机程序而设计)代表 JVM
JVM的特点
一次编译,到处运行
自动内存管理
自动垃圾回收功能
JVM代码执行流程(任一个环节失败,都无法通过编译)
Java源码(.java文件)→Java编译器→字节码(.class)→JVM→操作系统
Java编译器
词法分析→语法分析→语法/抽象语法树→语义分析→注解抽象语法树→字节码生成器
JVM
类加载器→字节码效验器→【翻译字节码(执行解析)/JIT编译器【保证性能】(编译执行)】执行引擎
二次编译
第一次是源文件编译成字节码文件
第二次是字节码文件中的字节码指令编译成机器指令并缓存放入方法区中
JVM的架构模型
Java编译器输入的指令流基本上是一种基于栈的指令集架构,另外一种是基于寄存器的指令集架构。
由于跨平台性的设计,Java的指令都是根据栈来设计的。优点是跨平台,指令集小,编译器容易实现;缺点是性能下降,实现同样的功能需要更多的指令,执行性能比寄存器差。
JVM的生命周期
虚拟机的启动 是通过引导类加载器创建一个初始类来完成,这个类是由虚拟机的具体实现指定的。
虚拟机的执行 程序开始执行它才运行,程序结束时结束。
执行一个所谓的Java程序的时候,真正在执行的是一个叫做Java虚拟机的进程。
JVM发展历史
Sun Classic VM 世界上第一款商用的Java虚拟机,jdk1.4时被淘汰。内部只提供解释器。
Exact VM 准确式内存管理。jdk1.2时,sun提供了此虚拟机,可以知道内存中某个位置的数据具体是什么类型。Solaris平台短暂使用,终被Hotspot虚拟机替换。
三大商用虚拟机
HotSpot VM 目前依旧在使用的虚拟机,jdk1.3以后都是默认虚拟机 ,HotSpot指的是特点代码探测技术;在服务器,桌面到移动端,嵌入式都有应用。
通过计数器找到最具编译价值代码,出发即时编译或栈上替换
通过编译器(执行性能)与解释器(负责响应时间)协同工作,在最优化的程序响应时间与最佳执行性能中取得平衡
BEA的 JRockit 专注于服务器端应用,不包含解释器(不关注程序的启动速度),全部代码都靠即使编译器编译后执行,世界上最快的JVM。
全面的Java运行时解决方案组合 -MissionControl服务套件(监控内存泄漏),是一套以极低的开销来监控丶管理和分析生产环境中的应用程序的工具。
J9 市场定位于HotSpot接近,服务器端,桌面应用,嵌入式等多用途
内存结构
类加载子系统
类加载子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识。
ClassLoader只负责class文件的加载,至于是否可以运行,由Execution Engine(执行引擎)决定。
加载的类信息存放于一块称为方法区的内存空间。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量。
类加载的过程
加载(狭义上)Loading
通过一个类的全限定名获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
连接-linking
验证(Verify) 目的在于确保class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全;主要包括四种验证:文件格式验证,元数据验证,字节码验证,符号引用验证
准备(Prepre) 为类变量分配内存并且设置该类变量的默认初始值,即零值。
这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化。
这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会锁着对象一起分配到Java堆中。
解析(Resolve)
将常量池内的符号引用转换为直接引用的过程
解析操作往往会伴随着JVM在执行完初始化之后再执行
符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在<Java虚拟机规范>的class文件格式中。直接引用就是直接指向目标的指针丶相对偏移量或一个简洁定位到目标的句柄。
解析动作主要针对接口,字段,类方法,接口方法,方法类型等。
初始化
初始化阶段就是执行类构造器方法<clinit>()的过程
此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
构造器方法中指令按语句在源文件中出现的顺序执行。
<clinit>()不同于类的构造器;虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。
类加载器的分类
JVM支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader).
一般来说,自定义类加载器就是程序员自己定义的类加载器,但是Java虚拟机规范是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器。
无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个。
虚拟机自带的加载器
启动类加载器(引导类加载器,Bootstrap ClassLoader)
这个类加载使用C/C++语言实现,嵌套在JVM内部。
它用来加载Java的核心类库(JAVA_HOME/jre/lib/rt.jar;resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类。
并不继承自java.lang.ClassLoader,没有父加载器。
加载扩展类和应用程序类加载器,并指定为他们的父类加载器。
出于安全考虑,Bootstrap启动类加载器只加载包名为java,javax,sun等开头的类。
扩展类加载器(Extension ClassLoader)
Java语言编写,由sun.misc.Lanucher$ExtClassLoader实现
派生于ClassLoader类
父类加载器为启动类加载器
从java。ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。
应用程序类加载器(系统类加载器,AppClassLoader)-Java应用程序的默认加载器
派生于ClassLoader类
父类加载器为扩展类加载器.
负责加载环境变量classpath或系统属性
ClassLoader
ClassLoader类,是一个抽象类,其后所有的类加载器都继承于它(除了引导类加载器Bootstrap ClassLoader)
双亲委派机制-工作原理
如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
如果父类加载器还存在其父加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
优势:①避免类的重复加载 ②保护程序安全,防止核心API被随意篡改
沙箱安全机制 -对核心源代码的保护(破坏引导类加载器,比如自创java.lang.String运行main方法)
其他
在JVM中表示两个class对象是否为同一个类存在两个必要条件:
类的完整类名必须一致,包括包名
加载这个类的ClassLoader(指ClassLoader实例对象)必须相同
类加载器的引用
JVM必须知道一个类是由那种具体的类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。
当解析一个类型到另一个类型的引用的时候,JVM需要保证这两个类型的类加载器是相同的。
类的主动使用和被动使用
主动使用,分七种情况:
创建类的实例
访问某个类或 接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射
初始化一个类的子类
Java虚拟机启动时被表明为启动类的类
JDK7开始提供的动态语言支持
除了以上7种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化