一Java内存模型

JDK,JRE,JVM

  • JDK:JavaSEDevelopmentKit,Java开发工具,提供了编译和运行Java程序所需的各种资源和工具,包括JRE和Java开发工具。
  • JRE:JavaRuntimeEnvironment,Java运行环境,包括JVM和Java核心类库。
  • JVM:JavaVisualMachine,运行Java程序的核心虚拟机。

Java内存模型

  • 首先了解一下计算机中的存储模型。

  • RAM:易挥发性随机存取存储器(Random access Memory),即可以往存储器中随时读写数据。
    高速存取,读写时间相等。断电后数据丢失。如,计算机内存,在计算机中也叫主存,是直接与CPU交换数据的内部存储器;以及CPU里用的高速缓存。

  • ROM:只读内存(完全可以理解为硬盘),是只读存储器(Read-Only Memory),
    特性时一旦储存资料就无法再将其改变或删除。
    通常用在不需经常变更资料的系统中,并且资料不会因为电源关闭而消失。和RAM比存取速度很低。如,计算机启动用的BIOS芯片(固化在电脑主板中的一个基本的输入、输出系统)。

  • 一般情况下程序存放在ROM或Flash中,运行时需要拷贝到RAM(后面称“内存”)中执行。

  • Java内存模型JMM:为了一致定义程序中各种变量的访问规则,屏蔽不同硬件和操作对内存的不同访问规则,JMM从宏观上把内存分为堆区和栈区,堆区包括堆+方法区,栈区包括虚拟机栈+本地方法栈+程序计数器。堆栈二区最大的区别就是,堆区是线程共享的,而栈区是线程隔离的。

  1. 程序计数器:处于程序独占区,是一个非常小的内存空间,可以看成是当前线程所执行的字节码的行号指示器。

  2. 本地方法栈:为虚拟机执行native方法服务。

  3. 下面详细介绍堆和栈(特指虚拟机栈)主要存放的对象。

  • 栈用于存放与方法有关的类变量和方法内部的局部变量,堆用于存放创建的对象,堆中单独分出一个方法区用于存放类加载的内容:类的信息(属性和方法,其实属性不应该加载进来的)、静态变量和常量池(如字符串常量)等。如下图所示:

  • (1)栈:栈以帧为单位保存线程的运行状态。一个线程拥有一个栈区,一个栈区拥有多个栈帧,某个线程在第一次调用方法时开栈帧(最典型的就是主线程通过调用main方法而开栈帧),每个栈帧维护自己的局部变量表、操作数栈和动态链接(在类加载过程中,解析步骤会把一部分符号引用转为直接引用,即把方法名等转为方法所在内存地址,而剩下一部分要到执行过程中才变成直接引用,这部分就叫做动态链接),用于保存在方法执行过程中遇到的方法参数、局部变量、中间运算结果、返回值等,而且如果变量是对象的话,则只保存地址引用,并指向堆中的实例,只有基本类型才会保存值。也就是说,栈帧中只会保存对象的引用或者基本类型的值这种占容小的东西。

  • 当线程调用一个方法时,虚拟机会从方法所在的类信息中得知此方法的局部变量区和操作数栈的大小,分配内存作为栈帧,并将此栈帧压栈。方法返回后(Java方法的两种返回方式:一种是正常地以return方式返回;一种是抛出异常的非正常返回),虚拟机会将当前栈帧弹出并释放掉(随着方法执行完毕、线程终止而被清理掉,所以不需要GC),这样上一个方法的栈帧就成为当前栈帧了。
  • 如,以下程序执行到箭头那一行时,j要到栈帧里拿:

  • 栈溢出时将抛出StackOverflow异常,常见于递归调用。

  • (2)堆:JVM中最大的内存区域,存储对象实例(指的是实例内存而不是地址引用),也就是说,不管是在类中声明的成员属性,还是在方法中声明的局部变量,只要它是非基本类型,就会被存储在堆中。

  • 方法区其实也类似于堆,甚至可以把它理解为是堆区的一部分。它用于类加载过程中存储类共享的一些东西,如类的信息(名字、修饰符等)、类中的静态变量、类中的常量(方法区中开辟了一个常量池)、类中的Field信息、类中的方法信息。当通过Class对象中的getName、isInterface等方法获取信息时,这些数据都来源于方法区。

  • 在虚拟机运行的所有线程创建的对象实例都放在堆中,可能是一个线程创建一个对象实例,也可能多个线程就是共享一个对象实例,就像抢票一样,票总数是多个线程只共享一份的。如果多个线程各自创建了某个类的实例,那么在各自的栈中保存了不同的引用地址,分别指向堆中不同的实例,如果该类中存在静态属性,那么堆中每个实例都持有静态属性的引用地址,它们纷纷指向方法区中同一个静态属性,于是多个线程操作同一个静态属性。

  • 堆是垃圾收集器管理的主要区域,当堆中的存储对象实例超过堆的存储空间时,即造成堆的溢出,java程序抛出内存溢出异常OutOfMemoryError。因此堆中的实例对象不再被需要时,应及时回收空间(在一定条件下被GC)。

  • 堆和栈的联系:由于栈是存储线程内类方法运行状态的内存区域,只要与方法有关的类变量和方法内部的局部变量都会被存储到栈帧,如果这些变量有的是对象引用时,它们指向的实例对象或数组必定存储在堆中。当方法执行完后,栈帧被弹出,栈帧占用的存储空间也被释放,但堆中的实例对象或数组不会被释放,它们由垃圾收集器之后释放。

  • PS:双引号包起来的全是字符串常量。

总结

  • jvm将内存划分为堆、方法区、程序计数器、虚拟机栈(VM Stack,也翻译为Java方法栈)、
    本地方法栈(Native Method Stack)。堆和方法区是线程共享的,栈和程序计数器是线程隔离的。

  • Java程序的运行过程:一个Java源文件->编译为.class字节码文件->运行在jvm上->告知jvm程序的运行入口->jvm通过字节码解释器加载运行->开始运行->从内存调东西进来,那么怎么调呢?

  • jvm初始运行时即分配堆和方法区;至于栈帧(包括虚拟机栈和本地方法栈)和程序计数器(用于指示当前线程所执行的字节码行号),是在jvm遇到一个线程时才会分配的,线程终止即回收。因此,栈和程序计数器的生命周期与所属线程相关,而堆和方法区与程序运行的生命周期相同。GC作用的场所只发生在线程共享区。

  • 内存分配参考文章12

  • 线程与堆栈的关系

  • 指针碰撞

  • 参考教程

  • 参考教程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值