最近在拜读周志明老师的深入理解Java虚拟机-第二版,这个系列的文章算是作者的学习记录。因为在面试中,或者线上调优,虚拟机的问题对于作者来说都是一个老大难的问题。所以准备趁此机会好好恶补一下。
为什么要了解Java内存区域
虚拟机虚拟机,顾名思义 一台虚拟的计算机。我们真实的计算机由各种各样的组件组成,cpu 硬盘 内存 电源 主板,每一个组件有对应的硬件协议,这些组件组合在一起通上电计算机就开始工作。那么虚拟机作为一台计算机,他运行时的载体是什么,他在载体的哪个部分运行的。没错,就是真实计算机的内存里面,作为硬件,他的容量就一定时有限的,我们不可以无限的在虚拟机中添加东西。这也就是我们为什么了解Java内存区域的原因,为了避免人为操作的原因导致程序运行时撑爆了实际的物理内存。
运行时数据区域
运行时的内存区域主要分为五大块
- 程序计数器
- 虚拟机栈
- 本地方法栈
- 方法区
- 堆 我们也可以根据线程共有私有来简单的划分
- 线程共有 方法区 堆
- 线程私有 程序技术器 虚拟机栈 本地方法栈
程序计数器
我们开发中写的代码计算机可读不懂,代码在实际运行的时候,会经过编译 变成对应计算机可以读懂的字节码文件。而程序计数器可以看成当前线程执行的方法对应的字节码文件行数。字节码解释器就是通过改变计数器的值来获取下一条需要执行的指令。
由于Java中多线程是通过线程之间轮流切换并分配执行时间来进行执行的,在任何一个时刻,一个处理器都只能处理一条指令。因此为了保证线程切换之后恢复到上一次执行停止的位置,每个线程的技术计数器应该独立。我们也把计数器这个区域叫做线程私有的内存。
虚拟机栈
简单的理解,虚拟机栈每一个元素就是一个方法,当我们要执行一个方法的时候就会压栈,等方法结束之后弹栈。栈里面的元素叫栈帧,里面主要有四块内容
- 局部变量表
- 操作数栈
- 动态链接
- 方法出口
局部变量表中存放的是编译期间可知的基础数据类型如boolean byte int long 对象的引用(不一定是对象本身,也可能是对象的引用)
局部变量表的内存空间在编译期完成分配,当进去一个方法的时候,这个方法里面变量需要的内存大小是可知的,在方法运行期间也不会改变局部变量的大小。
本地方法栈
本地方法栈和虚拟机栈基本可以说是一样的,不同的点在于 虚拟机栈里面存放的是Java方法, 本地方法栈里面存放的native方法(也就是C++方法)
堆
堆应该是Java程序管理的内存中最大的一块了。它是所有线程共享的 里面存放的就是对象实例。
堆也是GC管理的最主要的内存区域了,由于现在垃圾收集器基本都采用的是分代收集算法,所以Java的堆还可以细分成为新生代和老年代,再细致一点就是Eden空间 from survivor空间 to survivor空间。这块的具体划分会放在垃圾回收的章节里面。
方法区
方法区和堆一样也是线程共享的,它里面主要存放的是常量,静态变量,对象的class文件
运行时常量池
运行池常量池是方法区的一部分,class文件除了有类的版本,字段,方法,接口等信息,还有一项就是常量池,用于存放编译期生成的各种字面值和符号引用,这部分内容将在类加载之后进去方法区的运行常量池里面存放。