一.编译过程
使用javac 编译程序
java源文件被jvm中的编译器编译为.class文件(byteCode)
当编译某个类时,如果该类依赖的类还没有编译 ,则会去优先编译依赖的类,然后引用
编译后的byteCode文件包含常量池和方法区两部分
常量池主要包括源文件中的常量、类名、成员变量等以及符号引用(类引用、方法引用等)
方法区主要包括各个方法的字节码
二.运行过程
java +
运行过程分为类加载和类执行两步
首先要了解jvm的基本组成
1.类加载
首先会在内存中创建一个jvm进程,然后根据环境变量中的classpath将字节码文件读入到内存中,通常是以字节数组形式读入,字节码文件中的类信息会加载到运行时数据区的方法区(只有将类信息加载至方法区,才可以创建对象,使用成员变量)
引用一段类加载器的说明:
类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、 系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。从 Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性, 在该机制中,JVM 自带的 Bootstrap 是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的引用。
2.类执行
当类加载完成后,此时的class类对象还不完整,需要进行连接操作。连接操作包含验证、准备(为静态变量分配内存和赋初值)和解析(将符号引用?替换为直接引用?)三个步骤。最后解释执行相应的类代码。
举例
public class Main {
public static void main(String[] args) {
Animal animal = new Animal("super_yc");
animal.printName();
}
}
class Animal{
private String name;
public Animal(String name) {
super();
this.name = name;
}
public void printName(){
System.out.println("Animal = " + this.name);
}
}
(1)JVM 找到main方法的主函数入口, 持有一个指向当前类常量池的指针,而常量池中的第一项是发现是一个对Animal对象的符号引用,并且main方法中第一条指令是Animal animal = new Animal(“super_yc”),就是让JVM创建一个Animal对象,但是方法区中还没有Animal类的类信息,于是
JVM就要马上的加载Animal类,将Animal类信息放入到方法区中,于是JVM 以一个直接指向方法区 Animal类的指针替换了常量池中第一项的符号引用。
(2)加载完Animal类的信息以后,JVM虚拟机就会在堆内存中为一个Animal类实例分配内存,然后调用其构造函数初始化Animal实例,这个实例持有指向方法区的Animal类的类型信息(其中包含有方法表,java动态绑定的底层实现)的引用。(animal指向了Animal对象的引用会自动的放在栈中,字符串常量"super_yc"会自动的放在方法区的常量池中,对象会自动的放入堆区)
(3)当使用 animal.pringName()的时候,JVM根据栈中animal引用找到Animal对象,然后根据Animal对象持有的引用定位到方法区中Animal类的类型信息方法表,获得pringName()函数的字节码地址,然后开始运行函数。
注:
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到内存中。
直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在内存中存在。
来源:CSDN
作者:不爱潮流的Sneaker不是好程序员
链接:https://blog.csdn.net/qq_42584241/article/details/104919401