JVM内存模型以及双亲委派机制

jvm内存模型

Oracle官网解释:

Chapter 17. Threads and Locks (oracle.com)

jvm内存结构

方法区

  1. 有时候也成为永久代,在该区内很少发生垃圾回收,但是并不代表不发生 GC,在这里进行的 GC 主要是对方法区里的常量池和对类型的卸载

  1. 方法区主要用来存储已被虚拟机加载的类的信息、常量、静态变量和即时编译器编译后的代码等数据。

  1. 该区域是被线程共享的。

  1. 方法区里有一个运行时常量池,用于存放静态编译产生的字面量和符号引用。该常量池具有动态性,也就是说常量并不一定是编译时确定,运行时生成的常量也会存在这个常量池中。

虚拟机栈:

  1. 虚拟机栈也就是我们平常所称的栈内存,它为 java 方法服务,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。

  1. 虚拟机栈是线程私有的,它的生命周期与线程相同。

  1. 局部变量表里存储的是基本数据类型、returnAddress 类型(指向一条字节码指令的地址) 和对象引用,这个对象引用有可能是指向对象起始地址的一个指针,也有可能是代表对象的句柄或者与对象相关联的位置。局部变量所需的内存空间在编译器间确定

  1. 操作数栈的作用主要用来存储运算结果以及运算的操作数,它不同于局部变量表通过索引来访问,而是压栈和出栈的方式

  1. 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接.动态链接就是将常量池中的符号引用在运行期转化为直接引用。

本地方法栈:

本地方法栈和虚拟机栈类似,只不过本地方法栈为Native 方法服务。

java 堆是所有线程所共享的一块内存,在虚拟机启动时创建,几乎所有的对象实例都在这里创建,因此该区域经常发生垃圾回收操作。

程序计数器:

内存空间小,字节码解释器工作时通过改变这个计数值可以选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等功能都需要依赖这个计数器完成。该内存区域是唯一一个java 虚拟机规范没有规定任何OOM 情况的区域。

jvm类加载过程

加载→验证→解析→初始化

  1. 加载

加载时类加载的第一个过程,在这个阶段,将完成一下三件事情:

  1. 通过一个类的全限定名获取该类的二进制流。

  1. 将该二进制流中的静态存储结构转化为方法去运行数据结构。

  1. 在内存中生成该类的 Class 对象,作为该类的数据访问入口。

  1. 验证

验证的目的是为了确保 Class 文件的字节流中的信息不会危害到虚拟机.在该阶段主要完成以下四种验证:

  1. 文件格式验证:验证字节流是否符合 Class 文件的规范,如主次版本号是否在当前虚拟机范围内,常量池中的常量是否有不被支持的类型.

  1. 元数据验证:对字节码描述的信息进行语义分析,如这个类是否有父类,是否集成了不被继承的类等。

  1. 字节码验证:是整个验证过程中最复杂的一个阶段,通过验证数据流和控制流的分析,

确定程序语义是否正确,主要针对方法体的验证。如:方法中的类型转换是否正确,跳转指令是否正确等。

  1. 符号引用验证:这个动作在后面的解析过程中发生,主要是为了确保解析动作能正确执行。

  1. 准备

准备阶段是为类的静态变量分配内存并将其初始化为默认值,这些内存都将在方法区中进行分配。准备阶段不分配类中的实例变量的内存,实例变量将会在对象实例化时随着对象一起分配在 Java 堆中。

  1. 解析

该阶段主要完成符号引用到直接引用的转换动作。解析动作并不一定在初始化动作完成之前,也有可能在初始化之后。

  1. 初始化

初始化时类加载的最后一步,前面的类加载过程,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的 Java 程序代码。

双亲委派机制

JVM中有3个默认的类加载器:

1、启动类(根类)加载器 (Bootstrap Class Loader)

2、扩展类加载器(Extension Class Loader)

3、应用程序类加载器(Application Class Loader)

双亲委派模型,要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。

Java平台通过委派模型去加载类。每个类加载器都有一个父加载器。

1、当需要加载类时,会优先委派当前所在的类的加载器的父加载器去加载这个类。

2、如果父加载器无法加载到这个类时,再尝试在当前所在的类的加载器中加载这个类。

1、因为双亲委派是向上委托加载的,所以它可以确保类只被加载一次,避免重复加载

共享功能:一些framework层级的类一旦被顶层加载器加载,缓存在内存。在其他任何地方用到时,都遵守双亲加载机制,派发到顶层加载器因已经加载,所以都不需要重新加载。

2、避免核心类被串改:Java的核心API都是通过引导类加载器进行加载的,如果别人通过定义同样路径的类比如java.lang.Integer,类加载器通过向上委托,两个Integer,那么最终被加载的应该是jdk的Integer类,而并非我们自定义的,这样就避免了我们恶意篡改核心包的风险

隔离功能:保证核心类库的纯净和安全,防止恶意加载。

猜你喜欢

转载自blog.csdn.net/qq_53368181/article/details/128853137