Java 虚拟机

Java虚拟机(Java Virtual Machine)是一种能够运行Java字节码的虚拟机。

实际上只要编译文件符合JVM对加载编译文件格式要求,任何语言都可以由JVM编译运行,例如JPythonScalaJRupy

总体结构

Java虚拟机主要被划分为四部分:

  • 类加载器(ClassLoader)
  • 运行时数据区(Runtime Data Area)
  • 执行引擎(Execution Engine)
  • 本地库接口(Native Interface)

Java是如何跨平台运行的?

Java虚拟机在这里充当了桥梁的作用,通过屏蔽了具体操作系统的API(不同平台JVM实现有所不同),只要生成Java字节码就可以在多平台不加修改的运行。

类加载器

类加载器(ClassLoader)负责把class加载到JVM。

Java文件再被JVM处理之前,需要编译成Java字节码格式的class文件,然后通过类加载器加载到JVM。

执行引擎

Java虚拟机通过模拟操作系统实现了一套虚拟架构,包括处理器堆栈寄存器等。

执行引擎(Execution Engine)就是模拟操作系统执行命令的原理,以指令为单位读取Java字节码

Java字节码指令都由操作码操作数组成。

Java字节码是实现跨平台的关键,所以在不同平台并不能直接执行,这就需要不同平台实现各自的执行引擎来生成各自可执行机器语言。


Java虚拟机中,存在三种执行方式:

解释器模式:读取字节码解释并且执行字节码指令,由于省去编译时间,应用启动快,但执行效率差,Python默认就是采用此方法。

JIT编译模式:通过把字节码编译成机器码达到提供执行效率的方法,由于在程序运行前需要编译,导致程序启动慢,但是执行效率高。

JIT(Just In Time)即时编译:在程序运行中对热点代码进行即时编译成机器码,从而提高程序运行效率。热点代码(HotSpotCode)可划分为:多次被调用的方法多次被执行的循环体

混合模式:上面两种模式的混合,在适合的时候选择合适的模式,从而解决两种模式存在的问题。

Java虚拟机到底采用那种模式呢?

实际上,不同的JVM采用了不同的方式,HotSpotVM才用了混合模式来执行引擎,而JRockitVM才用了JIT编译模式(并没有实现解析器)。
如果对解释和编译感兴趣的话,可以戳一下 为什么 JVM 不用 JIT 全程编译?

本地库接口

本地接口的作用是融合不同的编程语言为Java虚拟机所用,其一般被称为JNI(Java Native Interface)。

wikipediaJNI是一种编程框架,使得Java虚拟机中的Java程序可以调用本地应用/或库,也可以被其他程序调用

运行时数据区

运行时数据区Java虚拟机中最重要的组成部分,也常被称为JVM内存

JVM内存可分为以下五个部分

方法区线程共享,主要用于存储加载的类信息常量池静态变量JIT编译的代码等。


实际上不同JVM实现上也有所不同,

  • JDK1.7中,已经把放在永久代的字符串常量池移到中。
  • JDK1.8中,已经把放在永久代的静态变量移到中。
  • JDK1.8撤销永久代(Perm),引入元空间(Metaspace)(存放在本地内存,不在受JVM内存限制)。
  • 其他VM都不存在永久代,例如,JRockitVM就没有永久代

JDK1.8中JVM进程占用的内存会多出来1G?

由于JDK1.8默认开启了-XX:+UseCompressedClassPointersCompressedClassSpace默认值为1G,因此会发现JVM进程占用的内存会多出来1G。


线程共享,用于存储对象,是GC的主要区域。

C/C++不同之处就在于此,Java虚拟机为开发者省去了内存管理的麻烦,也降低了软件开发和维护的难度。

内存管理一般会涉及到内存释放的时机内存碎片并发清理内存垃圾的性能等问题。

具体GC算法可以戳 垃圾回收GC


虚拟机栈线程独占,用于存储局部变量表、操作栈、动态链接、方法返回地址等信息。

局部变量表:是用于存放方法参数和方法内部定义的局部变量,在编译期确定最大容量。
操作栈:执行引擎就是基于操作栈来工作的,在编译期确定最大容量。
动态链接:运行期加载的常量池中关联当前方法的符号引用


本地方法栈线程独占,用于执行本地方法(Native Method)


程序计数器线程独占,用于保存当前线程执行的内存地址,用于JVM程序多线程切换后恢复执行环境。

总结

Java虚拟机不仅提供了平台无关运行环境,并且接管了内存管理机制,从而使得Java开发、维护变得简单和流行。