运行时数据区
运行时数据区
程序计数器
也叫PC寄存器
-
它是一块较小的内存空间;
-
当前线程执行的字节码的行号指示器,是程序控制流的指示器;
-
每个线程都有一个独立的程序计数器,线程间的计数器互不影响,独立存储,属于“线程私有”的内存;
-
如果线程正在执行的是一个java方法,那么程序计数器记录的应该是正在执行的虚拟机字节码指令的地址;
-
如果线程正在执行的是一个本地(Native)方法,程序计数器的值应该为空(Undefined);
-
是唯一一个在《java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域
虚拟机栈
-
线程私有
-
描述了java方法执行的线程内存模型:
-
每个方法被执行时,java虚拟机栈会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;
-
每一个方法被调用直至执行完毕的过程,都对应着一个栈帧从入栈到出栈的过程
-
-
两种异常情况:
-
StackOverflowError:线程请求的栈深度大于虚拟机允许的深度时,会抛出StackOverflowError
-
OutOfMemoryError:如果虚拟机栈可动态拓展,当栈拓展时无法申请到足够的内存时会抛出OutOfMemoryError
-
局部变量表
-
存放了编译期可知的各种java虚拟机基本数据类型、对象引用和returnAddress类型
-
基本数据类型(boolean \ byte \ char \ short \ int \ float \ long \ double)
-
对象引用(引用地址或句柄)
-
returnAddress类型(指向一条字节码指令的地址)
-
-
这类数据类型在局部变量表中的存储空间以局部变量槽(slot)表示
-
64位的long和double类型的数据占用两个slot,其余类型占用一个
-
-
局部变量表所需的内存空间在编译期间完成分配
-
当进入一个方法时,需要在栈帧中分配多大空间的局部变量表是完全确定的,在方法运行期间不会改变
-
本地方法栈
-
虚拟机栈是为虚拟机执行java方法(字节码)服务,而本地方法栈是为虚拟机执行本地(Native)方法服务
-
具体的虚拟机可以根据需要自由实现它,例如HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一
-
两种异常情况,同虚拟机栈:
-
StackOverflowError:线程请求的栈深度大于虚拟机允许的深度时,会抛出StackOverflowError
-
OutOfMemoryError:如果虚拟机栈可动态拓展,当栈拓展时无法申请到足够的内存时会抛出OutOfMemoryError
-
java堆
-
是虚拟机所管理的内存最大的区域,存放对象实例和数组
-
被所有线程共享,在虚拟机启动时创建
-
java堆是垃圾收集器管理的内存区域(GC堆)
-
从内存分配的角度,由所有线程共享的java堆,可以划分成多个 线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB),提升对象的分配效率
-
java堆可以处于连续的物理上不连续的内存空间
-
java堆可以被实现成固定大小,也可以被实现成可扩展大小,主流的虚拟机都是可扩展,通过参数:-Xmx、-Xms调节
-
OutOfMemoryError:如果堆中没有内存完成实例分配,且堆无法再扩展时,会抛出OutOfMemoryError异常
方法区
-
所有线程共享,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
-
JDK8之前 用“永久代”来实现方法区
-
这样使得HopSpot的垃圾收集器能够向管理堆一样管理这部分内存,省去了专门为方法区编写内存管理代码的工作。
-
BEA 的 JRockit、IBM的 J9都不存在永久代的概念。
-
使用“永久代”并不是一个好主意,这导致了:
-
java应用更容易遇到内存溢出的问题,因为永久代有 -XX:MaxPermSize 的上限,即使不设置也有默认大小,而 J9和 JRockit只要没有触碰到进程可用内存的上限,例如32位系统中的4GB就不会出现问题
-
有极少数的方法,例如String::intern() 会因永久代的原因,在不同虚拟机下有不同的表现
-
-
-
永久代与方法区在HotSpot的变化:
-
JDK6开始打算放弃永久代,逐步采用本地内存实现方法区;
-
JDK7把原本放在永久代的 字符串常量池、静态变量等移出(移到堆中);
-
JDK8完全废弃了永久代的概念,改用与 JReckit、J9一样在本地内存中实现的“元空间”
-
-
这个区域的内存回收:目标主要针对常量池的回收和类型的卸载
-
OutOfMemoryError:如果方法区无法满足新的内存分配需要时,会抛出OutOfMemoryError异常
运行时常量池
-
是方法区的一部分
-
用于存放编译器生成的各种字面量与符号引用,部分内容将在类加载后存放在方法区的运行时常量池中。除了Class文件中描述的符号引用,还会把由符号引用翻译过来的直接引用放在运行时常量池中
-
动态性,运行期间也会有新的常量放入池中 (如intern()方法)
-
OutOfMemoryError:如果常量池 无法满足新的内存分配需要时,会抛出OutOfMemoryError异常
直接内存
-
直接内存并不是虚拟机运行时数据区的一部分;
-
也可能导致OutOfMemoryError异常。
本机直接内存的分配不受java堆大小的限制
NIO
https://www.jianshu.com/p/5bb812ca5f8e
NIO(JDK1.4)模型是一种同步非阻塞IO,主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(多路复用器)。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(多路复用器)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
基于通道(Channel)与缓冲区(Buffer)的I/O方式。
它可以使用Native本地库直接分配堆外内存,然后通过存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。避免了在java堆和Native堆之间来回复制数据,提高了性能。
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步