JVM之加载class文件

1.JVM相关概念

官网地址:Java Platform Standard Edition 8 Documentation (oracle.com)

jvm: java虚拟机,是java程序的运行环境(java二进制字节码的运行环境)

jre:jvm+基础类库(java.lang包下工具类、IO、集合类库、线程类等)组成了完整的运行环境

jdk:是由jvm+jre+编译工具(javac、javap等)组成,它是开发人员开发、测试时的一个工具包。

2.HotSpot JVM架构

hotspot目前是最常用的jvm架构,可以通过java -version查看,其架构图如图所示:

由图中可以看出JVM结构主要分为三大模块:

a.class Loader SubSystem 类加载

b Runtime Data Areas 运行时数据区 又包含 方法区、堆栈、java虚拟机栈、程序计数器、本地方法栈

c.Execution Engine 执行引擎 又包含JIT 即时编译器 、垃圾回收器

2.1 Class File

程序员写的程序都是.java文件,而JVM是加载class文件的,那么第一个问题,如何将java文件转换为jvm加载的class文件呢?使用jdk自带的编译工具javac就可以将.java文件编译为.class文件,javac的编译过程主要是整合编译原理、C语言、java语言进行一系列的词法分析、语法树生成、字节码生成等等

比如我们将一段java代码编译成class文件后,class文件的内容都是16进制表示的字节码

该字节码对应的内容信息可参考官网:Chapter 4. The class File Format (oracle.com)

XML 复制代码
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];
}

举例分析: 比如u4就代表无符号位4字节 也就是对应上面16进制class文件中的前8位(2个为1个字节,1个字节为8bit,1位16进制数为4bit)

magic对应cafebabe。带着magic在官网上搜索。可以看到这么一句话: The magic item supplies the magic number identifying the class file format。代表的是这是一个class文件的标记

按上述方法,依次查找,就可以手动解析出整个class文件对应的含义。

当然,也可以采用jdk自带的反汇编命令javap对class文件进行反编译,就可以直接查看字节码信息和指令等信息

java 复制代码
javap ‐v ‐c ‐p User.class > User.txt 进行反编译,查看字节码信息和指令等信息

3. 类加载机制

从第2节我们了解到了class文件的生成以及如何解读,那么有了class文件,CPU想要运行我们的程序,我们首先就需要把class文件以二进制的形式加载到内存中的一片物理地址中,那么JVM首先要做的就是类加载。类加载又分为3部分,Loading装载、Linking链接、Initialization初始化

官网:Chapter 5. Loading, Linking, and Initializing (oracle.com)

3.1 Loading

Loading is the process of finding the binary representation of a class or interface type with a particular name and creating a class or interface from that binary representation。

说人话就是:装载就是把类和接口名以二进制的形式表示出来

加载的顺序是由顶向下:主要分为几部分 a.jre自带的一些基础jar包的加载(bootStrap加载器负责);b.java平台扩展的一些jar包;c.java应用中指定的jar包,可以理解为就咱们自己写的一些类;d.自定义的class。

问题: 假如说bootStrap已经加载了java.lang.String。而我们App ClassLoader也用到了App ClassLoader那么这个类如果被加载了两遍,就会出现new对象时找不到引用哪个的问题。因此,双亲委派机制就诞生了。双亲委派机制主要就是为了检查某个类是否已经加载了。

自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个Classloader已加 载,就视为已加载此类,保证此类只所有ClassLoader加载一次。

3.2 Linking

a. 首先保证被加载类的正确性

b.为类或接口的静态变量分配内存,并将其初始化为默认值(比如 private static int a = 10 会先将a初始化为0)

c. 将运行时常量池中的符号引用(指的是16进制的class文件中的常量池的一些16进制表示)转换为直接引用(物理内存中的一段地址)

3.3 Initialization

对类的静态变量,静态代码块执行初始化操作(a = 10)

4.运行时数据区

JVM通过类加载器将类信息,静态变量等信息加载到了内存中,那么java真正运行的时候还有方法、局部变量等信息,这些信息就会存储在运行时数据区中。运行时数据区可以以两个维度来划分进程生命周期(线程共享 全局变量)和线程生命周期 (局部变量)

方法区和堆栈都属于是进程生命周期,所有线程共享;java虚拟机栈、程序计数器、本地方法栈是线程生命周期(也就是线程安全的不存在并发问题)

官网地址:Chapter 2. The Structure of the Java Virtual Machine (oracle.com)

4.1Method Area(方法区)

方法区是各个线程共享的内存区域,在虚拟机启动时创建,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码(这个可以理解为代码中经常被使用的Util工具类)等数据。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

4.2 Heap(堆)

a.java堆是java虚拟机所管理内存中最大的一块,在虚拟机启动时创建,被所有线程共享

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

c.堆内存空间不足时,也会抛出OOM

JVM为了提高性能和内存开销,在堆中还分配了一分内存空间存储字符串,也就是字符串常量池,下图举例说明了字符串的存储方式。

代码验证:

java 复制代码
public class SCPDemo {
public static void main(String[] args) {
String str1="Jack"; // 这个常量一定会放到字符串常量池中
String str2="Jack";
String str3=new String("Jack");
String str4=str3.intern(); // 找字符串常量池中是否有该常量,如果有就直接返回,如果没有再创建
// equals 只会比较值 == 会比较地址
System.out.println(str1.equals(str2)); // true
System.out.println(str1==str2); // true
System.out.println(str1.equals(str3)); // true
System.out.println(str1==str3); // false
System.out.println(str1.equals(str4)); // true
System.out.println(str1==str4); // true
}
}

4.2.1 java对象内存布局

一个对象在内存中包括3个部分 对象头、实例数据和对齐填充。举个例子,假设我们有一个Student类,那么该类在内存中占多大内存呢?

java 复制代码
class Student{
    private double salary;
    private Object obj;
}

实例对象占用8+8 = 16 对象头 8+8+4 20 对齐填充为8字节的整数倍 因此该对象占用40字节的内存空间

4.2.2 方法区引用指向堆

4.2.3 堆指向方法区

4.3 Java Virtual Machine Stacks(Java虚拟机栈)

a. 虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创建。

b.每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。

代码示例:

java 复制代码
void a(){
}
void b(){
}
void c(){
}

流程图:

每一个栈帧中的局面变量表如果有实例对象引用,则指向堆内存

4.4 本地方法栈

4.5 The PC Register

如果线程正在执行java方法,则计数器记录的是正在执行的虚拟机字节码指令的地址。如果正在执行的是Native方法,则这个计数器为空。

5.总结

一个java程序的执行,首先经历编译阶段由.java文件编译为16进制class文件并存放在磁盘或IO设备中,然后JVM可以适用于各个操作系统,将class文件编译为CPU可以运行的二进制文件。JVM首先会经过类加载器,将类名接口名、静态变量、常量等进行初始化并加载到内存中用二进制表示,存储在运行时数据区的方法区中,java代码中的实例对象,全局变量,字符串常量等存储在堆栈中,线程共享;java虚拟机栈为java中的具体线程执行,会以栈结构存储每一个方法,每一个方法都是用栈帧来表示,栈帧中包含了局部变量、操作数栈、动态链接和调用完成,局部变量若有实例引用则指向堆栈,动态链接为指向本地方法栈;本地方法栈则主要指向native方法;程序计数器记录的则是正在执行java方法的虚拟机字节码的地址,若是native方法,则计数器为空。

相关推荐
小安运维日记13 分钟前
Linux云计算 |【第四阶段】NOSQL-DAY1
linux·运维·redis·sql·云计算·nosql
kejijianwen1 小时前
JdbcTemplate常用方法一览AG网页参数绑定与数据寻址实操
服务器·数据库·oracle
m0_741768854 小时前
使用docker的小例子
运维·docker·容器
学习3人组4 小时前
CentOS 中配置 OpenJDK以及多版本管理
linux·运维·centos
厨 神5 小时前
vmware中的ubuntu系统扩容分区
linux·运维·ubuntu
Karoku0665 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
geek_Chen015 小时前
虚拟机共享文件夹开启后mnt/hgfs/下无sharefiles? --已解决
linux·运维·服务器
(⊙o⊙)~哦5 小时前
linux 解压缩
linux·运维·服务器
最新小梦7 小时前
Docker日志管理
运维·docker·容器
鸡鸭扣7 小时前
虚拟机:3、(待更)WSL2安装Ubuntu系统+实现GPU直通
linux·运维·ubuntu