图 本文思维导图
一、概述
java从源码到程序运行过程是java源程序到字节码文件,最后到运行结果的过程。
图 Java运行过程
Java编译器将java源文件(.java)转换成字节码文件(.class),类加载器(ClassLoader)将字节码文件加载进内存,然后进行字节码校验,最后Java 解释器翻译成机器码。
图 Java编译过程
二、JVM
2.1 jdk、jre与 jvm
JDK(Java Development Kit): 是Java的开发工具包,是整个java开发的核心;
JRE(Java Runtime Environment): 是java程序的运行环境,所有的Java程序必须依赖jre才能运行;
JVM(Java Virtual Machine): java虚拟机,是一个虚拟的中间平台,只负责将编译后的字节码文件转换成当前计算机能理解并执行的指令;
图 jdk、jre与jvm的关系
JDK主要包含JRE、Java基础类库(Java API)和Java的一些工具包。
图 jdk1.8安装目录
bin: 可执行文件,包含javac文件(编译器)和java文件(源码编译器);
include: 包含一些头文件,用于java 和 JVM进行交互;
lib: 类库;
jre: java 运行环境;
(legal 法律文件)
图 jre目录
jre/bin: 与bin目录可执行文件相同,但是不包含javac文件;
lib: 运行环境用到的核心类库,属性设置和资源文件;
2.2 jvm 中java程序的执行情况
在jvm中,通过类加载器将字节码文件(.class)加载进内存,java解释器翻译成对应的机器码,最后在操作系统解释运行。
图 jvm中java程序的执行情况
方法区:虚拟机规范中将方法区看作是堆的逻辑部分,但在HotSpot实现上,将堆和方法区分开。堆中主要存放的是实例化的对象,方法区存放的是类的信息。
类型信息 | 1)完整有效名(包名.类名)。2)直接父类完整有效名。3)访问修饰符。4)直接接口的一个有序列表。 |
域信息 | 1)域(属性)名称。2)域类型。3)域修饰符。4)域的声明顺序。 |
方法信息 | 1)方法名称。2)返回类型。3)参数数量、类型及顺序。4)方法修饰符。5)方法的字节码。6)异常表。 |
表 方法区存储的类的信息
jdk1.6及之前 | 习惯把方法区称为永久代。静态变量存放在永久代上。 |
jdk1.7 | 永久代还存在,字符串常量池、静态变量从永久代移除,保存在堆中。 |
jdk1.8及之后 | 无永久代,由元空间取代。类的类型信息、字段、方法、常量保存在元空间,单字符常量池、静态变量仍在堆中。 |
表 HotSpot 方法区演进过程
元空间本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间不在虚拟机设置的内存中,而是使用本地内存。
元空间取代永久代的原因:
1、元空间的最大可分配空间就是系统可用内存,因为元空间并不在虚拟机中。
2、对永久代进行调优是很困难的。
堆区:存储该程序在运行时所有创建的对象及数组。一个JVM实例只存在一个堆内存,所有线程共享,包含新生区、养老区、永久区;
栈区:存储值类型,在线程创建时被创建,基本单位是栈帧;
图 栈区
PC寄存器:指令计数器,存储指向下一条指令的地址;
本地方法栈:和栈区功能类似,不同点在于栈区执行的是java方法,本地方法栈执行的是本地的方法;
2.2.1 常量池
在Java源文件编译后的字节码文件中,一个Java源文件,包含了许多变量、方法及字面量信息,这些信息在编译后不会直接存储在字节码文件中,而是在直接码文件中把这些信息的引用保存起来。这就是常量池塘。
而运行时常量池,是指在运行时会将字节码文件中的常量池加载到方法区中。
2.3 java类的初始化
当程序要使用某个类时, 如果该类还未被加载到内存中, 则系统会通过加载, 连接, 初始化三步来实现对这个类进行初始化。
图 类的初始化
2.3.1 加载
加载就是将class文件读入内存, 并为之创建一个Class对象(任何类被使用时系统都会创建且只创建一个Class对象)。
JVM进行类加载阶段需要完成以下三件事情:
1. 通过一个类的全限定名称来获取定义此类的二进制字节流;
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3. 在java堆中生成一个代表这个类的java.lang.Class对象, 作为方法区这些数据的访问入口;
类的加载时机:
1. 创建类的实例;
2. 使用类的静态变量或者为静态变量赋值;
3. 调用类的静态方法;
4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象;
5. 初始化某个类的子类;
6. 直接使用java命令来运行某个主类;
图 加载阶段
2.3.2 连接
连接就是将类的二进制数据合并到JRE中。
连接分为以下三步:
1)检验,验证检查class文件数据的正确性。文本格式检验、元数据检验、字节码检验、符号引用检验、是否有正确的内部结构(构造器、方法、变量、代码块),并和其他类协调一致。
2)准备,正式为类变量分配内存空间并设置初始值。
3)解析,虚拟机常量池的符号引用替换为直接引用。
图 连接阶段
2.3.3 初始化
执行类的类构造器<clinit>方法(为变量完成赋值)。
初始化步骤:
1. 假如这个类还没有被加载和连接, 则程序先加载并连接该类;
2. 假如该类的直接父类还没有被初始化, 则先初始化其直接父类;
3. 假如类中有初始化语句, 则系统依次执行这些初始化语句;