一.Java类执行步骤
我们要执行Hello.java,其代码如下:
public class Hello{
public static void main(String[] args){
System.out.println("hello,world!");
}
}
编译:
将java文件编译为JVM可读的字节码(class文件),我们经常使用的代码编辑器,在运行项目的时候,就会将我们写的java文件编译成字节码
javac Hello.java
//执行完成之后,会生成一个Hello.class
运行:
ClassLoader将字节码放到Java虚拟机中执行
java Hello
//运行结果: hello,world!
二. 类加载器ClassLoader
1. 功能作用
ClassLoader是类加载器, 程序在运行过程中,会根据需要,通过Java类加载器(ClassLoader)来加载某个Class文件到内存中,只有Class文件被加载到JVM中之后,才能被其他Class使用
2. 类加载步骤
2.1. 加载(Loading)
- 通过类全名,例如:java.lang.Integer
- 主要通过URLClassLoader, 查找并加载类的Class文件(字节码)到JVM方法区内(JDK1.8使用本地内存), 可以加载的Class文件如下:
- 加载磁盘class文件
- 加载网络Class文件
- 加载ZIP,Jar等归档文件
- 编译Java源文件
- 然后在堆内存创建一个Class对象,用来封装类在方法区的数据结构
/**
* URLClassLoader测试类
*/
public class URLClassLoaderTest{
public static void main(String[] args){
System.out.println("hello,world!");
}
}
2.2. 链接(Linking)
- 将各个class文件链接起来,配合工作
- 链接分为验证,准备,解析三步
2.2.1 验证
重新进行编译检查,验证类的正确性,确保被加载的类可以正确执行
2.2.2 准备
- 为类的静态变量分配内存空间
- 并初始化为一个默认值
2.2.3 解析
- 将类中的符号引用解析为直接引用
- 符号引用: 在java中没有指针类型,某个对象就是一个符号引用
- 直接引用: 指针指向某个地址空间,就是直接引用
2.3 初始化(Initialzation)
- 为类的静态变量赋值
- 执行静态代码块的代码
- 类代码执行步骤:
- 多个静态代码块和静态变量按顺序执行
- 在对象创建过程中会先初始化成员变量
- 然后执行构造代码块
- 最后再执行构造方法
2.3.1 初始化测试
public class JVM {
//静态成员变量
public static int num1;
static {
//静态代码块,打印日志和赋值
System.out.println("static1 num1 = " + num1);
num1 = 2;
System.out.println("static2 num1 = " + num1);
}
public static void main(String[] args) {
//main方法打印num1
System.out.println("main num1 = " + num1);
}
}
//运行结果:
static1 num1 = 0
static2 num1 = 2
main num1 = 2
- 运行结果分析
- 首先ClassLoader准备内存空间,把静态变量num1设置为默认值0
- 然后按顺序执行static代码块,打印num1的值
- 然后再执行num1的赋值语句
- 最后再打印num1的值
2.3.2 在哪些情况下初始化类
如果JVM没有使用类,那么类的加载过程到链接这一步就结束了,不会执行初始化的代码,也就是类需要我们主动使用,才会初始化类,类的主动使用有下面六种情况:
- 初始化一个类的实例
- 访问某个类的静态变量,或者为类的静态变量赋值
- 调用了类的静态方法
- 使用反射,实例化一个对象
- 初始化一个子类,那么父类也会被初始化
- JVM启动时标明了要启动的类,例如文章开头的java Hello
public class JVM {
// 静态成员变量
public static int num1;
static {
// 静态代码块,打印日志和赋值
System.out.println("JVM static1 num1 = " + num1);
num1 = 2;
System.out.println("JVM static2 num1 = " + num1);
}
public JVM() {
// 构造函数,赋值为3
num1 = 3;
}
public static void test() {
//静态方法 打印值
System.out.println("JVM test num1 = " + num1);
}
}
(1). 初始化一个类的实例
//JVM测试类
public class JVMTest {
public static void main(String[] args) {
JVM jvm = new JVM();
System.out.print("JVMTest JVM.num1 = " + JVM.num1);
}
}
//运行结果:
JVM static1 num1 = 0
JVM static2 num1 = 2
JVMTest JVM.num1 = 3
- 运行结果分析:
- 当执行new JVM()的时候,会先加载JVM这个类
- 于是先加载到JVM中,然后再链接
- 链接的准备过程中,先为JVM类的静态变量分配内存空间,并设置为默认值
- 然后在执行JVM的static静态代码块,打印日志,并赋值
- 最后执行构造方法
(2). 访问某个类的静态变量,或者为类的静态变量赋值
//JVM测试类
public class JVMTest {
public static void main(String[] args) {
System.out.print("JVMTest JVM.num1 = " + JVM.num1);
}
}
运行结果:
JVM static1 num1 = 0
JVM static2 num1 = 2
JVMTest JVM.num1 = 2
(3). 调用了类的静态方法
//JVM测试类
public class JVMTest {
public static void main(String[] args) {
JVM.test();
}
}
运行结果:
JVM static1 num1 = 0
JVM static2 num1 = 2
JVM test num1 = 2
(4). 使用反射,实例化一个对象
//JVM测试类
public class JVMTest {
public static void main(String[] args) {
try {
Class.forName("jvm.JVM");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
//运行结果
JVM static1 num1 = 0
JVM static2 num1 = 2
(5). 初始化一个子类,那么父类也会被初始化
//继承JVM的子类
public class JVM2 extends JVM {
//num2静态变量
public static int num2 = 2;
static {
//打印日志和赋值
System.out.println("JVM2 static1 num2 = " + num2);
num2 = 4;
System.out.println("JVM2 static2 num2 = " + num2);
}
public static void main(String[] args) {
//main方法 实例化一个JVM2对象
new JVM2();
}
}
//运行结果
JVM static1 num1 = 0
JVM static2 num1 = 2
JVM2 static1 num2 = 2
JVM2 static2 num2 = 4
- 运行结果分析:
- 当我们实现子类的时候,会先加载父类











网友评论