一.JVM内存区域划分(JVM是一个Java进程)
一个进程运行过程中就需要重操作系统这里申请到一些内存资源
JVM也是如此,搞一大块内存,供Java代码执行时使用
JVM把这一大块内存又划分成不同的区域,分别代表不同的用途
各个部分的存储内容:
栈:局部变量,方法的调用关系(如:递归)
堆:new出来的对象里面的属性
方法区:类对象,static成员,类被加载到内存后放的地方,方法的内容
程序计数器:记录了当前线程执行下一个指令的内存地址
如:
public class Test{
public int n=20;
public static int a=10;
public static void main(String[]args){
Test t=new Test();
}
}
这里的的t是局部变量放在栈上
n是new出来的内容放到堆上
a是static成员放在方法区
误区:变量在哪里,和是不是“基本类型”,是否是“引用类型”没有关系
二、JVM类加载机制(把一个硬盘文件加载到内存中)
最开始写的.java文件,把它编译成.class文件(字节码)
运行.java程序,JVM会读取.class文件,把文件的内容放到内存中,就会构成一个.class对象
类健在的流程:
1.加载:找到.class文件,打开.class文件,读取.class文件的内容,并尝试解析格式
2.验证:检查一下当前.class文件的格式,是否符合要求
3.准备:给类对象分配内存
最终目标是构造成完整的内对象,分配内存+初始化
分配出来的内存空间,内容是0的值(此时此刻,类对象上的static成员也就是0)
4.解析:主要是初始化类对象中涉及到的一些字符串常量(其实字符串常量已经在.class文件中了,直接读到内存中就行了)
这里用符号引用替换直接引用的过程(符号5引用:此时.class文件中,不知道字符串真实的内存地址是哪里的,只能知道一个相对位置的偏移量,知道字符串的内容在.class文件的那个地方;等到把字符串内容加载到内存之后,就可以把真实的地址写换到刚才的符号引用这里了)
5.初始化:对类对象进行更具体的初始化操作,初始化静态成员,执行静态代码块,加载父类
三、双亲委派模型(描述加载构成中,如何找.class文件)
Bootstrap ClassLoader(负责加载JVM准库中的类):Java有一个标准文档,描述了都要提供哪些类
Extension ClassLoader(负责加载JVM扩展的类):除了标准库之外,实现JVM的厂商,可能还会添加一些类
Application ClassLoader(负责加载第三方库):像MySQL,jdbc等一下放在Maven中的依赖
双亲委派模型,就描述了类加载的流程:
1.从Application ClassLoader开始。不会立即搜索第三方库,而是先把任务委派给父亲,让父亲尝试加载
2.接着到了Extension ClassLoader,它也不会立即搜索JVM扩展的类,而是把任务委派给他的父亲,让他的父亲尝试加载
3.接着到了Bootstrap ClassLoader,它也是把任务委派给自己的父亲,但是他没有父亲,那么他就自己搜索类了。
如果自己找到了这个类,就会进行后续的加载(也就和Extension、Application没有什么关系了)
如果没有找到这个类任务就会交给孩子
4、任务回到Extension ClassLoader,就要搜索扩展库中的目录,看有没有匹配的.class文件
如果有的话,就进行后续加载也就和Application没有什么关系了
5、任务回到了Application ClassLoader,就要搜索第三方库的目录(往往是项目目录,以及和JVM的一些配置项有关-classpath有关系)
如果找到了类,就会进行后续加载
如果没有找到就会抛出一个异常
一个类什么时机会被加载?(懒汉模式用到了才加载)
1、构造类的实力
2、使用了类的静态方法/静态属性
3、子类的加载会触发父类
类加载了之后,后续使用就不必加载了(类对象已经是现成了的)
注:类卸载(把类对象干掉)
特殊情况:
一般来说类加载了之后,就不必考虑卸载了,一直保持到程序运行结束
有些特定的场景需要用到类卸载操作
有的服务器需要打“热补丁”代码有bug,正常操作应该是修改代码,重新编译,用新的版本代替就得版本,重启服务器
有些特殊情况,服务器不方便重启,就可以“打补丁”,通过一些特殊手段,把书序替换的类给卸载掉,直接用加载好的心得类替换(新版代码)(Java这里用的倒不是很多,Java的服务器一般都是分布式系统,天然就可以方便重启的)
不重启服务器,也能更新代码
“热”不需要重启,不需要重新编译,也不需要重启