JVM内存模型:

2021/6/29 7:22:30

本文主要是介绍JVM内存模型:,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

JVM内存模型:

主要分以下几部分:

在这里插入图片描述

主要说法:

  1. 堆是线程共享的内存区域,栈是线程独享的内存区域。
  2. 堆中主要存放对象实例,栈中主要存放各种基本数据类型、对象的引用。

java内存分配过程中,是对象的引用指向内存的区域,然后进行初始化操作。

但是堆是全局变量共享的,因此在同一个时间内可能会有两个线程进行对象的创建,如果有两个对象同时指向了一个内存区域,该如何解决?

在这里插入图片描述

为了解决这个并发操作,对象的内存分配一定是一个同步控制。但是无论使用什么去解决都会影响内存的效率。

由于对象的分配是一个高频操作,此处说明HotSpot虚拟机的方法;

每个线程在Java堆预先分配一小块内存,然后再给对象分配内存时候,直接从这块“私有的”内存里面进行分配,当这部分使用完了继续分配“私有内存”。

此方法是TLAB分配,Thread Local Allocation Buffer :线程本地分配缓冲区;

这部分缓冲区(Buffer)是从堆内存划分出来的,但是在本地线程独享的。

需要注意的是:我们说的TLAB是线程独享,而只是在分配的时候是线程独享的,至于在读取垃圾回收、其他上面是线程共享的;而且再使用上面没有什么区别。

也就是说线程初始化时候,都会有一块TLAB区域,并不是说这个内存其他线程不能访问,还是可以读写的,而无法再次内存中分配内存而已;并且在TLAB分配内存之后,并不影响垃圾回收的移动,对象可能一开始在TLAB分配,存放在Eden区,但还是会被垃圾回收会被移动到Survivor 区、老年代。

还有Eden区分配的TLAB内存并不是很大,当创建一个大对象时候,是无法进行分配的,还是会有可能分配在老年代。所以我们经常说:小的对象分配起来比大的对象效率更高。

PC寄存器:
  • 是一个比较小的内存空间。一个线程的执行,是通过字节码解释器改变当前程序的计数器的值,来获取下一条需要执行的字节码指令,来保证程序的正确执行。

  • Java支持一次运行多个线程,每个线程都有自己独立的PC寄存器,各个线程的计数器互不影响任何时候一个线程只能运行一个方法的代码。

  • 如果方法不是Native的,PC寄存器包含当前正在被执行的JVM指令地址,如果方法是Native的,PC寄存器的值是未指定的(Undefined)。

  • 程序计数器是线程安全的

    (运行数据区中唯一一个不会发生内存泄漏的区域OOM)

执行过程:

  1. 计数器读入要执行的 JVM 指令号 ;
  2. CPU 获取计数器中的指令号;
  3. CPU 根据从计数器中获取的指令号执行相对应的指令;
  4. 计数器更新要执行的 JVM 指令号。
Java虚拟机栈:
  • 就是我们常说的

  • 虚拟机栈是 Java 方法执行的内存模型;

  • 每个线程只能有一个活动栈帧,对应着当前正在执行的方法,该栈帧处于栈顶,在虚拟机栈中,每个方法都对应了一个栈帧;

  • 栈是对栈顶元素进行操作的。使用先进后出的规则。栈是私有的,它的生命周期与线程相同,每个线程都会分配一个栈的空间,就是每个线程都拥有独立的栈空间;

  • 栈中存放的是栈帧。每个方法在执行的时候都会创建一个栈帧;栈帧中包含局部变量表操作数栈,动态链接和方法出口(方法返回地址)等信息。每一个方法从调用运行到结束的过程,就跟着一个栈帧在栈中压栈出栈的过程。

    • 局部变量表:

      • 用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。储存的是基本数据类型和对象的引用,但是不包含对象的内容。在内存空间编译期间进行分配,在方法运行期间不会改变局部变量表的大小。
      • 局部变量表的最小单位是变量槽(Variable Slot),一个大小为32位,对于64位进行两个存放。
      • 为了尽可能的节省栈帧空间,局部变量表的Slot可以进行复用,方法中定义的局部变量,其他的作用域不一定去覆盖整个类,当方法运行时超出了某个方法的作用域,就是变量失效,此时这个方法对应的slot就可以给其他的变量使用。就是Slot的复用。
    • 操作数栈:可能出现的异常有:

      • 当一个方法开始执行时,操作数栈为空; 随着方法的执行,会从局部变量表或对象实例的字段中复制所需要的数据并写入到操作数栈,再随着计算的进行将栈中的元素出栈到局部变量表或返回给调用者。

      • StackOverflowError:栈溢出错误

        如果一个线程在计算时所需要用到栈大小 > 配置允许最大的栈大小,那么Java虚拟机将抛出 StackOverflowError异常;

      • OutOfMemoryError:内存不足

        栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。

    • 动态链接

      • 是指向运行时常量池的引用 ;
      • 在 class 文件中,描述一个方法调用或访问其成员变量时通过符号引用来表示的,动态链接的作用就是将这些符号引用所表示的方法转换为实际方法的直接引用。
    • 方法返回地址

      • 方法返回地址: 方法返回地址是方法调用的返回,包含正常返回(有返回值)和异常返回(无返回值),不 同的返回值类型有不同的指令;
      • 无论方法采用何种方式退出,在方法退出后都需要返回到方法调用的位置,程序才能继续执行,方法返回时可能需要在当前栈帧中保存一些信息,用来帮助它恢复上层方法的执行状态。
本地方法栈:(Native方法栈)

​ Native方法不是用Java编写的,是使用本地语言C++、C语言实现的。为了支持它需要使用传统栈,比如C语言栈,但JVM也不加载Native方法,不需要。

Java堆:
  • 堆是Java虚拟机上所管理的内存中最大的一块内存区域;

  • 堆内存对所有的线程共享,在虚拟机启动时创建;

  • 堆内存主要存放的是对象和数组的内存区域;

  • 堆内存是垃圾回收器的主要管理的区域,Java的垃圾回收器回收的就是堆上对象的内存空间;

  • 堆在逻辑上分为“新生代”和“老年代”(1:2),垃圾收集器对这两个区所使用的回收策略也不一样;

    • 新生代又分为伊甸园区(Eden)、幸存区(Survivor)8:2,Survivor区分为From和To,1:1
    • Eden区满了进行MinorGC,不需要清除的放在From,Eden再次进行MinorGC将Eden去和From区进行存活对象复制到To区,From和To进行角色交换…;15次以上没有处理的对象放入老年代。
  • MinorGC清理新生代垃圾;

    MajorGC清理老年代垃圾;

    Full GC清理整个堆空间;

方法区:
  • 方法区在逻辑上是堆的一部分,但在具体实现上不强制方法区的位置,不同的虚拟机厂 商可以有不同的实现,如 JDK1.8 之前使用永久代实现,1.8 后使用元空间实现;
  • 方法区在 JVM 启动时创建,它是所有线程所共享的;
  • 跟堆一样是线程共享的区域,一样存储已经被虚拟机加载的类信息,常量,静态变量等等;
  • 方法区用于存储类的结构:
    • 运行时常量池(含字符串常量)、静态变量、类的信息、常量;
    • 类信息: 魔数,版本号,常量池,类(字段和方法),父类和接口数组,字段,方法等信息。

​ 运行时常量池的存在是避免了频繁创建个销毁对象而影响了系统性能;比如String常量池、Integer、Double、Character、Float等等。不需要创建对象,直接从常量池里面取。

​ 并且根据不同的包装类型范围有所不同,Character是0-127,其他都是-128-127

    Integer a = new Integer(13);
    Integer a1 = new integer(13);
    Integer b = 13;
    Integer b1 = 13;
    Integer c = 199;
    Integer c1 = 199;
    System.out.println(a == a1);//false
    System.out.println(b == b1);//true
    System.out.println(c == c1);//false
		//在范围都是直接取常量池的值,new 都在堆中, 

而 JVM 的优化问题主要在线程共享的数据区中:堆、方法区



这篇关于JVM内存模型:的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程