java进阶之一初识虚拟机

java进阶之一初识虚拟机

写在前面
  学习和使用java语言,JVM(java虚拟机)是必须要掌握的知识。jvm就是java号称一次编译到处运行的基础,它能够将,编译的 class 代码(字节码)翻译成底层操作系统可以运行的机器码并且进行调用执行,所以它是Java 程序能够运行的根本,像 Java 对象的创建、使用和销毁,还有垃圾回收以及某些高级的性能优化,例如,热点代码检测等功能都是在 JVM 中进行的。下面我们就来学习总结下,JVM的组成以及它的运行原理。
  我们知道,JVM是java程序的载体,市场上JVM的种类很多,各有特点,虽各有不同,但要遵循同一个规范,那就是《Java虚拟机规范》。在我写的所有博客中,提到的JVM都是指 HotSpot 虚拟机,它是 Sun/OracleJDK 和 OpenJDK 中的默认 JVM,也是目前使用范围最广的 JVM。
  下面一张图我们来看下java虚拟机的一个内存布局图,如下:
在这里插入图片描述

  从图中我们能能清晰的看到,内存布局中,有些区域是线程独占的,如:虚拟机栈、本地方法栈、程序技术器等。有些区域是线程共享的,如:堆和方法区。下面我们就来看下这些区域的作用。
  堆(Java Heap) 也叫 Java 堆或者是 GC 堆,它是一个线程共享的内存区域,也是 JVM 中占用内存最大的一块区域,Java 中所有的对象都存储在这里。堆大小的值可通过 -Xms 和 -Xmx 来设置(设置最小值和最大值),当堆超过最大值时就会抛出 OOM(OutOfMemoryError)异常。
  方法区(Method Area) 也被称为非堆区,用于和“Java 堆”的概念进行区分,它也是线程共享的内存区域,用于存储已经被 JVM 加载的类型信息、常量、静态变量、代码缓存等数据。
  程序计数器(Program Counter Register) 线程独有一块很小的内存区域,保存当前线程所执行字节码的位置,包括正在执行的指令、跳转、分支、循环、异常处理等。
  虚拟机栈也叫 Java 虚拟机栈(Java Virtual Machine Stack),和程序计数器相同它也是线程独享的,用来描述 Java 方法的执行,在每个方法被执行时就会同步创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。当调用方法时执行入栈,而方法返回时执行出栈
  **本地方法栈(Native Method Stacks)与虚拟机栈类似,它是线程独享的,并且作用也和虚拟机栈类似。只不过虚拟机栈是为虚拟机中执行的 Java 方法服务的,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
  总的来说, 类被加载后,方法区会被分出一块内存,存储这个类的所有信息,但是这个内存块存储的依然是.class文件,并不能被我们使用,我们还需要一个能被直接使用的对象,此时堆区就开始发挥作用。类的信息被存储在方法区后,jvm虚拟机又会堆区创建一个java.lang.Class对象,这个对象就好像方法区对应类的一个镜子,把方法区存储的类的结构全部反射过来,然后封装起来,成为了一个Class类的对象(此处运用到反射知识)。这个Class对象与对应的类是一对一服务,因为他有类的结构信息,所以他自然可以构造出一个类的对象。我们平时使用的对象就是由这个Class类的对象生成。到此,类的加载已经完成,但是此时依旧没有我们需要使用的对象产生。如下图:
在这里插入图片描述
  看完了JVM的内存模型,我们再来看下JVM的执行流程,如下图:
在这里插入图片描述
  由上图可知,JVM执行流程如下:
  第一步:把 Java 代码(.java)转化成字节码(.class)
  第二步:通过类加载器将字节码加载到内存中,所谓的内存也就是我们上面介绍的运行时数据区,但字节码并不是可以直接交给操作系统执行的机器码,而是一套 JVM 的指令集。
  第三步:需要使用特定的命令解析器也就是我们俗称的
执行引擎(Execution Engine)**将字节码翻译成可以被底层操作系统执行的指令再去执行,这样就实现了整个 Java 程序的运行,这也是 JVM 的整体执行流程。
由上面的执行流程可知,第二步类加载是很关键一步,这一步也是比较复杂,且必须掌握的一步。下面我们就来看类的加载过程,下图描述了类的加载过程:
在这里插入图片描述
  由上图可知,类加载一共分为以下几个阶段:1.加载阶段;2.验证阶段;3.准备阶段;4.解析阶段;5.初始化阶段。下面我们来看这几个阶段主要做了什么事情。
   1. 加载阶段
  此阶段用于查到相应的类(通过类名进行查找)并将此类的字节流转换为方法区运行时的数据结构,然后再在内存中生成一个能代表此类的 java.lang.Class 对象,作为其他数据访问的入口。
  注意:需要注意的是加载阶段和连接阶段的部分动作有可能是交叉执行的,比如一部分字节码文件格式的验证,在加载阶段还未完成时就已经开始验证了。
  2. 验证阶段
  此步骤主要是为了验证字节码的安全性,如果不做安全校验的话可能会载入非安全或有错误的字节码,从而导致系统崩溃,它是 JVM 自我保护的一项重要举措。
  验证的主要动作大概有以下几个:
  文件格式校验包括常量池中的常量类型、Class 文件的各个部分是否被删除或被追加了其他信息等;
  元数据校验包括父类正确性校验(检查父类是否有被 final 修饰)、抽象类校验等;
   字节码校验,此步骤最为关键和复杂,主要用于校验程序中的语义是否合法且符合逻辑;
  符号引用校验,对类自身以外比如常量池中的各种符号引用的信息进行匹配性校验。
  3. 准备阶段
  此阶段是用来初始化并为类中定义的静态变量分配内存的,这些静态变量会被分配到方法区上。
  HotSpot 虚拟机在 JDK 1.7 之前都在方法区,而 JDK 1.8 之后此变量会随着类对象一起存放到 Java 堆中。
  4. 解析阶段
  此阶段主要是用来解析类、接口、字段及方法的,解析时会把符号引用替换成直接引用。
  所谓的符号引用是指以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可;而直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
符号引用和直接引用有一个重要的区别:使用符号引用时被引用的目标不一定已经加载到内存中;而使用直接引用时,引用的目标必定已经存在虚拟机的内存中了。
  5. 初始化
初始化阶段 JVM 就正式开始执行类中编写的 Java 业务代码了。到这一步骤之后,类的加载过程就算正式完成了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值