JVM-内存模型

本文转自 http://blog.youkuaiyun.com/zhshulin/article/details/50389981

本篇其实就是一个读书笔记,书是《深入理解JAVA虚拟机》,在网上搜索JAVA内存,说的比较好的其实很多都源自这本书,作为一个JAVA程序员,理解虚拟机是通向高级程序员的必经道路。本篇中的图片源自当时网上,具体出处找不到了,请见谅。

这里写图片描述

所谓的线程安全,其实就是不同线程对同一个资源的访问和修改引发的数据不一致问题。

对于线程安全的理解,需要了解JVM中的内存模型。JAVA中一个线程创建的时候,JVM就会为其分配一个主内存和工作内存,每个线程都有自己的完全独立的工作内容,而主内存却是他们所共享的,那么问题来了,线程不安全就是由于共享主内存导致的。一旦多个线同时对主内存中的同一个资源进行操作,那么就可能导致资源状态的不一致。我们知道当一个线程需要和主内存进行交互的时候,数据从主内存拷贝到工作内存中,在线程实际处理的时候,数据其实是一个副本,当线程处理完了之后再去更新主内存,正是由于这种机制,故而会产生线程的安全性问题,需要对资源加锁来解决这种问题。

这里写图片描述

1、程序计数器
这是个什么鬼呢?我们都知道,CPU的计算时间是以分片的方式给到每个线程的,换句话说,所谓的并行其本质就是串行。比如线程A执行到了一部分,CPU将控制权给了线程B,那么线程A重新得到CPU的资源时,如何恢复工作呢?这个程序计数器就来帮助线程A找到其中间状态,从而恢复到正确的执行位置。程序计数器所占内存是线程私有的,同时也是Java 虚拟机规范中没有规定任何OutOfMemoryError 情况的区域。

2、JAVA虚拟机栈
它也是线程私有的,它所占有的内存空间也就是我们平时所说的“栈(stack)内存”。并且和线程的生命周期相同。虚拟机栈描述的是Java 方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame ①)用于存储局部变量表(基本数据类型,对象的引用和returnAddress类型)、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

局部变量表需要的内存在编译期间就确定了,故而方法运行期间不会改变其大小。

在Java 虚拟机规范中,对这个区域规定了两种异常状况:
a.如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;
b.如果虚拟机栈可以动态扩展(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度 的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。

3、JAVA堆
JAVA堆一般是JVM管理的内存中最大的一块,JAVA堆在主内存中,是被所有线程共享的一块内存区域,其随着JVM的创建而创建,堆内存的唯一目的是存放对象实例。同时JAVA堆也是GC管理的主要区域

如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java 堆中还可以细分为:新生代和老年代;再细致一点的有Eden 空间、From Survivor 空间、To Survivor 空间等。
如果从内存分配的角度看,线程共享的Java 堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。
不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步划分的目的是为了更好地回收内存,或者更快地分配内存。

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。

4、本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(譬如Sun HotSpot 虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。

5、方法区

方法区也是各线程共享的一个内存区域。主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

虽然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java 堆区分开来。

Java 虚拟机规范对这个区域的限制非常宽松,除了和Java 堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样“永久”存在了。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载,一般来说这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻,但是这部分区域的回收确实是有必要的。在Sun 公司的BUG 列表中,曾出现过的若干个严重的BUG 就是由于低版本的HotSpot 虚拟机对此区域未完全回收而导致内存泄漏。

根据Java 虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError 异常。

6、常量池
Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表(constant_pool table),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放(JDK1.7开始,常量池已经被移到了堆内存中)。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)。

<think>首先,用户的问题是:“我想获取JVM内存模型的简述”,意思是“我想获取JVM内存模型的简述”。所以,我需要提供一个简洁的、概括性的JVM内存模型描述。 从系统级指令: - 所有行内数学表达式必须使用 $...$ 格式。 - 独立公式必须使用 $$...$$ 格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 用户提到了“参考站内引用”,并提供了多个引用: - 引用[1]:讨论了JMM和JVM的区别和关联。 - 引用[2]:简要介绍了JVM是什么。 - 引用[3]:描述了JVM内存模型的部分和内存溢出情况。 - 引用[4]:提供了JVM内存模型的简述,包括程序计数器、虚拟机栈等。 用户说:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 但在这个对话中,这是第一次交互,没有上一次问题。用户指的是“用户上一次的问题”,但在当前上下文中,似乎是这个问题的开始。所以,我应该基于用户当前的查询来回答。 核心任务:简述JVM内存模型。 从引用中提取关键点: - JVM内存模型包括: - 线程共享区域:堆内存、元空间(方法区的实现)、直接内存(在某些引用中提到)。 - 线程私有区域:程序计数器、虚拟机栈、本地方法栈。 - 具体组件: - **程序计数器**:线程私有,存储当前指令地址。 - **虚拟机栈**:线程私有,存储方法调用栈帧,包含局部变量等。 - **本地方法栈**:线程私有,用于native方法。 - **堆内存**:线程共享,存储对象实例。 - **元空间**:线程共享,存储类元数据(方法区的实现)。 - 内存溢出情况: - 堆内存溢出:Java heap space。 - 栈溢出:StackOverFlowError。 - 元空间溢出:Metaspace OutOfMemory。 - 直接内存溢出:Direct buffer memory(从引用[3])。 用户要求“简述”,所以保持简洁。不需要深入细节。 生成相关问题:在回答结束时,生成相关问题。 回答结构: 1. 先提供一个概述。 2. 然后简要描述各个内存区域。 3. 最后添加相关问题。 使用中文:整个回答用中文。 引用标识:如果引用内容,在末尾添加[^1]等。但用户提供了引用,我应该自然地引用它们。 现在,构建回答: - 引言:简述JVM内存模型是什么。 - 描述组件: - 程序计数器 - 虚拟机- 本地方法栈 - 堆内存 - 元空间 - 简要提到内存溢出(可选,因为用户问简述)。 - 结束。 相关问题:例如, 1. JVM内存模型与JMM的区别? 2. JVM内存溢出的常见原因? 3. 如何优化JVM内存使用? 确保LaTeX:在这个主题中,可能没有数学表达式,但如果有变量或方程,用$...$。例如,如果在描述大小,但这里不需要。 最终响应结构: - 简述内容。 - §§相关问题§§部分。 </think>### JVM内存模型简述 Java虚拟机JVM内存模型描述了Java程序运行时内存的分配和管理结构,主要包括线程共享区域和线程私有区域。这些区域协同工作,确保程序的正常执行和对象存储。基于引用[3]和[4],JVM内存模型的核心组件可简述如下: 1. **程序计数器(Program Counter Register)**: 线程私有,用于存储当前线程执行的字节码指令行号指示器。每个线程都有自己的程序计数器,确保线程切换后能正确恢复执行位置。它是较小的内存区域,不会发生内存溢出错误[^3][^4]。 2. **虚拟机栈(Java Virtual Machine Stack)**: 线程私有,每个方法调用时会创建一个栈帧,存储局部变量、操作数栈、方法出口等信息。如果递归调用过深或帧数超过限制(如通过 `-Xss` 参数设置),会导致 `StackOverflowError`;如果栈空间无法扩展,则可能抛出 `OutOfMemoryError`[^3][^4]。 3. **本地方法栈(Native Method Stack)**: 线程私有,功能类似于虚拟机栈,但专门用于执行本地(native)方法(如C/C++实现的函数)。同样可能发生栈溢出或内存溢出错误[^3][^4]。 4. **堆内存(Heap)**: 线程共享,是所有对象实例和数组的分配区域,存储对象的实际数据。堆内存由垃圾回收器管理,如果对象分配过多或发生内存泄漏(如大对象未释放),会抛出 `java.lang.OutOfMemoryError: Java heap space`[^3][^4]。 5. **元空间(Metaspace)**: 线程共享,是方法区的实现(在JDK 1.8后取代永久代),存储类元数据信息(如类结构、常量池等)。元空间使用本地内存(非堆内存),如果类加载过多(如动态生成类),可能导致 `java.lang.OutOfMemoryError: Metaspace`[^3][^4]。 6. **直接内存(Direct Memory)**(非标准JVM组件,但常见于实际应用中): 线程共享,用于NIO操作(如ByteBuffer),通过堆外内存直接访问系统资源。如果分配过大,会引发 `java.lang.OutOfMemoryError: Direct buffer memory`[^3]。 JVM内存模型通过上述区域实现内存隔离和高效管理,其中线程私有区域(程序计数器、虚拟机栈、本地方法栈)确保线程安全,而线程共享区域(堆、元空间)支持对象存储和类加载。需注意,JVM内存模型JVM Memory Structure)与Java内存模型(JMM)不同:JMM关注线程间数据共享和可见性规则(如主内存和工作内存),而JVM描述物理内存分配细节[^1]。总体上,JVM内存模型优化了跨平台执行效率,但需合理配置以避免溢出问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值