学习参考资料:周志明老师的著作《深入理解Java虚拟机(第3版)》
1.运行时数据区域
根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会分为以下几个运行时数据区域,如下图所示。
其中每个线程有自己独自的虚拟机栈、本地方法栈、程序计数器
1.1程序计数器
程序计数器(Program Counter Register) 是一块较小的内存空间,通过改变这个计数器的值来选取下一条需要执行的字节码指令。
刚才我们说程序计数器是私有的,这是因为Java虚拟机在执行多线程时,采用的是线程轮流切换,处理器分配执行时间,这样就要求每次切换后需要及时指向该线程所运行的位置且各自的计数器互不影响。
如果线程正在执行的是一个Java方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址;
如果线程正在执行的是一个本地(Native)方法,那么计数器的值为空。
1.2Java虚拟机栈
每个方法被执行时,都会在Java虚拟机栈中同步创建一个栈帧(用于存储局部变量表、操作数栈、动态连接、方法出口等信息),也就是说每个方法在执行至执行完毕后,都对应着一个栈帧从入栈到出栈的过程!
那么这个栈帧有什么作用呢?
接下来我们从栈帧的存储局部变量表开始说起:
局部变量表存放了在编译期可知的基本数据类型和对象引用;
局部变量表中的存储空间以局部变量槽来表示,64位的long和double类型会占用两个槽;存储空间在编译时就可以完成分配,即在运行时栈帧中的局部变量表的大小就已经确定好了,并且不会改变局部变量表的大小。
如果线程请求的深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;
如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError 异常。
1.3本地方法栈
本地方法栈和虚拟机栈发挥的作用是类似的,只不过虚拟机栈为虚拟机提供Java(也就是字节码)服务,本地方法栈是为虚拟机提供本地(Native)方法服务。
1.4Java堆
Java堆是内存虚拟机所管理内存中最大的一块,几乎所有的对象实例都在这里分配内存。被所有线程共享的内存区域。
Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx 和-Xms 设定)。如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError 异常。
1.5方法区
方法区也是所有线程共享的内存区域,用来存储已被虚拟机加载的类信息、常量、静态变量。
方法区只是一个抽象的概念,具体的实现是通过以下两种方式:
- 永久代:JDK1.7及以前
- 元空间:JDK1.8及以后
JDK1.7以前使用永久代来实现方法区,这样就省去了专门为方法去编写内存管理的工作。
但是随着时间的推移这种设计导致了Java应用更容易遇到内存溢出的问题(因为永久代有-XX:MaxPermSize 的上限即内存上限,不设置也会有!)
JDK1.7的时候为了临时解决上面的情况,将永久代的字符串常量池、静态变量等移到堆中。
JDK1.8把JDK1.7中永久代还剩余的内容(主要是类型信息)全部移到元空间!而元空间是占用的本地内存。
还有要注意的是,并非进入了方法区就跟永久代的名字一样不会进行垃圾回收了。相对而言,会很少出现,主要是针对常量池的回收和对类型的卸载。
后面还会陆陆续续更新这系列的读书笔记,期待您的关注~~
|