JVM内存模型

JVM内存结构


关于JVM虚拟机

什么是JVM虚拟机?
JVM是Java Virual Machine(Java虚拟机)的缩写,JVM是一个虚构出来的计算机,是通过在一台实际的计算机上模拟计算机功能来实现的。Java虚拟机包含一套字节码指令集、一组寄存器、堆、栈、方法区等。JVM屏蔽了与具体操作系统相关的信息,使得Java程序只需要生成在虚拟机上运行的字节码,就可以在拥有Java虚拟机的操作系统平台上运行。JVM在执行字节码时,实际上还是通过虚拟机把字节码解释成具体操作系统平台的机器指令执行。

JVM虚拟机的内存结构
Java虚拟机主要分为五大模块:类装载子系统、运行时数据区、执行引擎、本地方法接口和垃圾回收模块。而运行时数据区又分为方法区、堆、虚拟机栈、本地方法栈、程序计数器。
在这里插入图片描述
在这里插入图片描述

一、类装载子系统

类加载子系统主要负责加载Class文件
加载过程主要为加载、验证、准备、解析、初始化、使用、卸载七个阶段,也叫做类的生命周期。
在这里插入图片描述

二、运行时数据区

1.程序计数器

程序计数器(Program Counter Register)是一个较小的内存空间,它可以看作是当前进程所执行的字节码的行号指示器。在运行时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,它是程序控制流的指示器,分支、循环、跳转、异常处理、线程转换执行恢复等基础功能都需要依赖这个计数器来完成。
由于Java虚拟机的多线程都要通过线程轮流切换、分配处理器执行时间的方式来实现,在任何一个时刻。一个处理器都只会执行一条线程中的指令。因此,为了在线程切换后恢复到线程的正确执行位置,每一条线程都需要有一个独立的程序计数器,各个线程之间的计数器互不影响,独立存储。由此可见,程序计数器的内存区域是线程私有的内存。
比较笼统的介绍就是说程序计数器来记录命令执行到哪一处,当发生线程转换时,当前线程,比如说主线程使用程序计数器记录程序执行到哪一个命令行,然后转换到其他线程执行,此线程还未执行完语句就被主线程抢占式调度抢占了CPU的控制权,在被抢占之前线程的私有的程序计数器记录执行到命令地址等待再一次被调度就从当前指令开始执行,而此时执行主线程先从程序计数器来查看执行到的指令的地址,从当前的指令地址开始执行语句。

2.Java虚拟机栈

与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候(方法入栈也叫做压栈),此时栈都会同步创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法的入口和出口等信息。每一个方法被调用直至执行完毕的过程,就对应一个栈帧在栈中从入栈到出栈的过程,出栈也称为弹栈。
看了很多的文章都是把Java内存区划分为堆内存(Heap)和栈内存(Stack),但是实际上Java内存的划分要比此复杂的多。
在这里插入图片描述

3.堆内存(Heap)

在Java应用程序当中,Java堆是虚拟机当中内存最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建。我们所学的Java面向对象的语言,所谓万物皆对象,而堆内存的唯一目的就是存放对象的实例,由此可见Java堆的重要性。通过new关键字和newInstance()以及clone()等创建出来的对象都存在于堆内存。
由于Java堆的目的就是来存放对象实例,所以会占用大量的内存,如果存在大量废弃的对象和大量的新生对象会消耗很大的内存空间,因此使用垃圾收集器来管理此内存区域,也被称为GC堆。从内存回收的角度看垃圾收集器采用分代收集算法,为了方便回收又把Java堆在逻辑上分为新生代和老年代,也有叫做新生区和养老区。新生代用来存放刚刚创建的对象和创建不久的对象,新生代有进一步分eden(伊甸区)、survivorSpace0和survivorSpace1。刚刚创建的对象存在于伊甸区,幸存0区和幸存1区用来存放经过一次或多次GC后还存活的对象。幸存0区和幸存1区还可以成为from区和to区,每经过一次GC都会回想交换from和to。经过十五次GC还存会的对象会进入老年代。(此处为垃圾回收机制,由于内容过于丰富不在详细解析。)
需要注意的是Java堆可以处于物理上不连续的内存空间中,但是在逻辑上必须被视为连续的。Java堆既可以实现成固定大小的,也可以是扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的,我们可以通过修改参数-Xmx和-Xms来设定堆空间的内存大小。如果堆空间内存不够时,Java虚拟机会抛出OutOfMemoryError异常。

4.方法区

方法区(Method Area)与Java堆一样,是各个线程共有的内存区域,它主要用来存储已经被虚拟机加载的类的类型信息、常量、静态变量等数据。提到方法区就会有很多疑惑,就会想到永久代这个概念。在JDK1.8之前我们都习惯把方法区成为永久代,将二者混为一谈。其实不然,本质上这两者并不是等价的,只是说使用永久代来实现方法区而已,笼统地说方法区是永久代的一部分,当然了是处于绝对的主导地位。在jdk1.8之前的版本中方法区和堆空间逻辑上是两个分开的独立部分,但实际上二者的物理地址是连续的,如下图所示
在这里插入图片描述
在JDK1.7中永久代中存储的部分数据已经开始转移到JavaHeap或Native Memory(本地内存)中了。比如说,符号引用(Symbols)转移到了本地内存,字符串常量池转移到了堆空间。看了很多人文章说JDK1.7已经不存在方法区了,这是不准确的说法。此时还是存在方法区的,只是说方法区中的大部分功能迁移到了别的空间。然后在JDK1.8中,取消了永久代的概念,随之而来的是元空间。对于1.8来说不存在方法区的说法也是不准确的,方法区是一种规范,只不过是实现的方式和空间发生了改变。元空间的概念创建之后,方法区就存在于元空间(Metaspace)(Meta这个词真的很有趣,静看Face Book改名为Meta后股价骤涨)。而此时的元空间也不再象永久代那样物理地址与堆连续,而是存在于本地内存,也被称为直接内存。

```

5.本地方法栈

本地方法栈(Native Method Stacks)与 Java 虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。
Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法。当线程调用 Java 方法时,虚拟机会创建一个栈帧并压入 Java 虚拟机栈。然而当它调用的是 native 方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java 虚拟机栈中压入新的栈帧,虚拟机只是简单地动态连接并直接调用指定的 native 方法。

总结

看了很多文章,在加上自己的见解,努力夯实基础,欢迎指正。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值