- 示例:
public class ServerA{
public static int flushInterval;
public static ServerB serverB = new ServerB();
}
public class ServerB{
}
-
Java代码运行在JVM上的,JVM想要执行Java代码,首先需要将代码编译成class文件;
-
然后再使用类加载器将class字节码文件加载到JVM内存中,类加载器遵循双亲委派机制;
-
启动类加载器(Bootstrap ClassLoader):
主要是负责加载我们在机器上Java安装目录下的“lib”目录中的核心类库; -
扩展类加载器(Extension ClassLoader):
主要是负责加载我们在机器上Java安装目录下的“lib\ext”目录中的类; -
应用程序类加载器(应用程序类加载器):
这类加载器就负责去加载“ClassPath”环境变量所指定的路径中的类其实你大致就理解为去加载你写好的Java代码吧,这个类加载器就负责加载你写好的那些类到内存里; -
自定义类加载器:
除了上面那几种之外,还可以自定义类加载器,去根据你自己的需求加载你的类;
假设你的应用程序类加载器需要加载一个类,他首先会委派给自己的父类加载器去加载,最终传导到顶层的类加载器去加载,但是如果父类加载器在自己负责加载的范围内,没找到这个类,那么就会下推加载权利给自己的子类加载器; -
双亲委派机制:
JVM的类加载器是有亲子层级结构的,就是说启动类加载器是最上层的,扩展类加载器在第二层,第三层是应用程序类加载器,最后一层是自定义类加载器;
-
启动类加载器(Bootstrap ClassLoader):
-
类加载过程分几步,分别是加载,验证,准备,解析,初始化
-
加载
加载的话就是刚才说的用类加载器去加载类,将class文件加载至java虚拟机,并存储在方法区。方法区存储类信息、常量、静态变量; -
验证
这一步就是根据Java虚拟机规范,来校验你加载进来的“.class”文件中的内容,是否符合指定的规范,假如说,你的“.class”文件被人篡改了,里面的字节码压根不符合规范,那么JVM是没法去执行这个字节码的,所以把“.class”加载到内存里之后,必须先验证一下,校验他必须完全符合JVM规范,后续才能交给JVM来运行; -
准备
主要是给类分配一定的内存空间,然后给他里面的类变量(也就是static修饰的变量)分配内存空间,来一个默认的初始值,比如上面的示例里,就会给“ServerA.flushInterval”这个类变量分配内容空间,给一个“0”这个初始值; -
解析
主要是给符号引用变成直接引用,就是把一些变量什么temp,直接换成物理地址,不然执行的时候JVM也不认识temp是啥 -
初始化
在初始化阶段时,才会执行类的初始化代码,主要是给类变量赋值,准备阶段只是设置了初始值,这个是核心阶段,执行类的初始化,如果发现这个类的父类没有初始化,会先暂停,然后去初始化父类,也是走类加载的一套流程,直到父类加载完了,再执行子类的初始化
以上就是类大概的加载的过程,加载的类是放在JVM的元空间(Metaspace),在JDK 1.8以前的版本里叫方法区,如下图
-
加载
-
示例:
public class ServerA{
public static void main(String[] args) {
ServerB serverB = new ServerB();
serverB.test();
}
}
public class ServerB{
public String test(){
String temp = "test";
return temp;
}
}
-
类加载到永久代之后,会使用字节码执行引擎去执行我们所写的代码编译出来的字节码指令;
我们写好的Java代码会被翻译成字节码,对应各种字节码指令,所以当JVM加载类信息到内存之后,实际就会使用自己的字节码执行引擎,去执行我们写的代码编译出来的代码指令,那么在执行字节码指令的时候,JVM里就需要一个特殊的内存区域了,那就是“程序计数器”,这个程序计数器就是用来记录当前执行的字节码指令的位置的; -
在执行字节码指令时,需要一个特殊的内存区域程序计数器去记录当前执行的字节码指令的位置; 由于JVM是支持多线程的,代码可能会被多线程并发执行,因此每个线程都会有自己的一个程序计数器,专门记录当前这个线程目前执行到了哪一条字节码指令了;
-
每个线程都有自己的Java虚拟机栈,当具体某个线程执行到某个方法时,首先会给这个方法创建一个栈帧,将栈帧压入线程的虚拟机栈中;栈帧里就有这个方法的局部变量表 、操作数栈、动态链接、方法出口等东西;
局部变量表:
操作数栈:
动态链接:
方法出口:字节码执行过程如下图:
-
当方法执行完毕,栈帧出栈,里面的局部变量直接就从内存里清理掉了,局部变量指向堆内存的引用也随之失效;
-
这里的栈帧如果没有执行完时,其实都是GC Root,垃圾回收时,就是根据这里的局部变量的引用和永久代的引用来判断对象是否存活;
-
当引用失效,堆内存实例则会变成"垃圾"对象,JVM本身是有垃圾回收机制的,他是一个后台自动运行的线程;当触发垃圾回收机制,垃圾对象就会被清理掉,释放内存;如图: