JVM内存模型


JVM是什么就不再赘述了,下面直接上正文

JVM四个组成部分

JVM分别有四个组成部分,分别是:

  1. 类加载器(Class Loader)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地方法接口(Native Interface)
    在这里插入图片描述

类加载器

官方 API 文档的介绍:

类加载器是一个负责加载类的对象。ClassLoader 是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转换为文件名,然后从文件系统中读取该名称的“类文件”。每个 Java 类都有一个引用指向加载它的 ClassLoader。不过,数组类不是通过 ClassLoader 创建的,而是 JVM 在需要的时候自动创建的,数组类通过getClassLoader()方法获取 ClassLoader 的时候和该数组的元素类型的 ClassLoader 是一致的。

简单来说,类加载器的主要作用就是加载 Java 类的字节码( .class 文件)到 JVM 中(在内存中生成一个代表该类的 Class 对象)。

JVM在启动的时候,并不会一次性的加载所有类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才会去加载,这样的加载方式对内存更加友好。

对于已经加载的类会被放在 ClassLoader 中。在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次。

JVM 中内置了三个重要的 ClassLoader:

  1. BootstrapClassLoader(启动类加载器) :最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库( %JAVA_HOME%/lib目录下的 rt.jar 、resources.jar 、charsets.jar等 jar 包和类))以及被 -Xbootclasspath参数指定的路径下的所有类。
  2. ExtensionClassLoader(扩展类加载器) :主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs 系统变量所指定的路径下的所有类。
  3. AppClassLoader(应用程序类加载器) :面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
    除了 BootstrapClassLoader 是 JVM 自身的一部分之外,其他所有的类加载器都是在 JVM 外部实现的,并且全都继承自 ClassLoader抽象类。这样做的好处是用户可以自定义类加载器,以便让应用程序自己决定如何去获取所需的类。

每个 ClassLoader 可以通过 getParent() 获取其父级 ClassLoader,如果获取到 ClassLoader 为null的话,那么该类是通过 BootstrapClassLoader 加载的。( 这是因为BootstrapClassLoader 由 C++ 实现,由于这个 C++ 实现的类加载器在 Java 中是没有与之对应的类的,所以拿到的结果是 null。)

运行时数据区(Runtime Data Area)

堆(Heap)(线程共有)

在这里插入图片描述

在这里插入图片描述

存放内容:通过new关键字创建的对象
内存分布:堆内存分为年轻代老年代年轻代默认占整个堆内存的三分之一的内存空间,老年代占三分之二的内存空间。
年轻代又分为伊甸园区和幸存区,幸存区又分为S0和S1
(伊甸园区、S0、S1默认的内存空间比例是 8 : 1 : 1 (基于年轻代的堆内存空间))

  • 年轻代
    • 伊甸园区(Eden)
      对象内存是优先分配到伊甸园区。 当伊甸园区内存占满时,执行 minor gc。minor gc是回收整个年轻代的内存空间(STW)
      gc是由字节码执行引擎开启垃圾收集线程。先执行STW,再通过 可达性分析算法去判断对象是否需要销毁。如果不需要销毁则把对象移入到幸存区S0,否则回收内存。
      可达性分析算法:通过GC Roots对象作为起点,从这些节点开始往下搜索引用的对象,搜索过的路径称为引用链。如果存在有引用的对象,则标记为非垃圾对象。如果没有找到引用的对象,则标识为垃圾对象。当标识为垃圾对象时并不是马上会回收,会经过第二次的可达性分析算法,如果在第二次依然没有引用对象,则回收,否则会 ‘ 复活 ’ 对象。

什么是GC Roots对象?它可以是栈中引用的对象、方法区中类静态属性应用的对象、方法区中常量引用的对象等等

  • 幸存区(Survivor)

    • FormSpace(S0)
      当伊甸园区内存占满时,同时 minor gc 没有回收的内存对象,会移入到Form内存区,同时该对象的分代年龄+1
    • ToSpace(S1)
  • 老年代(OldGen)
    当幸存区放不下内存对象且又无法回收时或当对象的分代年龄等于15时以及其他情况,则会把内存对象移入到老年代内存中

    当老年代内存空间占满时,会执行 Major gc 。full gc 会回收整个堆以及方法区的内存对象(STW),前提时在通过可达性分析时,确定对象可以回收才会回收。
    当老年代内存空间占满时,同时也无法回收内存对象,又在不断的往老年代存放内存对象,这时系统就会发生内存溢出(OOM)。

方法区(永久代)

存放内容:常量、静态变量、类信息(类文件)
(如果是基本数据类型,则就存放在方法区中。如果是引用数据类型,存放是一个指针地址,指向堆内存的对象地址)

栈(Stack)(线程私有)

存放内容:临时变量、局部变量、基础类型(boolean、byte、char)
栈帧:

  • 一个方法对应一块栈帧内存空间。当一个线程进来后,JVM会为这个线程分配一块栈内存空间,这个线程在运行的过程中,为每一个遇到的方法再分配一块属于这个线程栈内存的内存空间,即栈帧内存空间。这个栈帧内存空间属于这个方法,存放这个方法中的局部变量。
  • 栈帧的数据结构:先进后出。先执行的方法后结束,后执行的方法先结束。
  • 栈帧的主要四个结构:
    • 局部变量表:主要存放局部变量,如a,b,c,会为这些局部变量分配内存空间,以存放局部变量的值。如果这个值是基本数据类型,那么这个值放在当前为局部分配的内存空间中。如果局部变量的值是引用数据类型,则这个值存放的是一个指针地址,指向的是堆内存空间的值的所在的位置。
    • 操作数栈:存放操作的数值,如1,2,3。(就是存放变量的临时内存空间)
    • 动态链接:把一些符号引用转化为直接引用,如:方法名或变量名就是java中的符号,符号存在内存中,有一个内存地址指向这个符号,就是存在动态链接里。
    • 方法出口:当前方法的调用者方法现状。如:当A方法调用了B方法,在B方法执行完成后,需要确定A方法从哪一行继续执行,记录这一行的执行就是方法出口。

本地方法栈(Native Method Stack)

  • Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
  • 线程私有的。
  • 允许被实现成固定或者是可动态扩展的内存大小
  • 本地方法一般是使用C语言实现的。
  • 它的具体做法是Native Method Stack中登记native方法,在Execution Engine 执行时加载本地方法库。

程序计数器(Program Counter Register)

记录行号,是存放JVM正在运行或马上运行的代码行的行号或位置(每一个线程都会分配一块内存给程序计数器使用)。程序计数器存放的其实就是一个个的内存地址,每个内存地址指向是这句代码在方法区中的位置。
(当线程被挂起后需要再次执行时,是根据程序计数器所记录的行号的位置继续向下执行,而不是重新执行。)
在这里插入图片描述

执行引擎(Execution Engine)

概述:

  • 执行引擎是Java虚拟机核心的组成部分之一。
  • “虚拟机”是一个相对于“物理机”的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎则是由软件自行实现的,因此可以不受物理条件制约地定制指令集与执行引擎的结构体系,能够执行那些不被硬件直接支持的指令集格式。
  • JVM的主要任务是负责装载字节码到其内部,但字节码并不能够直接运行在操作系统之上,因为字节码指令并非等价于本地机器指令,它内部包含的仅仅只是一些能够被JVM所识别的字节码指令、符号表,以及其他辅助信息。
  • 想要让一个Java程序运行起来,执行引擎(Execution Engine)的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以。简单来说,JVM中的执行引擎充当了将高级语言翻译为机器语言的译者。

执行引擎工作过程:

  • 执行引擎在执行的过程中究竟需要执行什么样的字节码指令完全依赖于PC寄存器。
  • 每当执行完一项指令操作后,PC寄存器就会更新下一条需要被执行的指令地址。
  • 当然方法在执行的过程中,执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。
  • 从外观上来看,所有的Java虚拟机的执行引擎输入、处理、输出都是一致的:输入的是字节码二进制流,处理过程是字节码解析执行、即时编译的等效过程,输出的是执行过程。
    在这里插入图片描述

本地方法接口(Native Interface)

是一种允许Java代码调用本地方法(如C或C++编写的方法)的机制。这种技术通常用于实现高性能的计算密集型任务,或者与底层系统库进行交互。(此处都是与底层系统相关的交互,就不过多描述,想学习此块内容建议去官方)

JVM运行步骤

  1. 通过javac命令把java文件编译为.class的字节码文件;
  2. 通过java命令把.class字节码文件以类装载子系统加载到运行时数据区(内存)中;
  3. 通过字节码执行引擎运行内存中.class文件的代码。
    在这里插入图片描述

JVM三种垃圾回收器

  1. Minor GC
    是指新生代GC,即发生在新生代(Eden区和Survivor区)的垃圾回收操作,当新生代无法为新对象分配内存空间时,会出发Minor GC。因为新生代中大多数对象的生命周期都很短,所以发生Minor GC的频率很高,虽然它会触发STW,但是它回收速度很快。
    触发条件:
    当Eden区分配满的时候触发

  2. Major GC
    清理OldGen区,用于回收老年代,出现Major GC通常会出现至少一次Minor GC。

  3. Full GC
    是指针对整个新生代、老年代、元空间的全局范围的GC。
    触发条件:
    老年代内存空间不足;
    元空间内存空间不足;
    调用System.gc时,系统建议执行Full GC,但是不一定会执行;
    通过Minor GC后进入的老年代的平均大小大于老年代的可用内存;
    在Minor GC回收内存时,由伊甸园区和Survivor From区把存活的对象向Survivor To区复制时,对象大小大于Survivor To空间的可用内存,则把该对象转存到老年代,且老年代的可用内存大小小于该对象大小。即老年代无法存放下新生代过度到老年代的对象时,会出发Full GC。

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值