JVM内存模型
java语言之所以能够实现跨平台主要是依赖于java虚拟机也就是jvm,Java编写完代码后被编译成二进制文件,然后由jvm加载运行,因此只要支持jvm的系统都支持java以及基于jvm运行的其他编程语言,例如scala,groovy 等。今天我们不讨论jvm是如何支持二进制文件加载运行的,来聊聊在jvm中内存是如何分配的。
类加载子系统涉及到java中的ClassLoader负责加载java二进制文件,即可以从本地加载也可以通过自定义ClassLoader从网络下载,类加载子系统所加载的文件就会放到内存的方法区,方法区不仅存储类信息还可能会存储一些运行时常量池信息。方法区是jvm的一个规范,不同的虚拟机可以进行不同的实现,在ibm的j9和oracle的jRocket中并不存在,我们经常提到的方法区是指基于HotSpot中的永久带,在jdk8中永久带被移除,替代永久带的实现交metaspace。因此jdk7以前要想控制永久带的大小通过参数
-XX:PermSize=5M -XX:MaxPermSize=7M,
jdk8中则需要换成
-XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=512m
这点需要注意。方法区是所有线程共享的区域
Java堆在jvm启动的时候创建,几乎所有的java对象实例都存放于这里,这个区域同样是所有的线程共享。Java堆是完全自动化管理的,通过垃圾回收机制,垃圾对象会被自动清理,不需要手动释放。Java堆的结构图如下
Java堆结构示意图
在java堆中主要分为年轻代和老年代其比例默认是Eden0:Eden1:Tenured=1:1:8 Java栈是一块线程私有的内存空间,它和线程的执行有着密切的关系,线程执行主要表现为函数的调用,函数调用的数据都是通过Java栈进行传递。Java栈类似于数据结构栈,只支持入栈和出栈,在Java栈中保存的主要内容为栈帧,每一次函数的调用都会有对应的栈帧入栈,每一个函数的调用结束都会有一个栈帧被弹出,Java方法中有两种返回函数的方式,一种是正常的return返回,另外一种就是抛出异常,不管哪种方式都会导致出栈操作。下图是栈帧的数据结构 java栈帧结构示意.png
由于每次参数的调用都会生成对应的栈帧,从而占用一定的栈空间,如果栈空间不足,那么必然会影响函数的调用,当请求的栈深度大于最大的可用深度时,系统就会抛出StackOverflowError异常,因此Java虚拟机提供类参数-Xss来指定线程的最大栈空间。
局部变量表是栈帧的重要组成部分,它用于保存函数的参数以及局部变量,局部变量表中的变量只在当前函数调用中有效,函数调用结束后被销毁,因此如果函数的参数和局部变量比较多,会使得局部便量表变大,从而占用更多的栈空间,就会导致函数调用次数减少
操作数栈主要用于保存计算过程的中间结果同时作为计算过程中变量临时存储空间
栈帧数据区主要用于保存访问常量池的指针,方便程序访问常量池,此外当函数返回或者发生异常,虚拟机必须恢复调用者函数的栈帧,并让调用者继续执行。对于发生异常,虚拟机必须有一个异常处理表,方便发生异常时找到处理异常的代码,因为异常处理表也存储在栈帧数据区。