JVM历程
- Sun Classic VM 1996年发布,世界上第一款商用Java虚拟机,JDK1.4时被淘汰,现在hotspot内置了此虚拟机
- 这款虚拟机只提供了解释器(现在主流的虚拟机还会提供即时编译器JIT)
- 解释器和JIT两者用一个就可以让程序执行,但是只提供解释器的话,执行效率非常低。
- JIT即时编译器作用:如果发现有些代码反复执行(成为热点代码),JIT会将热点代码即时的编译成本地机器指令缓存到方法区。
- 但是在此款JVM中,JIT和解释器只会有一个同时工作。
- 现在虚拟机的主流是结合二者
- Exact VM jdk1.2时 提供 Exact Memory Management:准确式内存管理
- 用的时间很短
- 高性能虚拟机的雏形:
- 热点探测
- 编译器与解释器混合工作模式
- HotSopt VM SUN公司 最主流的虚拟机,
- 方法区:针对HotSpot讲的,J9和JRockit都没有方法区
- HotSpot就是指它的热点探测技术,
- 通过计数器找到最具编译价值代码,触发即时编译或栈上替换
- 编译器和解释器协同工作,最优化响应时间和最优化执行性能取平衡
- JRockit BEA公司(被甲骨文公司收购了)
- 专注于服务器端应用
- 不太关注程序启动速度,因此JRockit内部不包含解析器实现,全部代码都靠即时编译器编译后执行。
- JRockit虚拟机是世界上最快的虚拟机
- 优势:全面的Java运行时解决方案
- 面向延迟敏感型应用的解决方案JRockit Real Time
- MissionContro服务套件,一组以极低开销来监控管理分析生成环境中的应用程序的工具
- 专注于服务器端应用
- J9 IBM公司
- 市场定位和HotSpot接近
- 号称是世界最快,实际不如JRockit,在IBM自己的产品上去应用
- KVM和CDC/CLDC HotSpot
- Azul VM 和 BEA Liquid 高性能 虚拟机
- Azul特点:与特点的硬件平台绑定,软硬件配合的专有虚拟机
- Liquid特点:不需要操作系统支持,本身实现了一个专用的操作系统功能。
- Apache Harmony IBM和Intel联合开发开源
- Microsoft JVM 微软公司
- Taobao JVM 基于openJDK深度定制的
- Dalvik VM
- Graal VM
类加载的过程
类加载器的结构:
-
类加载器子系统的作用:
- 加载Class文件(物理磁盘上的文件)
- 只负责加载,能否运行则由Execution Engine决定
- 加载的类信息存放于一块成为方法区的内存空间。除了类信息外,方法区中还会存放常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是Class文件常量池部分的内存映射)
- ClassLoader是class文件进入JVM并转换为原数据放进方法区的搬运工
-
类的加载过程:加载、验证、准备、解析、初始化
-
Loading加载阶段:
- 通过一个类的全类名获取定义此类的二进制字节流,用classLoader.getResourceAsStream方法
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,也就是反射调用的对象,作为方法区这个类的各种数据访问入口。
- 加载到内存中的字节码文件的来源:
- 本地直接加载
- 网络获取 Web Applet
- 从zip压缩包读取,成为日后jar和war格式的基础
- 运行时计算生成,使用最多:动态代理
- 由其他文件生成,典型场景:JSP应用
- 从专有数据库获取.class文件,少见
- 从加密文件中获取,典型的防Class文件被反编译的保护措施
-
Linking链接阶段:
- 验证Verify:
- 确保Class文件的正确性,不危害虚拟机安全
- 四种验证:文件格式、元数据、字节码、符号引用
- 准备Prepare:
- 为类变量分配内存,并且设置类变量的默认初始值,即零值。此时各个变量都是零值,等到初始化阶段才会进行赋值。
- 这里不包含用final修饰的static,因为final在编译的时候就分配了,准备阶段会显式初始化,不会赋零值,而是直接赋给他值。
- 这里不会给实例变量分配初始化,类变量会分配在方法区中,而实例变量是随着对象一起分配到Java堆中的。
- 解析Resolve:
- 将常量池内的符号引用转换为直接引用的过程。
- 符号引用:一组符合来描述所引用的目标。例如,当一个类引用了另一个类,在编译该类的时候java并不知道你引用的类在哪,就用一些符号进行描述。
- 直接引用:直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。如果有了直接引用说明该引用指向的对象已经被加载进内存了,可以有明确的指向了。
- 解析主要针对类或接口、字段、类方法等。对应常量池中的CONSTANT_Class_info 这样的符号引用
- 将常量池内的符号引用转换为直接引用的过程。
- 验证Verify:
-
initialization初始化阶段
-
初始化阶段就是执行类构造器方法<clinit>()的过程
- 此方法不需要定义,Javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来
- 构造器方法中指令按语句在源文件中出现的顺序执行
- 如果类的定义中没有静态代码块和静态变量,是不会有<clinit>()方法的
- <clinit>方法在method一项中,
-
<clinit>()不同于类的构造器。
- 构造器是虚拟机视角下的<init>( )
- 若该类具有父类,JVM会保证子类的<clinit>( )执行前,父类<clinit>( )已经执行完毕
- 虚拟机必须保证一个类的<clinit>( )方法在多线程下被同步加锁,一个类如果没有被加载完,不会允许另一个线程进行加载
static class Father{ public static int A = 1; static { A = 2; } } static class Son extends Father{ public static int B = A; } public static void main(String[] args) { System.out.println(Son.B); // 2 } // 首先都是静态变量,都会进行赋值 // 执行顺序会确保先执行父类再执行子
-