Java代码经历三个阶段:源代码阶段(Source) -> 类加载阶段(ClassLoader) -> 运行时阶段(Runtime)
首先我们来理清一下Java代码整个执行过程, 让我们对其有个整体的认识:
Java源程序(.java)经过Java编译器(javac)以后, 生成一个或多个字节码(.class)文件, JVM将每一条要执行的字节码通过类加载器ClassLoader加载进内存, 再通过字节码校验器的校验, Java解释器翻译成对应的机器码, 最后在操作系统解释运行.
当程序要使用某个类时, 如果该类还未被加载到内存中, 则系统会通过加载, 连接, 初始化三步来实现对这个类进行初始化:
加载就是将class文件读入内存, 并为之创建一个Class对象(任何类被使用时系统都会创建且只创建一个Class对象)
JVM进行类加载阶段需要完成以下三件事情:
1. 通过一个类的全限定名称来获取定义此类的二进制字节流
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3. 在java堆中生成一个代表这个类的java.lang.Class对象, 作为方法区这些数据的访问入口
类的加载的最终产品是位于堆区中的Class对象, Class对象封装了类在方法区内的数据结构, 并且向Java程序员提供了访问方法区内的数据结构的接口
类的加载时机
1. 创建类的实例
2. 使用类的静态变量或者为静态变量赋值
3. 调用类的静态方法
4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
5. 初始化某个类的子类
6. 直接使用java命令来运行某个主类
通俗的说就是只要用到了类的东西类就会加载
JVM在运行时会产生3个类加载器组成的初始化加载器层次结构
- Bootstrap ClassLoader 根类加载器
用C++编写
也被称为引导类加载器, 负责java核心类的加载 该加载器无法直接获取
比如System, String等, 在JDK中JRE的lib目录下rt,jar文件中
- Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载 jre/lib/ext目录下的jar包或-Djava,ext,dirs指定目录下的jar包装入工作库
- System ClassLoader 系统类加载器(加载自己写的类以及第三方类库(导入的jar包))
负责在JVM启动时加载来自java命令的class文件, 以及classpath环境变量所指定的jar包和类路径
连接就是将类的二进制数据合并到JRE中
连接分为以下三步:
验证 检查载入Class文件数据的正确性
-
-
- 文件格式检验:检验字节流是否符合Class文件格式的规范, 并且能被当前版本的虚拟机处理
- 元数据检验:对字节码描述的信息进行语义分析, 以保证其描述的内容符合Java语言规范的要求
- 字节码检验:通过数据流和控制流分析, 确定程序语义是合法、符合逻辑的
- 符号引用检验:符号引用检验可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验
-
是否有正确的内部结构(构造器, 方法, 变量, 代码块), 并和其他类协调一致
准备 该阶段正式为类变量分配内存并设置类变量初始值
这些变量所使用的内存将在方法区中进行分配, 此时进行内存分配的仅包括类变量, 而不包括实例变量(实例变量将会在对象实例化时随着对象一起分配在Java堆中),
另外, 在这里分配的静态类变量是将其值定义为默认值, 这里所设置的初始值通常情况下是数据类型默认的零值(如0, 0L, null, false等), 而不是被在Java代码中
被显式地赋予的值, 正确的赋值将在初始化阶段执行,
解析 将类的二进制数据中的符号引用替换为直接引用
比如说类中方法中的运算, 运算中符号a=1 去掉a直接变成1, 这样可以节约很多资源
初始化就是对类的静态变量, 静态代码块执行初始化操作
类初始化阶段是类加载过程的最后一步, 前面的类加载过程中, 除了加载(Loading)阶段用户应用程序可以通过自定义类加载器参与之外, 其余动作完全由虚拟机主导和控制, 到了初始化阶段, 才真正开始执行类中定义的Java程序代码
初始化为类的静态变量赋予正确的初始值, JVM负责对类进行初始化, 主要对类变量进行初始化, 在Java中对类变量进行初始值设定有两种方式:
-
- 声明静态变量(类变量)时指定初始值
- 使用静态代码块为类变量指定初始值
初始化步骤:
1. 假如这个类还没有被加载和连接, 则程序先加载并连接该类
2. 假如该类的直接父类还没有被初始化, 则先初始化其直接父类
3. 假如类中有初始化语句, 则系统依次执行这些初始化语句
JVM在堆内存中创建对象, 类的成员变量进入到堆内存中, 赋默认值
最后就是我们熟悉的Runtime运行时阶段
Person p = new Person();p,study();
执行上述代码会在堆内存创建一个Person类的对象, 并且在栈内存分配一块储存空间存放Person类型的引用变量p, p存放该对象的地址并且指向该对象, 调用p的study方法实际是, 对象通过Person类的字节码对象来访问方法区Person字节码的study方法
名词解释 :
Java源程序: 即Java源代码, 用java语言编写的程序
Java类加载器(Java Classloader): 是Java运行时环境(JRE)的一部分, 负责动态加载Java类到JVM的内存空间中
JRE: 即Java Runtime Environment, Java运行环境,内部包含了一个Java虚拟机以及一些标准类库(Jar包)
JAR包: 通常用于聚合大量的Java类文件、相关的元数据和资源(文本、图片等)文件到一个文件, 以便开发Java平台应用软件或库
JVM: 即Java Virtual Machine一种能够运行Java字节码(Java bytecode)的虚拟机
类(Class): 类是具有共同属性和行为的对象的集合, 类定义了对象的属性和方法
字节码: 字节码是已经经过编译, 但与特定机器码无关, 需要解释器转译后才能成为机器码的中间代码
Java字节码: 是Java虚拟机执行的一种指令格式
Java编译器: 将Java源文件(.java文件)编译成字节码文件(.class文件, 是特殊的二进制文件, 二进制字节码文件), 这种字节码就是JVM的“机器语言”, javac命令可以简单看成是Java编译器
Java解释器: 是JVM的一部分, Java解释器用来解释执行Java编译器编译后的程序, java命令可以简单看成是Java解释器
运行时类: 加载到内存中的字节码文件对应的类称为运行时类, 此运行时类即为一个Class的实例
Java堆(Heap): 在JVM启动时创建, 是JVM所管理的内存中最大的一块, 在JVM 中,堆(Heap)是可供各条线程共享的运行时内存区域, 也是供所有类实例和数组对象分配内存的区域, Java堆是被所有线程所共享的一块内存区域, Java堆是垃圾收集器管理的主要区域
Java栈(Stack): 在函数中定义的基本类型的变量、Java指令代码、对象的引用变量均在函数的栈内存中分配,当超过变量的作用域后,Java 会自动释放掉该变量分配的内存空间
方法区(Non-Heap): 方法区与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息(构造方法和接口定义)、常量、静态变量、即时编译器编译后的代码等数据, 运行时常量池存在方法区中
原文地址:https://www.cnblogs.com/yangshaox/p/11611696.html