1.JVM内存区域划分
一个运行起来的Java进程就是一个JVM虚拟机,这就需要从操作系统中申请一片内存区域。JVM申请到内存之后,会把这个内存划分为几个区域,每个区域都有各自的作用。
一般会把内存划分为四个区域 :方法区 (也称 "元数据区"),堆区 ,栈区 ,程序计数器。
(1)方法区:存储的内容是 "类对象"。
(2)堆区:new()出来的对象就存储在堆区。
(3)栈区:存储的内容一般是 一些 方法的形参,方法的返回值,局部变量。
(4)程序计数器:程序计数器中存放的是接下来要执行的指令的地址。
一个示例代码,看看每个变量存放在内存的哪个区域:
name是"static成员",是"静态变量",也就是**"类成员"** ,因此 该变量在 方法区。
age是"成员变量" ,new 出来的对象就会包含age这个属性,因此age变量在 堆区。
demo是**"局部变量"** ,是一个 "引用类型" 的变量,存放的是 对象的地址。通过demo变量能够找到Demo对象。因此,demo存储在栈区。
2.JVM类加载的基本流程
我们编写的Java代码会被编译成.class文件,一个Java程序想要运行起来,就得找到.calss文件,把.class文件中的内容 构造成 "类对象",保存到内存的方法区中。以上过程就是类加载的过程。
类加载的过程可以分成5个步骤:加载 ,验证 ,准备 ,解析 ,初始化。
(1)加载 :找到**.class文件**,打开文件 并读取文件的内容。
(2)验证 :.class文件是一个二进制文件,需要验证当前读取的格式是否符合要求。
(3)准备 :给类对象分配内存空间。
(4)解析 :针对类对象中包含的字符串常量进行处理,进行一些初始化操作。
(5)初始化 :对 类对象进行初始化,把类对象中的各个属性都设置好。以及涉及到的static成员,静态代码块等。
3.双亲委派模型
双亲委派模型 属于类加载流程中的第一步,也就是 "加载" 过程所涉及到的。这个模型 会根据 全限定类名 找.class文件。
JVM内置了三个 类加载器,分别为 BootStrap ClassLoader(爷)、Extension ClassLoader(父)、Application ClassLoader(子)。
在找.class文件时,会根据类的全限定类名,形如"java.lang.String",从类加载器中寻找该类。
(1)首先,会把Application ClassLoader 作为入口 ,开始执行查找的逻辑。但Application ClassLoader不会立即区扫描自己负责的目录**(搜索当前目录和第三方库对应的目录)** ,而是会把这个查找任务 交给自己的父加载器 Extension ClassLoader。
(2)那么Extension ClassLoader 加载器,也不会立刻去扫描自己负责的目录**(负责JDK中的一些扩展的库对应的目录)** ,而是把查找任务 交给自己的父加载器BootStrap ClassLoader。
(3)则BootStrap ClassLoader 也不想立刻去扫描自己负责的目录**(标准库的目录)**,于是尝试去把查找任务给到其它的类加载器,但是它没有"父亲",它只能亲自去扫描标准库的目录。
(4)"java.lang.String"能够在标准库中找到对应的.class文件,就可以打开并读取文件,那么寻找.class文件的任务就完成了。
(5)如果给定的类不是标准库的类 ,那么BootStrap ClassLoader在标准库中就找不到.class文件 ,还是会把查找任务交给**"孩子"**来执行。
(6)那么任务就会回到Extension ClassLoader 加载器,它就会扫描自己负责的扩展库的目录。如果找到了,查找.class任务就结束了;若没找到,还是把任务交给"孩子"执行;
(7)任务就又回到了Application ClassLoader 类加载器,它就会去扫描自己的当前目录和第三方库目录。若找到了,找.class文件任务结束;若没找到,就会抛出ClassNotFoundException。
以上寻找.class文件过程的模型就是**"双亲委派模型"** ,其实就是一个 查找优先级 的问题。率先在标准库中去查找.class文件,若没找到,就去扩展库中去找,再找不到,就去第三方库去找,还找不到,就会抛出异常。