JVM超详解 (1)篇
什么是堆
- JVM的堆是一个用于存储Java对象实例的内存区域,具有特定的内存结构和管理机制,是Java程序运行时的重要组成部分。
堆的特点
-
唯一性与核心性:一个JVM实例只存在一个堆内存,它是Java内存管理的核心区域。
-
创建与大小:堆在JVM启动时即被创建,并且其空间大小在此时确定。堆是JVM管理的最大一块内存空间,尽管堆内存的大小是可以调节的。
-
内存连续性:尽管堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为是连续的。
-
共享与线程私有:所有的线程共享Java堆,但可以在其中划分线程私有的缓冲区(TLAB,Thread Local Allocation Buffer)。然而,需要注意的是,在某些特定情况下(如逃逸分析),对象实例可能会存储在栈上,而不是堆上。
-
对象存储:堆的主要作用是给对象分配空间并存放对象的实例。数组或对象永远不会直接存储在栈上,栈帧中保存的是指向对象或数组在堆中位置的引用。
-
垃圾回收:堆是垃圾回收(GC)的主要区域。方法结束后,堆中的对象不会立即被移除,而是在垃圾收集时才会被移除。从垃圾回收的角度,堆被分为新生代(包括Eden区和两个Survivor区)和老年代。新生代与老年代的空间默认比例是1:2,而在HotSpot中,Eden区和两个Survivor空间缺省所占的比例是8:1:1。几乎所有的Java对象都是在Eden区被new出来的。
什么是栈
在JVM(Java虚拟机)中,栈是另一种重要的内存区域,它用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
栈的特点
- 线程私有:每个线程在创建时都会创建一个虚拟机栈,其内部保存着一个个栈帧,对应着一次次的Java方法调用。因此,虚拟机栈是线程私有的。
- 生命周期:与线程同步创建,每个方法被执行的时候都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
- 栈帧:栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法出口等信息。
- 局部变量表:局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)。
堆和栈在(JVM)的区别
- 功能与作用:
栈内存主要用于存储局部变量和方法调用,它是程序执行的主要区域。栈的主要任务是执行程序。
堆内存则主要用于存储Java中的对象。无论是成员变量、局部变量还是类变量,它们指向的对象都存储在堆内存中。堆的主要任务是存放对象。 - 分配与释放方式:
栈内存是为编译器自动分配和释放的,例如函数参数、局部变量和临时变量等。栈的分配和释放是自动的,无需程序员干预。
堆内存则是由程序员自己申请和释放的。在堆中,程序员可以动态地分配和销毁对象。 - - 共享性:
栈内存是线程私有的,每个线程都有自己的栈空间。
堆内存是所有线程共有的,多个线程可以共享堆中的对象。 - - 异常错误:
如果栈空间不足,会抛出java.lang.StackOverFlowError异常。
如果堆空间不足,会抛出java.lang.OutOfMemoryError异常。 - - 空间大小:
栈的空间大小通常远小于堆的。栈的大小在JVM启动时确定,并且每个线程都有自己的栈空间。
堆的大小可以根据应用的需要动态调整,堆可以是固定大小的,也可以根据计算的需要进行扩展或收缩。 - - 生命周期:
栈中的局部变量随着方法的调用和结束而存在和消亡,因此其生命周期相对较短。
堆中的对象则可以在其不再被引用后,通过垃圾回收器进行回收,其生命周期相对较长。
总结来说,堆和栈在JVM中各自扮演着不同的角色,它们的功能、分配与释放方式、共享性、异常错误、空间大小以及生命周期都存在显著的差异。理解这些差异对于编写高效且稳定的Java程序至关重要。
什么是方法区
方法区(Method Area)是JVM(Java虚拟机)中的一个重要内存区域,它与Java堆一样,是各个线程共享的内存区域。方法区主要用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码缓存等数据。这些数据在程序运行期间是持久存在的,因此放在方法区中进行存储。
具体来说,方法区主要存储以下信息:
1.类型信息:包括类的结构(如字段、方法、构造函数等)以及类的名称、父类信息、接口信息等元数据。这些元数据在程序运行期间是不会改变的。
2.域信息:即类的静态变量以及常量池。常量池是方法区的一部分,用于存储类中的常量,包括字符串常量、数字常量等。
3.方法信息:包括方法的字节码,这是一种中间代码,可以被JVM解释执行或者编译成本地机器码执行。
方法区在JVM中的实现可以是永久代(PermGen)或元空间(MetaSpace)。随着JVM的发展,元空间逐渐取代了永久代,作为方法区的实现。与永久代不同,元空间使用本地内存来存储类的元数据,而不是在虚拟机设置的内存中。这使得元空间具有更好的伸缩性,可以根据应用的需要动态调整大小。
需要注意的是,方法区也是垃圾回收的目标之一。当某些类不再被使用时,它们所占用的空间会被垃圾回收器回收,以释放内存空间。
总的来说,方法区是JVM中用于存储类的元数据和静态数据的共享内存区域,它支持Java的动态特性和反射机制,是Java程序运行的重要组成部分。