JVM——Java内存模型

概述

我们的台式机有自己的内存,服务器也有自己的内存,而JVM作为一台虚拟的计算机,也有自己的内存。JVM的内存分为五大部分,分别为

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区
  • Java堆

按照线程之间的关系又可以分为三类

  • 线程私有的内存
  • 线程共享的内存
  • 直接内存

思维导图

Java内存模型如下
在这里插入图片描述
思维导图的形式:
在这里插入图片描述

程序计数器

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。简单来说,就是记录程序运行的行数

正在执行 java 方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如
果还是 Native 方法,则为空。

这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

虚拟机栈

虚拟机栈描述Java方法的执行,每一个方法执行时都会创建一个栈帧,用于存储方法运行的局部变量表,操作数栈,动态链接,方法出口,每一个Java方法从调用到执行完成,都对应一个从入栈到出栈的过程。

栈帧:是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。

本地方法栈

本地方法栈(Native MethodStacks)与虚拟机栈所发挥的作用是非常相似的,区别是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。

方法区

即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据

永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小。

运行时常量池也是方法区的一部分,Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

Java堆

创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为:

  • 新生代(Eden 区,From Survivor 区 ,To Survivor 区)
  • 老年代。

新生代

是用来存放新生的对象。一般占据堆的 1/3 空间。由于频繁创建对象,所以新生代会频繁触发MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom区、ServivorTo 三个区。

  • Eden 区:Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。
  • ServivorFrom区: 上一次 GC 的幸存者,作为这一次 GC 的被扫描者。
  • ServivorTo区:保留了一次 MinorGC 过程中的幸存者。

这里说一下这个MinorGC,它是一种垃圾收集算法,分为三步复制,清空,互换

  1. 复制:把Eden和ServivorFrom区中存活的对象复制到ServicorTo区,(如果有对象达到了老年的标准,直接方法老年代),同时把这些对象的年龄加1,如果ServicorTo区的空间不够了就放到老年代。
  2. 清空:清空Eden,ServicorFrom中的对象
  3. 互换:ServicorTo 和 ServicorFrom 互换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom区。

老年代

主要存放生命周期比较长的内存对象,老年代的对象比较稳定,所以 MajorGC 不会频繁执行。

这里再说一下这个MajorGC,它是一种标记清除算法,一般两种情况执行

  1. 在进行MajorGC之前进行了一次MinorGC,由新生代的对象进入老年代,老年代空间不够了
  2. 无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC 进行垃圾回收腾出空间

首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。

MajorGC 的耗时比较长,因为要扫描再回收。MajorGC 会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异常。

永久代

指内存的永久保存区域,主要存放 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永久区域

GC 不会在主程序运行期对永久区域进行清理

当加载的class过多的时候会抛出OOM异常(out of memory)

元数据区

在Java8中,去除了永久代,被一个称为“元数据区”(元空间)的区域所取代。

与永久代区别:元数据区的并不在虚拟机中,而是使用的本地内存

类的元数据放入 native memory, 字符串池和类的静态变量放入 java 堆中

加载多少类受物理内存的限制,而不是堆内存的限制

直接内存

直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值