基础积累:类加载过程引申JVM内存结构

2021-03-05  本文已影响0人  skipper_shou

前言

了解dubbo的时候,因为SPI机制用到了动态代理的机制,从而涉及到了类加载机制相关的东西,整个概念也属于非常底层的逻辑,也好久没整理了,现整理一下,便于后续翻阅。
尽可能的关联JVM相关的知识点,如果读者有补充的可以留言补充。

类加载机制概念

Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制。*
Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数,属性和方法等,Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能,这里就是我们经常能见到的Class类。

类加载过程

image.png

类装载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。主要要经过以下步骤:

1.加载

类的装载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。
加载.class文件的方式有:

2.验证

验证的目的是为了确保class文件中的字节流包含的信息流符合虚拟机的规范,因此,不同的虚拟机可能有不同的实现,大致可分为以下几步:

3.准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。

初始化

类初始化阶段是类加载过程的最后一步,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

以上也可知,方法区存储了静态变量类信息
中存储了类变量局部变量
扩展:即时编译器(JIT Just In Time)编译后的一些热点代码也会存放在方法区
常量数据在编译访问常量的代码时才会放入方法区中。
由上面介绍可以知道,方法区存放着各线程都可用的数据,因此是线程共享的。

类的实例化

类实例化的一般过程是:
父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数->静态代码块->方法。
在类的实例化过程中,首先会在虚拟机栈中,保存实例对象的引用,具体对象的属性实例会存放在中。
扩展:这里也可以理解垃圾的生成

Test testA = new Test();
Test testB = new Test();
testA.setName("a");
testB.setName("b");
testB = testA;

以上伪代码可解释,当引用关系变化时,原有testB.setName("b");所对应的堆中的内存就可以当做垃圾回收。
除了对象实例,还包括数组,所以这也解释为什么会报java.lang.OutOfMemoryError: Requested array size exceeds VM limit

方法调用

当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。
局部变量表(Local Variables)操作数栈(Operand Stack)指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)方法返回地址(Return Address)和一些额外的附加信息压入虚拟机栈中。
这也就解释了为什么递归过多会导致StackOverflowError,因为会不断的往栈中压入方法返回地址
这部分是在线程执行方法时生成,故也可以知是线程私有的。
扩展:程序计数器存储当前线程所执行的字节码的行号指示器。指向下一条要执行的指令。因此也是线程私有的。

JVM内存结构

image.png

堆的作用是存放对象实例数组。从结构上来分,可以分为新生代老年代。而新生代又可以分为Eden 空间From Survivor 空间(s0)To Survivor 空间(s1)。 所有新生成的对象首先都是放在新生代的。需要注意,Survivor的两个区是对称的,没先后关系,所以同一个区中可能同时存在从Eden复制过来的对象,和从前一个Survivor复制过来的对象,而复制到老年代的只有从第一个Survivor区过来的对象。而且,Survivor区总有一个是空的。
如图:-Xms设置堆的最小空间大小。-Xmx设置堆的最大空间大小。-XX:NewSize设置新生代最小空间大小。-XX:MaxNewSize设置新生代最小空间大小。

方法区

方法区(Method Area)与Java 堆一样,是各个线程共享的内存区域,也有人把方法区称为“永久代”(Permanent Generation),在Java8中永生代彻底消失了。
如图:-XX:PermSize 设置最小空间 -XX:MaxPermSize 设置最大空间。

方法栈

每个线程会有一个私有的栈。每个线程中方法的调用又会在本栈中创建一个栈帧。
如图:-Xss控制每个线程栈的大小。

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。
如图:-Xss控制每个线程的大小。

上一篇 下一篇

猜你喜欢

热点阅读