java虚拟机(java virtual machine简称JVM)
为java程序一个运行时的环境,是java程序运行时需要搭载的抽象计算机。
相比C,C++而言,java在虚拟机中实现了自动内存管理机制。不需要要像C,C++一样创建后,需要通过手动回收来控制内存泄漏,内存溢出问题。java将内存回收交给了虚拟机自动进行管理。
虽然java虚拟机能实现自动内存管理机制,但对于不同环境,不同内存大小,不同的系统特性等需要依据不同的情况对内存进行合适的调整和分配。若分配不合理或代码存在漏洞时,同样的会存在内存泄漏和溢出等方面的问题。
若对java虚拟机内存区域不了解的话,分配虚拟机资源,排查错误将成为一项相对比较艰难的工作。
依据《Java虚拟机规范(Java SE7版)》在java程序执行过程中将内存划分成几个不同的运行时数据区域:程序计数器(Program Counter Register),java虚拟机栈(VM Stack),本地方法栈(Native Method Stack),java堆(Heap),方法区(Method Area,其中运行时常量为方法区的一部分)等。
程序计数器(Program Counter Register)
计数器记录的是正在执行的虚拟机字节码的地址,可通过该程序计数器来制定下一个需要执行的字节码,但如果是native方法则计数器为空。
对于多线程而言,每条线程执行的位置和顺序是不一致的,当多线程在执行时,需要各自有独立的程序计数器来保证多线程间互不影响。通常将这类的内存区域成为“线程私有”。
在整个内存空间中占的内存相对比较小,相对的也是java虚拟机规范中唯一一个也没规定OOM的区域。
java虚拟机栈(java virtual machine stacks)
描述的为java方法执行的内存模型。当每个方法在执行的同时会创建一个栈桢(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。当法法开始调用到执行结束,在虚拟机栈中都有对应的位置。
若在多线程情况下,每条线程执行的顺序不一样,因此每个线程都有对应的java虚拟机栈,因此该内存区域对每个线程也是私有的,属于隔离的数据区。
局部变量表
- 存放编译期便可知的各类基本类型。
- 存放对象引用,reference类型,指向对象起始地址引用指针,句柄或相关位置。
- 存放returnAddress类型,指向字节码指令地址。
局部变量表,在编译期间便分配好了每个方法在栈中的内存空间,在运行期间不会改变局部变量表的大小。
若迭代过深或栈深度大于所允许的深度会抛出StackOverflowError异常。
本地方法栈(Native Method Stack)
与java虚拟机栈类似,只不过本地方法栈为Native方法进行服务。对于Sun HotSpot虚拟机而已,本地方法栈和虚拟机栈合二为一。
本地方法栈可能存在StackOverflowError和OutOfMemoryErroy异常。
Java堆(java Heap)
在虚拟机启动时便为每个程序分配了堆内存,是内存存储中占比最大的一块。
该内存区域是用来存放对象实例和数字。
同时垃圾收集器主要收集的内存就是JAVA堆,所以很多时候也被叫做了“GC堆”。Java堆又细分成了老年代和新生代。新生代又分为Eden区,FromSurvivor(S0),ToSurvior(S1)区。
当java堆无法扩展时,会抛出OOM。
方法区
为线程共享的内存区域,用于存储已经被虚拟机加载的类信息、常量、静态变量、及时编译器编译后的代码等数据。
方法区在Java虚拟机规范中定义为堆的逻辑部分,又叫做Non-Heap(非堆)。同时很多人也将方法区称为“永久代”。
当方法区中午饭满足内存分配需求时,将抛出OOM异常。
运行时常量池
为方法区的一部分。Class文件中包含类的版本信息、字段、方法、接口以及常量池。常量池用于存放编译期生成的各种字面量和符号引用,该部分内容在类的加载后进入方法区的运行时常量池中存放。
Java虚拟机规范对常量池没有做细节要求。
运行时常量池相对Class文件常量池具备动态性,可在运行期间将新的常量放入池中。
当运行时常量池无法申请到内存时会抛出OOM异常。
直接内存
非虚拟机运行时数据区的一部分,也非java虚拟机规范中的内存区域,但却被频繁使用,也可能导致OOM。
NIO引入给予通道Channel与缓存Buffer的I/O方式,使用Native函数库直接分配堆外内存,然后通过存储在Java对中的DirectByteBuffer对象作为这块内存的引用进行操作。
不受Java堆大小的限制,但是受到本机总内存大小以及处理器寻址空间的限制。
在做内存设置-Xmx时,要注意小于物理内存,否则容易造成直接内存的OOM。