JAVA内存区域

概述

java虚拟机在执行java程序的过程中会把所管理的内存划分为若干个不同的数据区域。大致可以分为程序计数器、虚拟机栈、本地方法栈、堆、方法区五块。
java虚拟机运行时数据区

1. 程序计数器

什么是程序计数器?

程序计数器是一个记录着当前线程所执行的字节码的行号指示器。

JAVA代码编译后的字节码在未经过JIT(实时编译器)编译前,其执行方式是通过“字节码解释器”进行解释执行。简单的工作原理为解释器读取装载入内存的字节码,按照顺序读取字节码指令。读取一个指令后,将该指令“翻译”成固定的操作,并根据这些操作进行分支、循环、跳转等流程。

那么程序计数器是否多余呢?

在单线程的情况下确实不需要用到程序计数器,一个线程在执行基础功能的时候没有程序计数器完全是可以的,当多线程协同合作进行时则需要用到程序计数器。
首先我们了解JVM的多线程实现方式:JVM的多线程是通过CPU时间片轮转(即线程轮流切换并分配处理器执行时间)算法来实现的。也就是说,某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。当被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置,在JVM中,通过程序计数器来记录某个线程的字节码执行位置。因此,程序计数器是具备线程隔离的特性,也就是说,每个线程工作时都有属于自己的独立计数器。

程序计数器的特点
  1. 线程隔离性,每个线程工作时都有属于自己的独立计数器。
  2. 执行java方法时,程序计数器是有值的,且记录的是正在执行的字节码指令的地址(参考上一小节的描述)。
  3. 执行native本地方法时,程序计数器的值为空(Undefined)。因为native方法是java通过JNI直接调用本地C/C++库,可以近似的认为native方法相当于C/C++暴露给java的一个接口,java通过调用这个接口从而调用到C/C++方法。由于该方法是通过C/C++而不是java进行实现。那么自然无法产生相应的字节码,并且C/C++执行时的内存分配是由自己语言决定的,而不是由JVM决定的。
  4. 程序计数器占用内存很小,在进行JVM内存计算时,可以忽略不计。
  5. 程序计数器,是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError的区域。

2. 虚拟机栈

虚拟机栈是java方法执行的内存模型:每个方法在执行的同时会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

  1. Java虚拟机栈也是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)
  2. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;(当前大部分JVM都可以动态扩展,只不过JVM规范也允许固定长度的虚拟机栈)
  3. Java虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时会创建一个栈帧。对于我们来说,主要关注的stack栈内存,就是虚拟机栈中局部变量表部分。
栈帧(Stack Frame)

是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的java虚拟机栈的栈元素。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。

注意:

在编译程序代码的时候,栈帧中需要多大的局部变量表内存,多深的操作数栈都已经完全确定了。
因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。

栈结构图如下:
栈结构
在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。

3. 本地方法栈

本地方法栈与虚拟机栈所发挥的作用是非常相似的,他们之间的区别不过是虚拟机栈为虚拟机执行java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

4. 堆

在JVM所管理的内存中,堆区是最大的一块,堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建。堆区的存在是为了存储对象实例,原则上讲,所有的对象都在堆区上分配内存(不过现代技术里,也不是这么绝对的,也有栈上直接分配的)。

一般的,根据Java虚拟机规范规定,堆内存需要在逻辑上是连续的(在物理上不需要),在实现时,可以是固定大小的,也可以是可扩展的,目前主 流的虚拟机都是可扩展的。如果在执行垃圾回收之后,仍没有足够的内存分配,也不能再扩展,将会抛出OutOfMemoryError:Java heap space异常。

5. 方法区

在Java虚拟机规范中,将方法区作为堆的一个逻辑部分来对待,但事实 上,方法区并不是堆(Non-Heap)。

方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。

方法区在物理上也不需要是连续的,可以选择固定大小或可扩展大小,并且方法区比堆还多了一个限制:可以选择是否执行垃圾收集。一般的,方法区上 执行的垃圾收集是很少的,这也是方法区被称为永久代的原因之一(HotSpot),但这也不代表着在方法区上完全没有垃圾收集,其上的垃圾收集主要是针对 常量池的内存回收和对已加载类的卸载。

在方法区上进行垃圾收集,条件苛刻而且相当困难,效果也不令人满意,所以一般不做太多考虑,可以留作以后进一步深入研究时使用。

运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类链接阶段完成翻译);运行时常量池除了存储编译期常量外,也可以存储在运行时间产生的常量(比如String类的intern()方法,作用是String维护了一个常量池,如果调用的字符“abc”已经在常量池中,则返回池中的字符串地址,否则,新建一个常量加入池中,并返回地址)。

6. 直接内存

直接内存并不是JVM管理的内存,可以这样理解,直接内存,就是 JVM以外的机器内存,比如,你有4G的内存,JVM占用了1G,则其余的3G就是直接内存,JDK中有一种基于通道(Channel)和缓冲区 (Buffer)的内存分配方式,将由C语言实现的native函数库分配在直接内存中,用存储在JVM堆中的DirectByteBuffer来引用。 由于直接内存收到本机器内存的限制,所以也可能出现OutOfMemoryError的异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值