JVM浅原理

1,什么是JVM

JVM是JRE的一部分。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台。所以,JAVA虚拟机JVM是属于JRE的,而现在我们安装JDK时也附带安装了JRE(当然也可以单独安装JRE)。

JVM相关的产品有很多, 通常最有名的莫过于现在Oracle公司所用的HotSpot 虚拟机。因此, 这里讨论的都是HotSpot虚拟机, 如果没有特别说明。

2,JVM架构图

  • JVM被分为三个主要的子系统:
    1. 类加载器子系统
    2. 运行时数据区
    3. 执行引擎

3,类加载子系统

类加载,是通过JVM的类加载器从JVM外部以二进制字节流的方式加载到JVM中;Java的动态类加载功能是由类加载器子系统处理。当它在运行时(不是编译时)首次引用一个类时,它加载、链接并初始化该类文件。

被java编译器编译过的.class文件 经过类加载器加载 、 验证、准备、解析、初始化之后, 才可以被使用。基本的过程如下:

3.1 加载

JVM本身有三种类加载器:BootStrap启动类加载器(负责从启动类路径中加载类,无非就是rt.jar。这个加载器会被赋予最高优先级)、Extension 扩展类加载器 (主要用于加载jre/lib/ext/下的jar)、Application 应用程序类加载器(加载classpath环境变量所指定的class);当然还有,自定义的类加载器(用于实现自己的类加载器, 如Tomcat中就实现多个类加载器,用来管理不同的jar)。

  • 加载:如果, 我有一个HelloWorld的类需要加载, 首先类加载器会去从最底层的类加载器去验证这个类是否被加载, 如果没有, 则委托给上一层的类加载器验证是否被加载, 如果到BootStrap类加载器都没有发现HelloWorld类被加载, 那么类加载器将执行加载任务, 如果BootStrap启动类加载器没有加载, 则委托给下一级的Extension类加载器去尝试加载,直到这个类被加载成功。 参考下图:

3.2 链接

  • 验证:其目的就是保证加载进来的.class文件不会危害到虚拟机本身, 且内容符合当前虚拟机规范要求。主要验证的内容大致有:文件格式、元数据验证、字节码验证、符号引用验证。其中文件格式验证, 主要确保符合class文件格式规范(如文本后缀为.class的文件将验证不通过), 以及主次版本号, 验证是否当前JVM可以处理等。元数据验证,主要验证编译后的字节码描述信息是否符合java语法规范。字节码验证, 其最为复杂, 主要通过控制流和数据流确定语义是否合法、符合逻辑。符号引用验证,可以看做是除自身以外(常量池中各种引用符号)的信息匹配校验,如通过持有的引用能否找到对应的实例。

  • 准备:正式为类变量分配内存,并设置类变量的初始值。这些变量都会在方法区中进行分配。

  • 解析:将常量池内的符号引用替换为直接引用的过程。主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄等。

3.3 初始化

  • 加载的最后阶段, 程序真正运行的开始。

4,JVM运行时数据区

既然类加载到JVM中, 那么数据如何真正的运行?如下图:

类加载进来,JVM是通过上图所示的区域来运行和管理这些加载进来的CLASS。即程序运行的是时候, 由上面逻辑单元来运行程序, 包括:方法区、堆、本地方法栈、栈、程序计数器(PC)五大部分组成。

4.1 方法区

用来存储已被虚拟机加载的类信息, 常量、静态变量、即时编译器编译后的代码等数据。每个JVM只有一个方法区,它是一个共享的资源。

4.2 堆

它是JVM用来存储对象实例以及数组值的区域,可以认为Java中所有通过new创建的对象的内存都在此分配,Heap中的对象的内存需要等待GC进行回收,每个JVM同样只有一个堆区。由于方法区和堆区的内存由多个线程共享,所以存储的数据不是线程安全的。

4.3虚拟机栈

所有的局部变量将在栈内存中创建。栈区是线程安全的,因为它不是一个共享资源;每个方法在执行时,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法调入则入栈, 方法执行完则出站。局部变量表存储各种基本类型数据(java的8种,其中long,double占用2个局部变量控件,其余数据占用1个)、对象引用(reference类型)。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时, 这个方法需要在帧中分配多大的局部变量空间是完全确定的。在方法运行期间是不会改变局部变量表的大小的。

4.4本地方法栈

此栈和JVM栈作用非常类似, 不同在于本地方法栈为虚拟机使用到的Native方法服务, 而JVM栈则是为Java执行的方法服务。Sun HotSpot虚拟机, 直接把本地方法栈和虚拟机栈合二为一。本地方法栈也会抛出StackOverFlowError和OutOfMemoryError异常。

4.5 程序计数器

可以看做是当前线程执行字节码的行号指示器。字节码解释器工作的时候就是通过这个计数器的值来选取下一条需要执行的字节码指令, 分支, 循环、跳转、异常处理、线程恢复等基础功能依赖计数器完成。

5,JVM垃圾回收

  • JVM垃圾回收原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)
  • 通常我们说的JVM内存回收总是在指堆内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的堆,而持久代则是之前提到的方法区。
  • 在java运行时区域中, 程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随程而灭。因为这几个区域的内存分配和回收都是具有确定性,这几个区域不需要过多考虑回收的问题。因为方法结束之后或线程结束之后, 内存自然就跟着回收了,而java堆和方法区不一样,一个接口中的多个实现类需要的内存可能不一样, 一个方法中的多个分支需要的内存也可能不一样,我们只有在程序处于运行期间时,才知道会创建哪些对象, 垃圾收集器关注的就是这部分内存。其也是动的。

垃圾收集器的区域如下图:

  • 垃圾收集本是有一套非常复杂的算法, 如果在方法区中进行垃圾收集, 那么其性价比极低的,因为垃圾回收主要收集永久带中的两部分内容:废弃的常量和无用的类。回收永久带中的常量和方法区非常相似。但是在堆中, 尤其是在新生代中,常规应用进行一次垃圾收集, 一般可以回收70%——95%的空间。而永久带的垃圾收集要远少于此;如上图所示, 每一个黑框中都是一个垃圾收集器, 对应特定的垃圾收集算法,来挺高整体的垃圾收集效率。
posted @ 2021-01-27 11:25  大个绿豆  阅读(270)  评论(0)    收藏  举报