JVM学习(一)类的加载和运行时的数据区

从官网上理解JVM

  1. 官网:https://docs.oracle.com/javase/8/

JDK,JRE,JVM 分布

  • Oracle有两种实现Java Platform Standard Edition(Java SE)8的产品:Java SE Development Kit(JDK)8和Java SE Runtime Environment(JRE)8。
  • JDK 8是JRE 8的超集,包含JRE 8中的所有内容,以及开发小程序和应用程序所需的工具,例如编译器和调试器。JRE 8提供了库,Java虚拟机(JVM)和其他组件,以运行用Java编程语言编写的小程序和应用程序。请注意,JRE包含Java SE规范不需要的组件,包括标准和非标准Java组件。
    在这里插入图片描述

从源码到类文件

源码

  • 源码也就是编译之前我们写的JAVA文件
class Person{
	private String name;
	private int age;
	private static String address;
	private final static String hobby="Programming";
	public void say(){
		System.out.println("person say...");
	}
	public int calc(int op1,int op2){
		return op1+op2;
	}
}

编译过程

  • Person.java -> 词法分析器 -> tokens流 -> 语法分析器 -> 语法树/抽象语法树 -> 语义分析器-> 注解抽象语法树 -> 字节码生成器 -> Person.class文件
  • 编译器底层是c写的

详解类文件

  • person.java 就转换成了 person.class,而 person.class 是JVM 能够看懂的二进制代码
cafe babe 0000 0034 0027 0a00 0600 1809
0019 001a 0800 1b0a 001c 001d 0700 1e07
001f 0100 046e 616d 6501 0012 4c6a 6176
612f 6c61 6e67 2f53 7472 696e 673b 0100
0361 6765 0100 0149 0100 0761 6464 7265
......
  • 想要看懂这个代码只能在官网上查看文档帮助,而一个class文件的组成是这样的
ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}
  • 通过这个来解释 class 文件
  1. magic:该magic项目提供标识class文件格式的幻数;它具有价值0xCAFEBABE。(也就是开头)
    在这里插入图片描述

  2. minor_version,major_version:

    • minor_version 和major_version项目 的值是此class文件的次要和主要版本号。主版本号和次版本号一起确定class文件格式的版本 。如果class文件的主要版本号为M,次要版本号为m,则将其class文件格式的版本表示为Mm。因此,class文件格式版本可以按字典顺序排序,例如1.5 <2.0 <2.1。
    • Java虚拟机实现可以支持class版本V的文件格式,当且仅如果V在于一些连续范围Mi.0 ≤ v ≤ Mj.m. Java虚拟机实现所遵循的Java SE平台的发行级别负责确定范围。JDK 1.0.2版中的Oracle Java虚拟机实现支持class文件格式版本45.0至45.3(含)。
    • JDK发行了1.1。*支持的class文件格式版本,范围从45.0到45.65535(含)。对于 k≥2,JDK版本1.k支持class45.0到44 + k.0之间的文件格式版本。
  3. 剩下的字段意思是

魔数与class文件版本
常量池
访问标志
类索引、父类索引、接口索引
字段表集合
方法表集合
属性表集合

Class文件加载到JVM中

  • 也即是类加载机制,通过下面的图解理解过程:
    在这里插入图片描述

装载:查找和导入class文件

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

    • 这个阶段是通过类装载器来装载的,而类装载器的种类有:
      在这里插入图片描述
    • 而如果这些类加载器都各自加载各自的资源的话,就有可能自定义的路径跟原本的系统jar包中的路径重叠,以至于不知道加载哪个,所以需要一种策略就是双亲委派策略
    • 双亲委派策略就是,从custom加载器向上传递,从顶部的bootstrap加载器先加载,如果没有再向下委派查找,只要某个Classloader已加载,就视为已加载此类,保证此类只所有ClassLoader加载一次。
      • 定义:如果一个类加载器在接到加载类的请求时,它首先不会自己尝试去加载这个类,而是把这个请求任务委托给父类加载器去完成,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
      • 优势:Java类随着加载它的类加载器一起具备了一种带有优先级的层次关系。比如,Java中的Object类,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载,因此Object在各种类加载环境中都是同一个类。如果不采用双亲委派模型,那么由各个类加载器自己取加载的话,那么系统中会存在多种不同的Object类。
      • 如何破坏呢?
        • 可以继承ClassLoader类,然后重写其中的loadClass方法
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

  3. 在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口

链接

  1. 验证:保证被加载类的正确性
    • 文件格式验证
    • 元数据验证
    • 字节码验证
    • 符号引用验证
  2. 准备:为类的静态变量分配内存,并将其初始化为默认值
  3. 解析:把类中的符号引用转换为直接引用(类似于 String str(符号引用) = 地址(直接引用))

初始化

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

运行时数据区

在这里插入图片描述

方法区(Method Area)

  • 方法区是各个线程共享的内存区域(非线程安全的),在虚拟机启动时创建(生命周期跟JVM一样)。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
  • Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池,用于存放编译时期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

堆(Heap)

  • Java堆是Java虚拟机所管理内存中最大的一块,在虚拟机启动时创建(生命周期跟JVM一样),被所有线程共享(非线程安全的)。
  • Java对象实例以及数组都在堆上分配。

虚拟机栈(Java Virtual Machine Stacks)

  • 虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创建。
  • 每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。
    在这里插入图片描述
  • 而当有些代码不是JAVA的是本地的,这个又怎么办呢

本地方法栈(Native Method Stacks)

  • 这个时候就需要用到本地栈了,本地栈就是存放当前线程执行的方法是Native类型的。
  • 但是如果本地方法栈方法执行完之后,又如何回到之前让出CPU的虚拟机栈的地址呢

程序计数器(The RC Register)

  • 这个时候就需要程序计数器,假如线程A正在执行到某个地方,突然失去了CPU的执行权,切换到线程B了,然后当线程A再获得CPU执行权的时候,怎么能继续执行呢?这就是需要在线程中维护一个变量,记录线程执行到的位置。
  • 程序计数器占用的内存空间很小,由于Java虚拟机的多线程是通过线程轮流切换,并分配处理器执行时间的方式来实现的,在任意时刻,一个处理器只会执行一条线程中的指令。因此,为了线程切换后能够恢复到正确的执行位置,每条线程需要有一个独立的程序计数器(线程私有)。
    • 如果线程正在执行Java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址;
    • 如果正在执行的是Native方法,则这个计数器为空。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值