jni 读取内存_详解JVM内存区域-通俗易懂,深入浅出

本文详细介绍了Java虚拟机的内存划分,包括线程私有区域(程序计数器、虚拟机栈、本地方法栈)和线程共享区域(Java堆、方法区、直接内存)。理解这些内存区域对于解决Java程序的内存泄露和溢出问题至关重要。
摘要由CSDN通过智能技术生成

概述

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,也就是Java虚拟机运行时的数据区域,包含程序计数器、虚拟机栈、本地方法栈、Java堆、方法区、直接内存等,不同的版本会有所差异。----写文不易,点个关注呗!

996bf341432acd1aaddd11c3a888e869.png

Java虚拟机运行时数据区域

在JVM的管理下,Java程序员不需要管理内存的分配和释放,这和在C和C++中是完全不一样的。所以,在JVM的帮助下,Java程序员很少会关注内存泄露和内存溢出的问题。但是,一旦JVM发生这些情况的时候,如果你不清楚JVM内存的内存管理机制是很难定位与解决问题的。

5d381dca4d6b3ba9a1b6f2b835311a35.png

概述

线程私有区域-生命周期与线程相同

线程私有区域有程序计数器、虚拟机栈、本地方法栈。线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 HotspotVM 内, 每个线程都与操作系统的本地线程直接映射(上篇文章说过线程映射这块,大家可以去看看),因此这部分内存区域的存否跟随本地线程的生死对应)。

线程共享区域-生命周期与相对应的虚拟机实例相同

线程共享区域有Java堆,方法区。生命周期与相对应的虚拟机实例相同,随相应的虚拟机实例的启动/关闭而创建/销毁。

38ccb5d27162e0e878c239049b2e9819.png

详解各区域

程序计数器( 线程私有)

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存。Java代码编译后的字节码在未经过JIT(实时编译器)编译前,其执行方式是通过“字节码解释器”进行解释执行。简单的工作原理为解释器读取装载入内存的字节码,按照顺序读取字节码指令。多线程是通过CPU时间片轮转来实现的。也就是说,某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。当被挂起的线程重新获取到时间片的时候,它要想从被挂起的地方继续执行,就必须知道它上次执行到哪个位置,在JVM中,通过程序计数器来记录某个线程的字节码执行位置。因此,程序计数器是具备线程隔离的特性。这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。需要注意的是在执native本地方法时,程序计数器的值为空,因为native方法是java通过JNI直接调用本地C/C++库,无法产生相应的字节码。

06dc998346c7dc46d3820e3b1bd2db71.png

程序计数器内存模型

虚拟机栈( 线程私有)

描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接(Dynamic Linking)、 方法返回值和异常分派( DispatchException)栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正结束成还是异常结束(抛出了在方法内未被捕获的异常)都算作方法结束。栈里面存放着各种基本数据类型和对象的引用(-Xss),-Xss参数是用来调整JAVA虚拟机栈的。一个线程调用多个方法,只会有一个栈。栈的缺省大小为1M。

局部变量表:是一组变量值存储空间,存储方法定义的局部变量、方法的参数。实例方法中有个隐含参数“this”,所以实例方法可以访问该类的实例变量和其他实例方法。局部变量表中变量的存放顺序:this(如果是实例方法)=>参数(如果有)=> 定义的局部变量(如果有)。

操作数栈:用来存放操作数,局部变量表中的变量是不可直接使用的,如需使用必须通过相关指令将其加载至操作数栈中作为操作数使用。

动态链接:在类加载阶段中的解析阶段会将符号引用转为直接引用,这种转化也称为静态解析。另外的一部分将在每一次运行时期转化为直接引用。这部分称为动态连接。

方法出口:一般来说,方法正常退出时,调用者的PC计数器的值可以作为返回地址,栈帧中会保存这个计数器值。而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中一般不会保存这部分信息。

a1eda0d34dc5c99209886f8fa0958ef4.png

虚拟机栈

Java堆(heap线程共享)

是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以细分为: 年轻代和老年代。年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。堆内存存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。

为什么要分代?主要为了解决碎片化。如果内存碎片化严重,也就是如果对象占用不连续的内存,已有的连续内存不够新对象存放,就会触发GC。

8a593a0166d3d9edd785ad1d93b80ee9.png

堆内存

8bece83eef84ed2cdfcd37a5f6ed7244.png

堆内存常用参数

方法区(永久代,线程共享)

即我们常说的永久代(Permanent Generation),存储程序运行时长期存活的对象,比如类的元数据、方法、常量、静态变量、属性、即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型的卸载, 因此收益一般很小)。在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池。(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

为什么移除永久代?为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。有了元空间就不再会出现永久代OOM问题了

b2c8d9e4649889d80f6a08521666b21d.png

jvm内存

直接内存

直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使DirectByteBuffer 对象作为这块内存的引用进行操作, 这样就避免了在 Java堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。

最后

都看到这里了,顺便点个关注吧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值