浅析Java的内存模型

一、前言

??之前过年在家,空闲时间比较多,想要了解一下JVM相关的内容,于是买了《深入理解Java虚拟机》这本书,过了一遍其中的基础知识。时隔多日,都忘得差不多了。为了重新捡起来,我决定复习一遍,并编写相关的系类博文加深印象,这是第一篇,来讲一讲JVM最基础的内容——Java的内存模型。

二、正文

?2.1 Java内存分布

??Java的内存主要分为五个部分:

  1. 程序计数器;
  2. Java虚拟机栈;
  3. 本地方法栈;
  4. 堆内存;
  5. 方法区;

??具体结构如下图所示:

2.2 程序计数器

??首先看第一部分——程序计数器。上过计算机组成原理的可能都听过这个名词,也大概知道它的作用是指向下一条需要执行的计算机指令,而在JVM中,程序计数器的作用也是类似。

??程序计数器是JVM中较小的一块内存空间,它的作用是记录下一条需要执行的字节码指令的地址Java代码编译后的字节码在未经过实时编译器编译前,其执行方式是通过“字节码解释器”进行解释执行)。在JVM的概念模型里,就是通过改变这个计数器的值来辅助程序的执行。选择,循环,方法调用,异常处理等功能都需要通过程序计数器的辅助来完成。

??JVM的多线程和CPU的多线程处理类似,都是使用时间片轮转的方式来执行各个线程,即每个线程轮流得到一个时间片,执行完这个时间片后,等待下一次调度,而每次只有一个线程在执行。正因为如此,线程在调度后,为了能够恢复到这个线程上一次执行的位置,就需要程序计数器的帮助,让程序计数器来告知下一条需要执行的指令的地址,对于每一个线程都是如此。所以,JVM中的程序计数器是线程私有的,即每一个线程都有一个独立的程序计数器,辅助当前线程的运行,各个线程之间互不影响。

??如果当前JVM执行的是一个Java方法,则程序计数器的值就是正在执行的字节码指令的地址;若当前执行的是一个本地方法(如Objecthashcode方法),则程序计数器的值为空(Undefined),因为本地方法并不会被加载到JVM的内存空间中。

?2.3 Java虚拟机栈

??虚拟机栈和程序计数器一样,也是线程私有的。Java虚拟栈是Java方法执行时需要依赖的内存空间,这个栈中的元素被称为栈帧。栈帧可以理解为一个对象,它记录了一个方法的局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法对应一个栈帧,而每一个方法从执行到返回,就对应着它所属的栈帧从入栈到出栈的过程,即当一个Java方法被调用,JVM就会创建这个方法的栈帧,并将它加入的虚拟机栈中,执行结束后才会出栈。对于JVM来说,正在执行的是虚拟机栈栈顶的栈帧所对应的方法,这个栈帧被称为当前栈帧,栈帧对应的方法被称为当前方法

??下面我来简单介绍一下栈帧最重要的两个部分:

  • 局部变量表:局部变量表是用来存放方法的参数以及在方法中定义的局部变量,它的大小在编译期间就被确定,并写入到class文件中。若一个变量是基本数据类型,则在局部变量表中存放的就是这个变量的具体值;若是引用类型,存放的则是这个变量所引用对象的地址。局部变量表的最小单位是变量槽(Slot),对于doublelong类型的变量,在局部变量中需要占用两个单位,其他类型只需要占用一个单位。
  • 操作数栈:从名字可以看出,这是一个栈Stack,它存放的是:方法运行时需要用到的操作数。和局部变量表一样,它的最大深度也是在编译期间就被计算好,并被写入class文件中的。在一个方法刚准备运行前,它是空的,而在运行的过程中,不断的有操作数被入栈和出栈(比如假设方法中需要计算num = 1 + 2,则在这条语句执行时,12被入栈,然后被取出,计算出3存入栈中,再将3取出赋值给num),程序的计算都需要依靠这个栈进行。操作数栈可以存放任意类型的数据(基本数据类型和引用类型,引用类型存放的是地址),而doublelong类型将占用两个单位,其余类型占用一个单位。

??在虚拟机规范中规定,若线程的虚拟机栈的深度超过了JVM允许的最大深度,将抛出StackOverflowError(比如调用没有出口的递归方法);若JVM实现的虚拟机栈可以动态扩展,则如果在扩展时没有申请到足够的内存,将抛出OutOfMemoryError异常。

?2.4 本地方法栈

??本地方法栈的作用和虚拟机栈相同,不同的是,虚拟机栈是辅助Java方法的运行,而本地方法栈是辅助本地方法的运行。在虚拟机规范中,并没有规定实现本地方法栈使用的语言,数据结构以及使用方式,所以JVM可以自由地实现它。与虚拟机栈一样,这一块内存区域也可能抛出StackOverflowErrorOutOfMemoryError异常,同时也是线程私有

?2.5 堆内存

??这一块内存是JVM中最大,可能也是最重要的一块内存,而堆存在的唯一目的就是存放实例对象,几乎所有的对象都是在堆中分配内存。和之前所说的几块内存不同,堆内存是所有的线程共享的,它在虚拟机启动时被创建

??堆内存是垃圾回收的主要区域,从内存回收的角度看,现在的垃圾回收机制一般都是分代收集,所以堆内存可以被分为新生代和老年代。新生代用来存放生命周期短的对象,这一块区域将频繁进行垃圾回收;老年代用来存放生命周期长的对象(或者在新生代内存不足时代替新生代存放对象),所以垃圾回收的频率较低,甚至不进行垃圾回收。

??根据Java虚拟机规范规定,堆可以是不连续的内存空间,只需要逻辑上连续即可。在实现虚拟机时,可以将堆实现为固定大小,也可以实现为可扩展的,当前的虚拟机一般都是采用可扩展的方式(可以通过配置参数来改变堆的大小)。如果堆中的内存被占满,实例对象无法存入,且堆无法再进行扩展时,将抛出OutOfMemaryError异常。

?2.6 方法区

??和堆一样,方法区也是线程共享的,它的作用是存放类信息,常量,静态常量等内容。Java虚拟机规范对这一块区域的限制非常宽松,方法区可以选择实现为固定的或者可扩展的,也可以选择是否实现垃圾回收。方法区中的数据,很少会发生改变,所以虚拟机规范并不强制要求在此区域实现垃圾回收。这块区域进行垃圾回收的主要目的就是卸载已经不需要使用的类,但是类卸载的条件是非常苛刻的。

??在方法区中有一块内存区域,叫做运行时常量池,它的作用就是存储编译期字面量以及符号引用。在一个类中如果定义了常量,或使用了字面量(如直接使用字符串为变量赋值,String str = ”aaa“),则在编译之后,会被放入到class文件的常量池中(class文件有一个常量池部分),同时,类编译后的符号引用也会被放入常量池。当程序运行时,class中的内容被加载到方法区中,而其中常量池的内容会被放入运行时常量池里面,被所有的类共享。运行时常量池是一个动态的内存区域,可以在程序运行时,动态的为其中添加数据,比如String类的intern()方法。因为运行时常量池是方法区的一部分,所以它的大小受到方法区的限制,当无法在其中申请空间时,将抛出OutOfMemaryError异常。

?2.7 我们平常理解的Java内存模型

??我们在学习Java的过程中,经常会看到这样的内容:Java的内存分为两部分,即栈内存和堆内存,栈内存中存放基本数据类型以及对象引用,而堆内存中存放对象。

??以上是对Java内存模型的片面描述,只关注了程序员最重视的部分。在上面的描述中,栈内存指的就是本地方法栈中,局部变量表的部分;而堆内存就是Java中的堆了。

三、总结

??以上内容对Java的内存模型进行了一个简单的描述,讨论了每一个部分的基本作用,了解了上面的内容后,再遇到Java内存方面的问题,也能更好地找出错误的所在。

四、参考

  • 《深入理解Java虚拟机》

原文地址:https://www.cnblogs.com/tuyang1129/p/12499181.html

时间: 2024-10-25 12:47:19

浅析Java的内存模型的相关文章

Java虚拟机内存模型及垃圾回收监控调优

Java虚拟机内存模型及垃圾回收监控调优 如果你想理解Java垃圾回收如果工作,那么理解JVM的内存模型就显的非常重要.今天我们就来看看JVM内存的各不同部分及如果监控和实现垃圾回收调优. JVM内存模型         正如你上图所看到的,JVM内存可以划分为不同的部分,广义上,JVM堆内存可以划分为两部分:年轻代和老年代(Young Generation and Old Generation) 年轻代(Young Generation) 年轻代用于存放由new所生成的对象.当年轻代空间满时,

java String 内存模型

关于java的内存模型,参照以下的一篇文章: https://isudox.com/2016/06/22/memory-model-of-string-in-java-language/

java线程内存模型,线程、工作内存、主内存

转自:http://rainyear.iteye.com/blog/1734311 java线程内存模型 线程.工作内存.主内存三者之间的交互关系图: key edeas 所有线程共享主内存 每个线程有自己的工作内存 refreshing local memory to/from main memory must  comply to JMM rules 产生线程安全的原因 线程的working memory是cpu的寄存器和高速缓存的抽象描述:现在的计算机,cpu在计算的时候,并不总是从内存读

关Java的内存模型(JMM)

JMM的关键技术点都是围绕着多线程的原子性.可见性和有序性来建立的 一.原子性(Atomicity) 原子性是指一个操作是不可中断的.即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰. 比如,对于一个静态全局变量int i,两个线程同时对它赋值,线程A给他赋值1,线程B给它赋值为-1.那么不管这2个线程以何种方式.何种步调工作,i的值要么是1,要么是-1.线程A和线程B之间是没有干扰的.这就是原子性的一个特点,不可被中断. 但如果我们不使用int型而使用long型的话,可能

java的内存模型

java内存模型 Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽掉各种硬件和操作系统的访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果.在此之前,主流程序语言(如C/C++等)直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台上内存模型的差异,有可能导致程序在一套平台上并发完全正常,而在另外一套平台上并发访问却经常出错,因此在某些场景下就不许针对不同的平台来编写程序.Java内存模型即要定义得足够严谨,才能让Jav

Java 堆内存模型

堆内存 Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象. 在 Java 中.堆被划分成两个不同的区域:新生代 ( Young ).老年代 ( Old ).新生代 ( Young ) 又被划分为三个区域:Eden.From Survivor.To Survivor. 这样划分的目的是为了使 JVM 可以更好的管理堆内存中的对象.包含内存的分配以及回收. 堆的内存模型大致为: 从图中能够看出: 堆大小 = 新生代 + 老年代.当中,堆的大小能够通过參数 –Xms

java 的内存模型

内存模型: 为了屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的并发结果.

JAVA的内存模型及结构

所有的Java开发人员可能会遇到这样的困惑?我该为堆内存设置多大空间呢?OutOfMemoryError的异常到底涉及到运行时数据的哪块区域?该怎么解决呢? Java内存模型 Java内存模型在JVM specification, Java SE 7 Edition, and mainly in the chapters “2.5 Runtime Data Areas” and “2.6 Frames”中有详细的说明.对象和类的数据存储在3个不同的内存区域:堆(heap space).方法区(m

浅谈Java的内存模型以及交互

本文的内存模型只写虚拟机内存模型,物理机的不予描述. Java内存模型 在Java中,虚拟机将运行时区域分成6中,如下图:              程序计数器:用来记录当前线程执行到哪一步操作.在多线程轮换的模式中,当当前线程时间片用完的时候记录当前操作到哪一步,重新获得时间片时根据此记录来恢复之前的操作. 虚拟机栈:这就是我们平时所说的栈了,一般用来储存局部变量表.操作数表.动态链接等. 本地方法栈:这是另一个栈,用来提供虚拟机中用到的本地服务,像线程中的start方法,JUC包里经常使用的