JVM运行时数据区域

  java虚拟机再执行Java程序的时候把它所拥有的内存区域划分了若干个数据区域。这些区域有着不同的功能,各司其职。这些区域不但功能不同,创建、销毁时间也不同。有些区域为线程私有,如:每个线程都有自己的程序计数器,则程序计数器随着用户线程创建而创建,随其销毁而销毁。而有些区域是所有线程共有的,如堆是被所有线程所共有的。

1.程序计数器(线程隔离)


程序计数器是一块较小的内存空间。它的作用是记录这个线程下一个指令的位置。Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间的方式来实现的。在多线程当中,线程在执行完自己所被分配到的时间片后会被切换线程,我们无法保证线程执行完毕后再被切换(时间片的大小是不确定的)。为了保证线程切换回来后能恢复到正确的执行位置。所以我们需要程序计数器为我们记录下一条指令的地址,等线程重新抢回CPU时,去这个程序计数器当中取到下一条指令的地址,继续往下执行,这就是程序计数器的主要作用。所以每个线程都需要自己的程序计数器,互不影响。所以程序计数器是线程私有的。

2.Java虚拟机栈(线程隔离)

当Java程序执行时,每个线程都会在Java栈上创建一个对应的栈帧(Stack Frame),栈帧用于存储方法的调用信息和局部变量等数据。每个方法在执行时都会创建一个新的栈帧,并且随着方法的调用和返回而入栈和出栈。

Java栈的主要功能是支持方法调用和方法返回操作。当一个方法被调用时,JVM会为该方法创建一个新的栈帧并将其推入当前线程的Java栈顶部。栈帧包含了方法的参数、局部变量、操作数栈等信息。

下面是Java栈中的主要组成部分:

  1. 局部变量表(Local Variable Table):局部变量表用于存储方法中定义的局部变量和方法参数。每个栈帧都有一个局部变量表,它是一个固定长度的数组。局部变量表中的每个元素可以存储一个基本数据类型或者一个对对象的引用。

  2. 操作数栈(Operand Stack):操作数栈用于存储方法执行过程中的操作数。当方法调用时,参数被压入操作数栈中,方法执行过程中的计算结果也通过操作数栈进行传递。操作数栈是一个后进先出(LIFO)的数据结构。

  3. 动态链接(Dynamic Linking):动态链接用于将符号引用解析为实际内存地址。当方法调用其他方法或访问其他类的成员时,JVM需要进行动态链接来确定具体的方法或字段。

  4. 方法返回地址(Return Address):方法返回地址用于记录方法调用后需要返回的位置。当一个方法调用另一个方法时,JVM会将调用方的返回地址入栈,以便在方法返回时恢复执行。

Java栈的大小在JVM启动时就被固定下来,每个线程的Java栈大小可以通过参数进行调整。当Java栈空间不足时,会抛出StackOverflowError异常。同时,如果栈帧过多导致Java栈的总大小超过了限制,也会抛出OutOfMemoryError异常。

3. 本地方法栈(线程隔离)

本地方法栈(Native Method Stack)与Java栈类似,但它专门用于执行本地方法(Native Method)。本地方法是使用非Java语言(如C、C++等)编写的方法,通过Java Native Interface(JNI)与Java程序进行交互。

本地方法栈的主要功能是支持本地方法的调用和执行。与Java栈相似,每个线程在执行期间都会在本地方法栈上创建一个对应的栈帧,用于存储本地方法的调用信息和局部变量等数据。每个本地方法的执行也会创建一个新的栈帧,并且随着方法的调用和返回而入栈和出栈。

本地方法栈与Java栈的区别在于,本地方法栈专门用于执行本地方法的调用和返回操作。本地方法栈的组成和功能与Java栈类似,包括局部变量表、操作数栈、动态链接等。

4.java堆(线程共享)

堆(Heap)是Java程序运行时动态分配内存的区域,用于存储对象实例和数组。堆是所有线程共享的,它在JVM启动时被创建,并且在JVM退出时被销毁。

下面是堆的一些重要特点和详细介绍:

  1. 动态分配内存:堆是动态分配内存的区域,它的大小可以根据需要进行动态调整。JVM会根据程序的内存需求自动在堆上分配和释放内存。

  2. 存储对象实例和数组:堆用于存储Java程序中创建的对象实例和数组。当通过关键字new创建对象时,对象的实例数据和引用类型的字段存储在堆上。

  3. 对象的生命周期:堆中的对象的生命周期不受方法的执行范围限制,当对象不再被引用时,它将成为垃圾(Garbage),由垃圾回收器(Garbage Collector)负责回收释放内存。

  4. 堆的分代结构:堆可以分为不同的代(Generation),通常将其划分为新生代(Young Generation)和老年代(Old Generation)。新生代用于存储新创建的对象,而老年代用于存储存活时间较长的对象。

  5. 堆的垃圾回收:堆中的垃圾由垃圾回收器自动回收。垃圾回收器会识别不再被引用的对象,并将其回收释放内存,使得堆中的空闲内存可供新对象使用。

  6. 堆的内存管理:堆的内存管理由JVM的内存管理子系统负责,包括内存分配、对象的布局和对象的回收等。JVM使用不同的垃圾回收算法和策略来优化堆的内存管理。

需要注意的是,堆是线程共享的,多个线程可以同时访问和操作堆上的对象。为了确保多线程访问的安全性,Java提供了线程同步机制(如锁、同步块等)来控制对共享对象的访问。

堆的大小可以通过JVM的启动参数进行配置。如果堆空间不足,可能会抛出OutOfMemoryError异常,这通常表示需要调整堆的大小或优化内存使用。

5.方法区(线程共享)

其实方法区是在JDK1.8以前的版本里存在的一块内存区域,主要就是存放从class文件里加载进来的类的,而且常量池也是在这块区域内的。

但是在JDK1.8之后,这块区域摇身一变,换了名字,叫做“Metaspace”,翻译过来就是“元数据空间”的意思,当然它只是改了个名,实现的功能是没变的。

以下是方法区的一些重要特点和详细介绍:

  1. 存储类的结构信息:方法区用于存储每个类的结构信息,包括类的名称、父类、接口、字段和方法的描述符、访问修饰符等。这些信息在类加载阶段被加载到方法区,并供虚拟机在运行时使用。

  2. 存储静态变量:方法区还用于存储类的静态变量(static fields)。静态变量是与类相关联的,不属于类的实例,因此被存储在方法区中。静态变量在类加载时被初始化,并在整个程序的生命周期中保持不变。

  3. 存储常量池:方法区包含了每个类的常量池,常量池用于存储类、接口、字段和方法的符号引用、字面量等常量。常量池是编译器在编译阶段生成的,包含了各种编译期间确定的字面量和符号引用。常量池的内容在方法区中被加载和存储,供虚拟机在运行时使用。

  4. 存储方法信息:方法区还存储了每个类的方法信息,包括方法的名称、参数类型、返回类型、访问修饰符等。这些信息在类加载过程中被加载到方法区,并供虚拟机在执行方法调用时使用。

  5. 方法区的垃圾回收:方法区进行垃圾回收的主要目标是回收常量池中不再使用的常量和无用的类信息。在早期的JVM版本中,方法区被认为是一个固定大小的永久代(PermGen),并且垃圾回收对它的影响较小。然而,从JDK 8开始,永久代被元空间(Metaspace)取代,元空间使用本地内存而不是虚拟机的内存,它的大小可以根据需要进行动态调整。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值