? ? ? ? 我们开发的.java文件也就是源文件在经过了java编译器的编译之后变成了.class文件也就是字节码文件,当jvm在执行过程中用到某个类,而这个类还未被加载到内存中时,会由类加载器负责这个类的加载,整个的加载过程分成五步:加载、验证、准备、解析、初始化。
? ? ? ? 加载阶段是由类加载器将这个类的字节码文件加载到内存中,并为这个类创建java.lang.Class对象,这个类的信息会被保存到方法区中。验证阶段是jvm对字节码文件执行字节码规范的验证,只有符合jvm字节码规范的class文件才能被成功加载到内存中,验证阶段是与加载阶段并行的。验证通过之后,进入准备阶段,准备阶段将为类变量分配存储空间,并为其赋予默认的初始值。解析阶段是将文件中的符号引用替换为直接引用,比如对于String str = "China";将str的引用替换为字符串常量池中China这个字符串常量的地址。初始化阶段会为类变量赋予真正的初始值。
? ? ? ? 以上五步执行完毕之后,就完成了这个类的加载,这个类就可以执行了,类的执行是由解释器配合着程序计数器完成的,解释器是由jvm提供的,它的作用是将字节码指令解释为本地机器指令执行,而程序计数器是由当前执行的线程提供的,它的作用是在解释器解释执行时,指向下一行将要被执行的字节码指令。线程在被创建的时候,jvm会为它分配栈内存及程序计数器,栈内存包括虚拟机栈和本地方法栈,所以栈内存和程序计数器都是被线程私有的内存区域,方法的执行就是发生在栈内存中的,普通方法在虚拟机栈中执行,native方法在本地方法栈中执行,以虚拟机栈为例来说明一下一次方法的执行过程:
? ? ? ? 虚拟机栈内部保存的是一个一个的栈桢,每个栈桢都对应着这个虚拟机栈所属线程中的一次方法调用,每个栈桢从入栈到出栈的过程都对应着一次方法从执行到退出执行的过程,那么虚拟机栈中为什么会有多个栈桢呢?这是因为我们的方法在执行过程中经常又去调用其他方法,而其他方法内部可能又存在着另外一个方法的调用,所以方法的执行过程中存在调用链,而处在调用链上的每一次方法调用都会有一个栈桢与之对应,所以虚拟机栈中会有多个栈桢存在,而处于栈顶的那个栈桢被叫做当前栈桢。栈是一个只能在一端执行操作的数据结构,比如虚拟机栈只能在栈顶执行栈桢的入栈和出栈操作,因此最后入栈的栈桢将最先出栈,而最先入栈的栈桢【比如main方法对应的栈桢】因为被压在了栈底所以最后才能出栈;当前栈桢对应的是当前正在执行的方法,比如:main方法的调用链是:main中调用A方法,A方法中又调用B方法,当main方法开始执行时,会为main方法创建一个栈桢入到虚拟机栈中,当前虚拟机栈中只有这一个栈桢,所以它就是当前栈桢,当调用A方法时,也会为A方法创建一个栈桢入栈,A方法的栈桢被入到了栈顶,所以变成了当前栈桢,而main方法的栈桢被压到了栈底,当调用到B方法时,又为B方法创建了一个栈桢入栈,B方法的栈桢又变成当前栈桢。
? ? ? ? 栈桢中保存的是本地变量表、操作数栈、方法的引用信息【或者是叫做方法的出口信息】,本地变量表内部保存的是当前方法的局部变量以及参数的值,操作数栈将配合着方法的执行过程,用于存储方法执行中的中间结果,比如有一个int类型的局部变量a,在方法中要实现这个变量的自增操作a++;在执行这个自增操作时,会将本地变量表中保存的a的值压到操作数栈中,在操作数栈中实现它的自增操作,然后将操作结果再从操作数栈中复制到本地变量表中的a变量上。当当前方法执行到最后一条指令,比如return指令时,也就到了当前栈桢出栈的时候了,随着return指令的执行,当前栈桢出栈,引用当前方法的那个方法所对应的栈桢就变成了当前栈桢;随着被调用的方法对应的栈桢依次出栈,最后main线程对应的栈桢变成了当前栈桢,而随着main方法执行结束,main的栈桢出栈,虚拟机栈中不再有栈桢存在,那么运行在当前线程上的一次方法的整个执行过程也就结束了。
? ? ? ? 当然在方法的执行过程中,会有新的对象被不断地创建出来,jvm为这些对象在堆内存或者方法区中分配存储空间,而随着方法的结束,一些对象变成了不会再被使用的垃圾对象,jvm提供了垃圾收集器去回收这些对象,释放它们所占用的内存空间,以免发生内存溢出。jvm在用户线程启动时会自动启动用于垃圾回收的gc线程,这些gc线程会与用户线程并发执行,来回收用户线程执行过程中产生的垃圾,这些gc线程也会随着用户线程的退出而停止执行。