java 和 C++之间有一堵有内存动态分配和垃圾回收技术所围城的高墙,墙外面的人想进去,墙里面的人想出来。
首先要明确一个问题!为什么要了解JVM?
我们把内存分配的工作交给JVM,如果我们 和它配合的不默契,那么出现问题(内存泄露,内存溢出)将是一件很脑壳痛的事情。要解决OutOfMemoryError
和StackOverflowError
我们就需要翻越这堵墙,看清楚墙里面的世界。
翻墙第一步,了解运行时数据区域
首先可以把这个区域按照是否线程共享分为两个部分:蓝色和绿色部分。
程序计数器PC
这和计算机CPU中的程序计数器大致一样,这一块实际上很小的,图画的有点问题。其保存的就是当前代码执行的位置。线程是可以并发的,所以每个线程有自己的PC。需要注意的是
- 当执行的是JAVA方法,PC指向的是虚拟机字节码指令的地址
- 执行的是Native方法,这个计数器值为空。
- 这个区域是不会发生
OutOfMemoryError
的。我的理解是它只是存储一个地址而已。
JAVA虚拟机栈
这个栈用来存储方法被调用的时候产生的局部变量表(基础数据类型和对象引用),操作数栈,动态连接等。这些数据除了long和double类型8Byte占用2个Slot(局部变量空间)外其他数据都是占用一个Slot。
每调用一个方法都会创建一个栈帧,并压栈。执行完成后弹栈。
- 当栈深大于最大深度的时候,抛出
StackOverflowError
。 - 虚拟机大多数可以动态扩展,如果扩展无法申请足够的内存会
OutOfMemoryError
本地方法栈
本地方法栈和JAVA虚拟机栈一样。
本地方法栈存储的是Native方法调用关系,变量等,具体的实现虚拟机规范没有规定。也会抛出StackOverflowError
和OutOfMemoryError
的错误。
JAVA堆 - 线程共享
这是JVM管理内存最大的一块了,它是用来存放所有的对象实例。(注,JIT编译,逃逸分析等技术让所有不再绝对)
这也是垃圾回收主要工作的区域,所有JAVA堆又叫GC堆。GC堆可以进一步划分
按照垃圾回收的角度:新生代和老年代
按照内存分配的角度:多个线程私有分配缓冲区TLAB
划分的目的在于方便内存管理。
方法区 - 线程共享
存储:类信息,常量,静态变量等。有的时候这个区域被成为永久带
,原因是GC在这里很少出现,但是这是不准确的,并非这里的数据就永久存在。
运行时常量池 - 方法区的一部分
存储:类的版本,字段,方法,接口,常量
具备动态性:运行时候可能有新的常量放入池中(暂时不是很理解)
因为运行时常量池是方法区的一部分,所以内存自然受到方法区的限制,当无法申请到新的内存的时候,会抛出OutOfMemoryError
。
直接内存
这个内存并不是JAVA虚拟机规范中定义的区域。我暂且理解为是物理机内存中不属于JVM
的一个内存块。
在JDK1.4
中,NIO
类的IO操作时候,读取或写入的数据Buffer就是在直接内存区域,这样可以提高效率,然后通过堆中的DirectByteBuffer
对象作为这个区域的引用。主机物理内存有限,可能出现OutOfMemoryError
。