首页 > 代码库 > Java虚拟机结构

Java虚拟机结构

1、JVM的结构

技术分享

JVM主要由类加载器子系统、运行时数据区(内存空间)、执行引擎以及本地方法接口等组成。其中运行时数据区又由方法区、堆、Java栈、PC寄存器、本地方法栈组成。

从上图中还可以看出,在内存空间中方法区和堆是所有Java线程共享的,而Java栈、本地方法栈、PC寄存器则由每个线程私有

 

2. 类加载器子系统(Class Loader)

类加载器子系统负责加载编译好的.class字节码文件,并装入内存,使JVM可以实例化或以其它方式使用加载后的类。JVM的类加载子系统支持在运行时的动态加载,动态加载的优点有很多,例如可以节省内存空间、灵活地从网络上加载类,动态加载的另一好处是可以通过命名空间的分隔来实现类的隔离,增强了整个系统的安全性。

ClassLoader的分类

  • 启动类加载器(BootStrap Class Loader):负责加载rt.jar文件中所有的Java类,即Java的核心类都是由该ClassLoader加载。在Sun JDK中,这个类加载器是由C++实现的,并且在Java语言中无法获得它的引用。
  • 扩展类加载器(Extension Class Loader):负责加载一些扩展功能的jar包。
  • 系统类加载器(System Class Loader):负责加载启动参数中指定的Classpath中的jar包及目录,通常我们自己写的Java类也是由该ClassLoader加载。在Sun JDK中,系统类加载器的名字叫AppClassLoader。
  • 用户自定义类加载器(User Defined Class Loader):由用户自定义类的加载规则,可以手动控制加载过程中的步骤。

ClassLoader的工作原理

类加载分为装载、链接、初始化三步。

(1)装载

通过类的全限定名和ClassLoader加载类,主要是将指定的.class文件加载至JVM。当类被加载以后,在JVM内部就以“类的全限定名+ClassLoader实例ID”来标明类。

在内存中,ClassLoader实例和类的实例都位于堆中,它们的类信息都位于方法区。

装载过程采用了一种被称为“双亲委派模型(Parent Delegation Model)”的方式,当一个ClassLoader要加载类时,它会先请求它的双亲ClassLoader(其实这里只有两个ClassLoader,所以称为父ClassLoader可能更容易理解)加载类,而它的双亲ClassLoader会继续把加载请求提交再上一级的ClassLoader,直到启动类加载器。只有其双亲ClassLoader无法加载指定的类时,它才会自己加载类。

双亲委派模型是JVM的第一道安全防线,它保证了类的安全加载,这里同时依赖了类加载器隔离的原理:不同类加载器加载的类之间是无法直接交互的,即使是同一个类,被不同的ClassLoader加载,它们也无法感知到彼此的存在。这样即使有恶意的类冒充自己在核心包(例如java.lang)下,由于它无法被启动类加载器加载,也造成不了危害。 
由此也可见,如果用户自定义了类加载器,那就必须自己保障类加载过程中的安全。

(2)链接

链接的任务是把二进制的类型信息合并到JVM运行时状态中去。 链接分为以下三步:

  • 验证:校验.class文件的正确性,确保该文件是符合规范定义的,并且适合当前JVM使用。
  • 准备:为类分配内存,同时初始化类中的静态变量赋值为默认值。
  • 解析(可选):主要是把类的常量池中的符号引用解析为直接引用,这一步可以在用到相应的引用时再解析。

(3)初始化

初始化类中的静态变量,并执行类中的static代码、构造函数。 

JVM规范严格定义了何时需要对类进行初始化: 

  • 通过new关键字、反射、clone、反序列化机制实例化对象时。 
  • 调用类的静态方法时。 
  • 使用类的静态字段或对其赋值时。 
  • 通过反射调用类的方法时。 
  • 初始化该类的子类时(初始化子类前其父类必须已经被初始化)。 
  • JVM启动时被标记为启动类的类(简单理解为具有main方法的类)。 

3、运行时数据区

 

Java虚拟机结构