JVM(一)初识

1、什么是JVM

  JVM是Java Virtual Machine(Java虚拟机)的缩写,是JRE的一部分,是Java平台的基石,是一种抽象的计算机,像真正的计算机一样,它具有指令集并在运行时操作各种内存区域。

  Java之所以可以Write once, run everywhere,就是因为Java语言使用Java虚拟机屏蔽了与具体平台的相关信息,使得Java语言编译只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上运行。

2、JDK/JRE/JVM的关系

  JDK是JRE的超集,包含JRE中所有的内容,以及开发小程序和应用程序所需的工具,例如编译器和调试器。JRE提供了库,Java虚拟机(JVM)和其他组件,以运行用Java编程语言编写的小程序和应用程序。请注意,JRE包括Java SE规范不需要的组件,包括标准和非标准Java组件。

  以下概念图说明了Oracle Java SE产品的组件:
在这里插入图片描述

3、源码到类文件

在这里插入图片描述

  假如现在有如下源码

public class Person {
    private String name;
    private Integer age;
    private static String address;
    private static final String job = "coding";

    public void say() {
        System.out.println("Person say……");
    }

    public int add(int val1, int val2) {
        return val1 + val2;
    }
}

  将源码编译(javac Person.java),得到如下内容,对应的含义可查看https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html

ca fe ba be 00 00 00 34 00 2a 0a 00 07 00 1a 09
00 1b 00 1c 08 00 1d 0a 00 1e 00 1f 09 00 06 00
20 07 00 21 07 00 22 01 00 04 6e 61 6d 65 01 00
12 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69
6e 67 3b 01 00 03 61 67 65 01 00 13 4c 6a 61 76
…………

4、 类文件到虚拟机

4.1、类加载机制

在这里插入图片描述

4.1.1、加载(Load)

查找和导入class文件

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在Java堆中生成一个代表这个类的java.lang.class对象,作为对方法区中这些数据的访问入口

4.1.2、链接(Link)

4.1.2.1、验证(Verify)

保证被加载类的正确性

  1. 文件格式验证
  2. 元数据验证
  3. 字节码验证
  4. 符号引用验证
4.1.2.2、准备(Prepare)

为类的静态变量分配内存,并赋默认值

4.1.2.3、解析(Resolve)

将类中的符号引用变为直接引用

4.1.3、初始化(Initialize)

对类的静态变量、静态代码块执行初始化操作

4.2、类加载器 ClassLoader

在加载(Load)阶段的第一步,通过类的全限定名获取此类的二进制字节流,需要借助类装载器完成,顾名思义,就是用来装载class文件的。

  1. 通过一个类的全限定名获取定义此类的二进制字节流

4.2.1、分类

Bootstrap ClassLoader

负责加载$JAVA_HOME中 jre/lib/rt.jar 里所有的class或 Xbootclasspath选项指定的jar包。由C++实现,不是ClassLoader子类.

Extension ClassLoader

负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中 jre/lib/*.jar 或 -Djava.ext.dirs指定目录下的jar包。

App ClassLoader

负责加载classpath中指定的jar包及 -Djava.class.path 所指定目录下的类和 jar包。

Custom ClassLoader

通过java.lang.ClassLoader的子类自定义加载class,属于应用程序根据 自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader。

4.2.2、图解

在这里插入图片描述

4.2.3、加载原则

  检查某个类是否已经加载:顺序是自底向上,从Custom ClassLoader到Bootstrap ClassLoader逐层检查,只要某个ClassLoader已加载,就视为此类已加载,保证此类只加载一次。
  类的加载顺序:从BootStrap ClassLoader到Custom ClassLoader逐层尝试加载此类,也就是从上到下。

双亲委派

定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回。只有父类加载器无法完成加载任务时,才会让子类加载器加载。

优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar中,无论那个加载器要加载这个类,最终都会委派给处于模型最顶端的启动类加载器进行加载,因为Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自行加载的话,那么系统中就会存在多种不同的Object类。

破坏:继承ClassLoader类,然后重写其中的loadClass方法,等等其他方法。

5、运行时数据区(Run-time Data Areas)

在加载阶段2,3步,可以发现有运行时数据、堆、方法区等名词

2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3. 在Java堆中生成一个代表这个类的java.lang.class对象,作为对方法区中这些数据的访问入口

说白了就是类文件被加载器装进来之后,类中的内容(比如变量、常量、方法,对象等这些数据)得要有个去处,也就是要存储起来,存储的位置肯定是在JVM中对应的空间。

5.1、官网概述

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5

  Java虚拟机定义了在程序执行期间使用的各种运行时数据区域。其中一些数据区域是在Java虚拟机启动时创建的,只有在Java虚拟机退出时才会被销毁。其他数据区域是每个线程的。每线程数据区在创建线程时创建,在线程退出时销毁。

5.2、图解

在这里插入图片描述

5.3、常规理解

5.3.1、方法区(Method Area)

  方法区是各个线程共享的内存区域,在虚拟机启动时创建。

  用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

  虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的是与Java堆区分开来。

  当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

值得说明

  1. 方法区在JDK8中就是Metaspace,在JDK6或7就是Perm Space
  2. Run-time Constant Pool

    Class文件除了类的版本信息、字段、方法、接口描述信息外,还有一项信息就是常量池,用于存放编译时期生成的各种字面量和符号引用,这部分将在类加载后进入方法区的运行时常量池存放。

5.3.2、堆(Heap)

  堆是各个线程共享的内存区域,在虚拟机启动时创建。

  Java对象实例以及数组都在堆上面分配

5.3.3、程序计数器(The pc Register)

我们都知道一个JVM进程中有多个线程在执行,而线程的内容是否能够拥有执行权,是根据CPU调度来的。

假如线程A正执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A在获得CPU执行权的时候,怎么能继续执行呢?这就需要线程中维护一个变量,记录线程执行到的位置。

  Java虚拟机可以同时支持多个执行线程。每个Java虚拟机线程都有自己的pc(程序计数器)寄存器。在任何时候,每个Java虚拟机线程都在执行单个方法的代码,即该线程的当前方法。如果该方法不是本机的,则pc寄存器包含当前正在执行的Java虚拟机指令的地址。如果线程当前执行的方法是本机的,那么Java虚拟机的pc寄存器的值是未定义的。Java虚拟机的pc寄存器足够宽,可以容纳特定平台上的返回地址或本机指针。

  如果线程正在执行Java方法,则计数器记录的是正在执行虚拟机字节码指令的地址;

  如果正在执行的是Native方法,则这个计数器为空。

5.3.4、虚拟机栈(Java Viraul Machine Stacks)

  每个线程都有一个私有的虚拟机堆栈,与线程同时创建。虚拟机堆栈类似于传统语言(如C)的堆栈:它保存局部变量和部分结果,并在方法调用和返回中发挥作用。因为虚拟机堆栈除了push和pop帧之外从不被直接操作,所以帧可以被堆分配。虚拟机堆栈的内存不需要是连续的。

  调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。


画图理解栈和栈帧
在这里插入图片描述

5.3.5、本地方法栈(Native Mehtod Stacks)

  如果当前线程执行的方法是Native类型的,这些方法就会在本地方法栈中执行

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值