JVM内存划分
JVM也就是java进程,这个进程一旦跑起来就会从操作系统这里申请一大块内存空间,JVM接下来就要进一步的对这个大的空间进行划分,划分成不同区域,从而每个区域都有不同的功能作用,一共分为如下几个区域
1.堆(heap)整个内存区域中,最大的区域,放的就是代码中new出来的对象(成员变量)
2.栈(stack)JVM虚拟机栈保存了java中的方法调用关系
3.元数据区(以前叫”方法区“,从java 8 改名字),放类对象的
4.程序计数器:是内存区域中最小的区域,只需要保存当前要执行的下一条指令(JVM字节码)的地址(这个地址就是元数据区里面的一个地址)
写一个伪代码,大家来判断是那个区域的
在上述代码中,a ,t2,s这三个都是Test的成员变量,都是在堆上的
b是static修饰,成了类属性,就会出现在类对象中,也就是元数据区
hello本体是在元数据区,s自身实在堆上的,里面保存了一共指向元数据区的地址
在下面的main中,t是代码中的”局部变量“,局部变量是在栈上的,而new Test()才是在堆上的,t只是保存了堆上的地址
画图理解
基本原则:一个对象在哪个区域,取决于对应变量的形态。
1.局部变量:栈上
2.成员变量:堆上
3.静态成员变量:方法区/元数据区
类加载的过程
我们正常写的java代码,是 .java 文件 (硬盘),一个 java 进程要跑起来,就需要把 .java 先变成 .class 文件 (硬盘),加载到内存中,得到"类对象。
以下是类加载的几个环节(八股内容)
1.加载:在硬盘上,找到对应的.class文件,读取文件内容
2.验证:检查.class里的内容是否符合要求,.class是javac编译器生成的,.class文件的格式的格式在java的官方文档中是有明确定义的,在验证过程中,会把读取进来的内容往明确定义的格式里套,能不能套进去,看是否有问题
3.准备:给类对象分配内存空间,内存空间会被分到元数据区,而这个空间里的数据会先默认为0
4.解析:针对字符串常量来初试化,把刚才.class文件中的常量的内容取出来,放到”元数据区“
5.初试化:针对类对象进行初试化,注意这里不是针对对象初试化,和构造方法无关,而是给静态成员进行初试化,执行静态代码块
经过上述过程,此时的类对象就敲定了,后续代码就可以使用这个类对象,创建实例,或者使用里面的静态成员了。
类加载中的”双亲委派模型“
双亲委派模型出现在上述类加载过程中的”加载“这一环节,根据代码中写的”全限定类名“(类名+包名)找到对应的.class文件
双亲委派模型,描述了 JVM 加载 .class 文件过程中,找文件的过程
类加载器
在JVM中包含的一个特定的模块/类,这个类负责完成后续的类加载工作,在JVM中内置了三个类加载器,负责加载不同的类,分别是BootstrapClassLoader,ExtentionClassLoader,ApplicationClassLoader。
工作过程:假设给定一个类的全限定类名 java123.Test(自己写的一个类)
1.工作从 ApplicationClassLoader 开始进行,ApplicationClassLoader 并不会立即搜索第三方库的相关目录,而是把任务交给自己的父亲来进行处理。(主打一个啃老)
2.工作就到了 ExtentionClassLoader,ExtentionClassLoader 也不会立即搜索负责的扩展库的目录,也是把任务交给自己的父亲来处理(主打一个啃老double)
3.工作就到了 BootstrapClassLoader,BootstrapClassLoader 也想交给自己的父亲来处理,但是它的 parent 指向 null, 只能自己处理,BootstrapClassLoader 尝试在标准库的路径中搜索上述类。如果这个类,在标准库中找到了,于是搜索过程就完成了,类加载器负责打开文件,读取文件等后续操作就行了.…..
如果没找到,任务还是要继续还给儿子来处理~~
4.工作回到了 ExtentionClassLoader,此时就要搜索扩展库对应的目录了,如果找到了,就由当前的类加载器负责打开文件,读取文件等后续操作。如果没找到,任务还是要继续还给儿子来处理~~
5.工作回到了 ApplicationClassLoader此时要搜索第三方库/用户项目代码的目录了,如果找到了,也是由当前的类加载器负责处理,如果没找到,任务还是要继续还给儿子来处理~~
此时, 没有儿子了!!!还没找到,就会最终抛出一个 ClassNotFoundException.
总结:拿到任务交给老爹,老爹处理不了再自己处理
注意:此处的"父子关系"不是通过类的继承表示的(不是父类和子类)而是通过类加载器中存在一个"parent" 这样的字段,指向自己的父亲。
那么为什么这么麻烦捏,不能直接自己处理吗?
上述过程主要是为了应对这个场景:比如你自己代码里写了一个类,类的名字和标准库/扩展库冲突了, 会确保加载的类是标准库的类(就不加载你自己写的类了)
有帮助理解麻烦点个赞