java从源码到程序运行过程是java源程序到字节码文件,最后到运行结果的过程。
图 Java运行过程
Java编译器将java源文件(.java)转换成字节码文件(.class),类加载器(ClassLoader)将字节码文件加载进内存,然后进行字节码校验,最后Java 解释器翻译成机器码。
图 Java编译过程
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: 运行环境用到的核心类库,属性设置和资源文件;
在jvm中,通过类加载器将字节码文件(.class)加载进内存,java解释器翻译成对应的机器码,最后在操作系统解释运行。
图 jvm中java程序的执行情况
方法区:被加载的class文件解析后的相关信息,各个线程共享。具体内容:1)类型信息,完整有效名称,直接父类完整有效名称,修饰符(public、private);2)域信息(成员变量),域名称,类型、修饰符及声明顺序;3)方法信息,名称,返回类型,参数数量和类型(按顺序),修饰符,方法的字节码,操作数栈,局部变量表及大小,异常表,每个异常处理的开始位置、结束位置,代码处理在程序计数器中的偏移地址,被捕获的异常类的常量池索引;4)no-final的类变量;
堆区:存储该程序在运行时所有创建的对象及数组。一个JVM实例只存在一个堆内存,所有线程共享,包含新生区、养老区、永久区;
栈区:存储值类型,在线程创建时被创建,基本单位是栈帧;
图 栈区
PC寄存器:指令计数器,存储指向下一条指令的地址;
本地方法栈:和栈区功能类似,不同点在于栈区执行的是java方法,本地方法栈执行的是本地的方法;
当程序要使用某个类时, 如果该类还未被加载到内存中, 则系统会通过加载, 连接, 初始化三步来实现对这个类进行初始化。
图 类的初始化
加载就是将class文件读入内存, 并为之创建一个Class对象(任何类被使用时系统都会创建且只创建一个Class对象)。
JVM进行类加载阶段需要完成以下三件事情:
1. 通过一个类的全限定名称来获取定义此类的二进制字节流;
2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;
3. 在java堆中生成一个代表这个类的java.lang.Class对象, 作为方法区这些数据的访问入口;
类的加载时机:
1. 创建类的实例;
2. 使用类的静态变量或者为静态变量赋值;
3. 调用类的静态方法;
4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象;
5. 初始化某个类的子类;
6. 直接使用java命令来运行某个主类;
图 加载阶段
连接就是将类的二进制数据合并到JRE中。
连接分为以下三步:
1)检验,验证检查class文件数据的正确性。文本格式检验、元数据检验、字节码检验、符号引用检验、是否有正确的内部结构(构造器、方法、变量、代码块),并和其他类协调一致;
2)连接,正式为类变量分配内存并设置类变量初始值;这些变量所使用的内存将在方法区中进行分配, 此时进行内存分配的只包括类变量, 而不包括实例变量(实例变量将会在对象实例化时随着对象一起分配在Java堆中);
图 连接阶段
3)初始化,初始化为类的静态变量赋予正确的初始值, JVM负责对类进行初始化, 主要对类变量进行初始化, 在Java中对类变量进行初始值设定有两种方式:①声明静态变量(类变量)时指定初始值;②使用静态代码块为类变量指定初始值;
初始化步骤:
1. 假如这个类还没有被加载和连接, 则程序先加载并连接该类;
2. 假如该类的直接父类还没有被初始化, 则先初始化其直接父类;
3. 假如类中有初始化语句, 则系统依次执行这些初始化语句;