hello world 作为我们学习的第一个个程序,看起来很简单,但是要理解其执行的具体流程还是需要很深的功底,包括对组成原理,操作系统的理解,今天将java hello world 进行整理一下吧!!
废话不多说,先上hello world
public class Main {
public static void main(String[] args) {
String s = "helloWorld";
String s2 = new String("helloWorld");
System.out.println(s);
System.out.println(s2);
}
}
很简单的程序,具体执行是怎么样的?
相信大家都知道 java 代码的可移植性,是由于java解释器和虚拟机,所以处理java原代码的过程,就是java代码执行的过程:
Java 代码的运行过程?
Java 源代码 -> 编辑器 -> 字节码文件
字节码 -> JVM -> 机器码文件
每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是 Java 为什么能够 跨平台的原因
java 原文件通过编译器编译成.class字节码文件,字节码文件通过 JVM 虚拟机,生成机器码文件
1. 代码编译 ,词法语法语义分析,将java 原代码编译成字节码文件;
我们的.java 文件最终会转换为.class文件
2. 类加载机制,采用双亲委派避免重复加载(类名+类加载器),加载,验证,准备(准备会对一些常量进行初始化,遍历初始化为0或null),解析,初始化
我们知道 JVM 虚拟机的入口就是类加载器,在加载java中的类时采用双亲委托机制,可以防止用户新写的类,替代jre中的类。
当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父 类去完 成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中, 只有当父类 加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的 Class), 子类加 载器才会尝试自己去加载。
类加载器有,启动(Bootstrap)类加载器,扩展(Extension)类加载器,系统(System)类加载器,自定义类加载器
我们的Main类由系统类加载器进行加载,委托给父类加载器
在JVM中表示两个class对象是否为同一个类对象存在两个必要条件
类的完整类名必须一致,包括包名。
加载这个类的ClassLoader(指ClassLoader实例对象)必须相同
详细的类加载过程是:
JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化。
- 加载, 这个阶段会在内存中生成一个代表这个类的 java.lang.Class 作为方法区这个类的各种数 据的入口。
- 验证,确保 Class文件的字节流中包含的信息是否符合当前虚拟机的要求
- 准备,是正式为类变量分配内存并设置类变量的初始值阶段,public static int v = 8080,实际 上变量 v 在准备阶段过后的初始值为 0 而不是 8080,但是如果声明的是常量就是8080,例如 public static final int v = 8080。
4.解析,解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。
3. 在jvm内存中栈和本地线程栈是线程私有的,会在线程中创建一个main方法的栈帧
在main方法栈帧中,里面S,S2存放在其中的字符变量表中,然后“helloword”是一个字符串常量,在jvm中会存放在方法区中的字符常量池中的,然后将其从常量池中弹到操作数栈空间,然后赋值到s的空间中。这个 new String("helloword"), 先指向堆空间,堆空间再去指向 方法区常量池中。复制一份常量数据再放到s2 对应的寄存器空间中。然后字面变量在字符变量表中指向这些空间。
具体可以使用 javap -c Main.class 解析.class 文件
Compiled from "Main.java"
public class com.bupt.learn.Main {
public com.bupt.learn.Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
// 将 helloword 从常量池推送到栈顶
0: ldc #2 // String helloWorld
// 将栈顶 引用 型数值存入第二个局部变量
2: astore_1
// 堆中创建一个对象,并将其引用值压入栈顶
3: new #3 // class java/lang/String
// 复制栈顶数值并将复制值压入栈顶
6: dup
// 将 helloword 从常量池推送到栈顶
7: ldc #2 // String helloWorld
// 调用超类构造方法,实例初始化方法,私有方法
9: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
// 将栈顶 引用 型数值存入第三个局部变量
12: astore_2
// 获取指定类的静态字段,并将其压入栈顶
13: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
// 将第二个 引用 型局部变量推送至栈顶
16: aload_1
// 调用实例方法
17: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
20: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
23: aload_2
24: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
27: return
}
jvm.PNG
4. System.out.println(s):系统加载System.class字节码文件到方法区,并且系统会默认在堆区创建System.out、System.in、System.err三个对象。
字符串在被输出时会自动调用toString()方法。
println 是 java 原始的IO包,是使用 BIO 进行输出
一次I/O的完成的步骤
当进程发起系统调用时,这个系统调用就进入内核模式,然后开始I/O操作
I/O操作分为两个步骤;
1、磁盘把数据装载到内核的内存空间,
2、内核的内存空间的数据copy到用户的内存空间中(此过程是I/O发生的地方)
阻塞:进程发起I/O调用,进程又不得不等待I/O的完成,此时CPU把进程切换出去,进程处于睡眠状态则此过程为阻塞I/O
阻塞I/O系统怎么通知进程?
I/O完成,系统直接通知进程,则进程被唤醒
BIO.PNG
网友评论