(二)java虚拟机内存管理

理解java虚拟机中的内存组成

Posted by chchaooo on February 28, 2018

《深入理解java虚拟机》一书对java的内存管理机制有比较详细的介绍。需要说明的是该书所介绍的是《Java虚拟机规范》中所定义的虚拟机机制。实际上各个厂家(sun,IBM)在实际实现时会根据需求和具体情况自己定义内部情况。

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和消失。

生命周期与用户线程的数据区:

程序计数器:每条线程都需要有一个独立的程序计数器。

Stack(栈):主要存储基本类型变量(int count)和对象引用。生命周期与线程相同。每个方法被被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表,操作数栈,动态连接,方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机中从入栈到出栈的过程。

Stack的速度较快,管理简单,但每次操作的数据或者指令字节长度是已知的和确定的。(栈帧中操作数栈的深度,局部变量表的大小在编译器就已经确定了)

生命周期与JVM相同的数据区:

Heap(堆)被所有线程共享,在虚拟机启动时创建。主要用于存储Object(对象实例)中的属性(属性的类型和属性值)。在Heap 中分配一定的内存保存对象实例和对象的序列化比较类似。而对象实例(例如数组,StudentBean)在Heap 中分配好以后,需要在Stack中保存一个4字节(32位系统)的Heap 内存地址,用来定位该对象实例在Heap 中的位置,便于找到该对象实例。(堆上还需要有对象类型数据在方法区中的地址,某个类的方法信息不应该每个对象实例都复制一份,因为对于不同对象来说,它们都是相同的)。

当前主流的虚拟机都是按照可扩展来实现的(通过-Xms和-Xmx控制)。如果堆中没有内存完成实例分配,且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

Method Area(方法区)被所有线程共享,在虚拟机启动时创建。主要用于存储Ojbect中的对象类型数据(如对象类型(类名),访问修饰符,常量池,父类,实现的接口,方法等)。方法区在虚拟机启动时创建。尽管方法区在逻辑上是heap的一部分,简单的实现仍然可以选择对它既不回收也不压缩。

各个数据区的回收机制:

  • Stack的内存管理是随着线程的生命周期自动管理的;
  • Heap 则是随机分配内存,不定长度,存在内存分配和回收的问题
  • Method Area则主要回收两部分内容:废弃常量和无用的类。一般回收效率很低,只有在大量使用反射,CGLib等bytecode框架的场景,以及动态生成JSP和OSGi这类频繁自定义ClassLoader的场景才需要虚拟机具备类卸载的功能,以保证方法区(永久代)不会溢出。

运行时数据区