JVM 内存区域
JVM运行时内存区域
Java 虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域用途各异,创建和销毁的时间也不尽相同。根据<<Java虚拟机规范>> 规定,Java虚拟机所管理的内存将包括以下几个运行时数据区域。
程序计数器
程序计算器是一块较小的内存空间,可以看作是当前线程所执行的字节码的信号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。Java虚拟机的多线程是通过线程轮流切换、分配处理器执行时间来实现的。在同一时间,一个CPU核心只会执行一条线程中的指令。因此,为了避免多线程运行时的互相影响,每个线程都会分配一个独立的程序计数器,独立存储,为线程所私有的内存。
Java虚拟机栈
在Java中,虚拟机栈的生命周期与线程相同。其描述的是Java 方法执行的线程内存模型:
每个方法被执行的时候,Java 虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。方法调用执行完毕,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。局部变量表存放了编译器可知的各种Java虚拟机基本数据类型、对象引用和returnAddress类型。(指向一条字节码指令的地址)
在JVM规范中,虚拟机栈区域规定了两类异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlowError异常;如果虚拟机栈容量可以动态扩展,当栈扩展却无法申请到足够的内存时,会抛出OOM异常。
本地方法栈
本地方法栈与虚拟机栈所发挥的作用十分相似,区别仅在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的本地(native)方法服务。
本地方法栈中可使用任意的语言和数据结构,由虚拟机自行决定如何实现它。在HotSpot中,直接将本地方法栈和虚拟机栈合二为一了。与虚拟机栈一样,本地方法栈中,同样可能抛出SOE和OOM异常。
堆
堆(Heap)虚拟机所管理的内存中最大的一块。所有线程共享这一内存区域,在虚拟机启动时便会创建。堆的作用就是存放对象实例,Java中几乎所有的对象实例都在这里分配内存。
堆是垃圾收集器所管理的内存区域,从内存回收角度,由于现代垃圾收集器大部分都基于分代收集理论设计,所以Java堆中经常出现"新生代","老生代","永久代","Eden 空间","From Survivor空间"等名词,这类区域仅是一部分垃圾回收器的共同特性,而非JVM具体实现的固有内存布局,也不是JVM规范中对堆的细致划分。将堆细分的目的也只是为了更好地回收内存,或者更快地分配内存。堆大小可以是动态扩展的,当堆无法再扩展是会抛出OOM。
方法区
方法区(Method Area)用于存储已被虚拟机加载的Class文件中的类型信息、常量、静态变量和即时编译器编译后的代码缓存等数据。在JDK8 之前,HotSpot使用堆中的"永久代"来实现方法区,这样使得HotSpot的垃圾收集器能够像管理Java堆一样管理这部分内存,省去了专门为方法区编写内存管理代码的工作。在JDK8之后,HotSpot废弃了"永久代"的概念,将方法区移至由本地内存实现的元空间中。
运行时常量池
运行时常量池为(Runtime Constant Pool)方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容在类加载后存放到方法区的运行时常量池中。运行时常量池相对于Class文件常量池有一个重要特征就是具备动态性,并不要求常量一定只有编译期才能产生,也就是说,在运行期,也可以将新的常量放入池中,如String.intern()方法。
直接内存
直接内存(Direct Memory)并不是JVM运行时数据区的一部分,但这一区域也被频繁的使用,并且也有可能导致OOM。在JDK 1.4中新加入的NIO类,引入了Channel和Buffer的IO方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java 堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。在部分场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
堆外内存其实就是机器内存,在C语言中,分配的就是机器内存。分配在堆内的数据,要通过网络IO发送时,需要先拷贝到堆外内存,再写入到Socket里发送出去;而直接分配在堆外内存,便无需将数据从堆中移动到堆外内存,性能更好。
堆外内存也会被垃圾收集器回收,回收解析可参考https://www.jianshu.com/p/35cf0f348275。在无法申请堆外内存时,主动调用System.gc()强制执行Full GC。需要注意的是如果配置了-XX:+DisableExplicitGC,会导致System.gc()不可用,可能会影响到堆外内存的回收。JDK8 中堆外内存默认大小为64m,可通过-XX:MaxDirectMemorySize=128m 手动配置。
private static long directMemory = 67108864L;
public static void saveAndRemoveProperties(Properties var0) {
if (booted) {
throw new IllegalStateException("System initialization has completed");
} else {
savedProps.putAll(var0);
String var1 = (String)var0.remove("sun.nio.MaxDirectMemorySize");
if (var1 != null) {
if (var1.equals("-1")) {
directMemory = Runtime.getRuntime().maxMemory();
} else {
long var2 = Long.parseLong(var1);
if (var2 > -1L) {
directMemory = var2;
}
}
}
var1 = (String)var0.remove("sun.nio.PageAlignDirectMemory");
if ("true".equals(var1)) {
pageAlignDirectMemory = true;
}
var1 = var0.getProperty("sun.lang.ClassLoader.allowArraySyntax");
allowArraySyntax = var1 == null ? defaultAllowArraySyntax : Boolean.parseBoolean(var1);
var0.remove("java.lang.Integer.IntegerCache.high");
var0.remove("sun.zip.disableMemoryMapping");
var0.remove("sun.java.launcher.diag");
var0.remove("sun.cds.enableSharedLookupCache");
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)