【转载】Java学习笔记:内存管理详解
JVM内存结构

线程私有区域:程序计数器与栈内存
- 程序计数器是JVM中最小的内存区域,每个线程独立拥有一个,用于记录当前线程执行的字节码指令地址。它是线程私有的,不会出现内存溢出问题。在多线程环境下,程序计数器确保线程切换后能恢复到正确的执行位置。
- Java虚拟机栈同样是线程私有的,其生命周期与线程相同。每当一个方法被调用时,JVM会在栈中创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接和方法返回地址等信息。局部变量表存放基本数据类 型和对象引用,而操作数栈则用于方法执行过程中的计算操作。栈深度超过限制时会抛出StackOverflowError,而动态扩展时无法申请足够内存则会导致OutOfMemoryError。
- 本地方法栈与虚拟机栈类似,但服务于Native方法。在HotSpot虚拟机中,本地方法栈和虚拟机栈通常合二为一。当调用JNI方法时,本地方法栈负责管理本地方法的调用状态。
线程共享区域:堆与元空间
- 堆是JVM中最大的内存区域,被所有线程共享,用于存储对象实例和数组。现代JVM普遍采用分代收集算法管理堆内存,将其划分为新生代(Young Generation )和老年代(Old Generation)。新生代又分为Eden区和两个Survivor区(From和To),新创建的对象首先分配在Eden区,经过多次GC后存活的对象会晋升到老年代。这种分代设计基于"弱代假说",即大多数对象生命周期短暂,优化了垃圾回收效率。
- 元空间(Metaspace)是方法区在JDK8 及以后的实现,取代了永久代(PermGen)。它存储类元信息、运行时常量池、静态变量和即时编译器编译后的代码等数据。与永久代不同,元空间使用本地内存(Native Memory),默认情况下只受系统可用内存限制,避免了永久代常见的OOM问题。元空间的引入解决了永久代调优困难、容易内存泄漏等问题,同时支持更灵活的类元数据管理。
内存区域间的交互原理
当Java程序创建一个对象时,JVM首先在堆中分配内存空间,然后将对象引用存储在栈帧的局部变量表中。对于静态变量和类信息,则存储在元空间中。方法调用时,当前方法的栈帧被压入虚拟机栈,方法执行完毕后栈帧弹出。本地方法调用则通过本地方法栈管理。
对象访问定位通常通过直接指针和句柄两种方式实现。HotSpot主要使用直接指针访问,对象引用直接指向堆内存中的对象实例,访问速度更快。而句柄方式则在堆中维护一个句柄池,引用指向句柄,再由句柄指向实际对象实例,这种方式在对象移动时(如GC时)只需修改句柄,引用本身不需要改变。
内存结构的演进与优化
从JDK7到JDK8,JVM内存结构最显著的变化是永久代被元空间取代。这一变化源于永久代的固有缺陷:固定大小容易导致OOM、调优困难、Full GC频繁等。元空间使用本地内存,由系统自动管理,大大降低了内存溢出的风险,同时提高了类加载和卸载的效率。
现代JVM还引入了压缩指针(Compressed Oops)技术,在64位系统中将64位指针压缩为32位,既节省了内存空间,又保持了寻址能力。对于大内存应用,JVM提供了G1、ZGC等垃圾收集器,针对不同内存区域特点进行优化,减少GC停顿时间。

浙公网安备 33010602011771号