编译到运行java程序大体流程
Java虚拟机的基本结构及其内存分区:
附加英文版的
JVM中把内存分为直接内存(非堆内存)、方法区、Java栈、Java堆、本地方法栈、PC寄存器等。
直接内存:就是原始的内存区,AIO操作时,数据直接在直接内存中发送到socket转送,零复制,避免了直接内存到堆间的复制。
JVM栈:线程之间私有,每个线程会对应自己的一个JVM栈,栈的基本出栈入栈单元称为栈帧,它是一个方法的表示,如主线程中第一压入的一定是main方法的栈帧,子线程就是run方法。栈帧中有局部变量表、操作数栈、返回地址、动态链接等。当栈内深度超过规定深度报StackOverFlowerError,当无法申请空间或空间小于一个JVM栈所需的内存大小,报OutOfMemoryError.执行引擎就是通过栈中字节码解释执行的。
本地方法栈:同JVM一样,这里不同的是。JVM栈为是字节码的方法服务,这里为native方法服务。
PC寄存器:功能如同CPU中的PC寄存器,指示要执行的字节码指令
Java堆:实例对象和数组存放的区域(因现在有逃逸技术。也可把对象存放在栈上),线程间共享,也是垃圾回收的主要局域,现在主要采用分代收集,分老年代、新生代。堆可以物理上不连续区域,只要逻辑上连续就可以。是runtime memory area最大的区域。
对新创建的对象划分内存方式有两种,若按规整划分使用碰撞指针方式;若非规整划分使用空闲表方式。垃圾回收算法决定了内存划分是否按规整,若回收算法中有compass使用规整划分,mark-swap标记整理的使用非规整划分。堆一般会有频繁的对象创建,难免在分配对象内存单元时,这种并发分配内存地址会出现线程不安全问题,JVM规范有两种解决方法,一是通过CAS加超时等待进行同步;二是按照每个线程,提前给予一定内存,这块内存称为TLAB(thread local allocation buffer)本地线程分配缓冲。对象的访问有句柄和直接指针方式。当堆内空间不足是OutofMemoryError
方法区:用于存放类、接口的元数据信息,加载进来的字节码的类信息、静态变量、常量、方法信息、编译后的代码等都存储在方法区。之中有还有个运行时常量池,class文件(字节码文件)中的常量池在类加载时放入到运行时常量池中。运行时可用intern将常量放入池中,Class对象中的getName、isInstance得到的信息来自于方法区。方法区是线程共享的,当方法区中内存空间不同时,会报OutOfMemoryError
JVM的功能模块主要包括类加载器、执行引擎和垃圾回收系统
类初始化
main方法 所在类会在执行前,先加载和类初始化。类初始化是从父类到子类自上到下
* 类初始化:先初始化父类,后子类初始
* 类初始化<clinit>():初始化类变量显示赋值代码、静态代码块,次序是自上到下,
* <clinit>()只执行一次*
* 在编译阶段,编译器收集所有的静态字段的赋值语句及静态代码块,
* 并按语句出现的顺序拼接出一个类初始化方法<clinit>()。
* 此时,执行引擎会调用这个方法对静态字段进行代码中编写的初始化操作。
1)类加载器会在指定的classpath中找到Son.class这个文件,然后读取字节流中的数据,将其存储在方法区中。
2)会根据Son.class的信息建立一个Class对象,这个对象比较特殊,一般也存放在方法区中,用于作为运行时访问Student类的各种数据的接口。
3)必要的验证工作,格式、语义等
4)为Son中的静态字段分配内存空间,也是在方法区中,并进行零初始化,即数字类型初始化为0,boolean初始化为false,引用类型初始化为null等。
在Son.java中只有一个静态字段:
private static int j=test();
此时,并不会执行赋值为test()返回值的操作,而是将其初始化为0。
5)由于已经加载到内存了,所以原来字节码文件中存放的部分方法、字段等的符号引用可以解析为其在内存中的直接引用了,而不一定非要等到真正运行时才进行解析。
6)在编译阶段,编译器收集所有的静态字段的赋值语句及静态代码块,并按语句出现的顺序拼接出一个类初始化方法()。此时,执行引擎会调用这个方法对静态字段进行代码中编写的初始化操作。
类实例初始
实例初始化<init>:先父类实例初始,后子类实例初始。
每个类实例初始过程:1.实例成员变量 、非静态代码块是 按序执行
2. 最后是自身构造方法, 若子类构造函数指定了使用父类的构造函数,
则父类初始该指定的构造函数
<init>()方法,是编译器将调用父类的<init>()的语句、构造代码块、实例字段赋值语句,
以及自己编写的构造方法中的语句整合在一起生成的一个方法。保证调用父类的<init>()方法
在最开头,自己编写的构造方法语句在最后,而构造代码块及实例字段赋值语句按出现的顺序
按序整合到<init>()方法中。
例子
/**
* 父类初始化<clinit>()
* 步骤1. j = method(); -> System.out.print("(5)");
* 步骤2. System.out.print("(1)");
* 父类实例初始化
* 步骤5. i = test();
* 步骤6.System.out.print("(3)"); -> System.out.print("(9)");
* 步骤7.System.out.print(i); //若不在子类中调有参的父类构造函数,则是默认的无参构造函数,System.out.print("(2)");
*/
class Father{
private int i = test();
private static int j = method();
static {
System.out.print("(1)");
}
public Father() {
System.out.print("(2)");
}
public Father(int i) {
System.out.print(i);
}
{
System.out.print("(3)");
}
public int test() {
System.out.print("(4)");
return 0;
}
public static int method() {
System.out.print("(5)");
return 0;
}
}
/**
*
* 子类初始化<clinit>()
* 步骤3. j = method(); -> System.out.print("(10)");
* 步骤4. System.out.print("(6)");
* 子类实例初始化
* 步骤8. i = test();-> System.out.print("(9)");
* 步骤9. System.out.print("(8)");
* 步骤10.System.out.print("(7)");
*
*/
public class Son extends Father{
private int i = test();
private static int j = method();
static {
System.out.print("(6)");
}
public Son() {
//super(); 第一行有一个默认的调用父类的无参构造方法
super(11); //显示的调用父类的有参构造方法
System.out.print("(7)");
}
{
System.out.print("(8)");
}
public int test() {
System.out.print("(9)");
return 0;
}
public static int method() {
System.out.print("(10)");
return 0;
}
/**
* main方法 所在类会在执行前,先加载和类初始化。类初始化是从父类到子类自上到下
* 类初始化:先初始化父类,后子类初始
* 类初始化<clinit>():初始化类变量显示赋值代码、静态代码块,次序是自上到下,<clinit>()只执行一次
*
*/
public static void main(String[] args) {
//实例初始化<init>:先父类实例初始,后子类实例初始。每个类实例初始过程:1.实例成员变量 、非静态代码块是 按序执行
//2. 最后是自身构造方法, 若子类构造函数指定了使用父类的构造函数,则父类初始该指定的构造函数
//注意;父类在初始 i = test()时是调用的子类的test(),因当前对象是Son且Son类中重学了父类的test();
Son son1 = new Son();
System.out.println();
Son son2 = new Son();
}
}
//结果
//(5)(1)(10)(6)(9)(3)11(9)(8)(7)
//(9)(3)11(9)(8)(7)