JVM的规范基本没有很多强制要求,所以,大部分书中介绍的都是简化的理想化的模型。大概可以分为两类区域:
线程共享的区域
- 堆 Heap
- 主要分配给JAVA对象实例和数组。
- 堆内存 在操作系统的视角(物理上)未必是连续的,但在JVM中应该视为逻辑上连续的。
- 由于内存区域经过不断分配和GC,如果是有碎片整理的GC(Serial、ParNew),可以简单用指针分割未用空间。否则(CMS)需要维护一个可用内存地址的表
- 通过参数-Xmx和-Xms可以设置堆内存大小
- 如果堆无法给新对象分配内存了,会抛出OutOfMemoryError异常
- 通过-XX:+/-UseTLAB参数设定本地线程缓冲区域
- 句柄和指针直接引用的区别在于句柄多了一层引用,
- 句柄设计好处在于,堆中对象移动时,需要改变的只有句柄的指向,指向句柄的众多变量不需要改变。可以类比面向接口的编程,接口实现可能会迭代变化,但是接口不变,接口调用者也不需要改变。
- 然而通过指针直接访问时,每次访问都少一步引用,所以JVM多采用直接引用。
- 方法区 Method Area
- 存储了已经加载过的类型信息,常量,静态变量和即时编译缓存
- 不等同于永久代,Hotspot在JDK7就已经把 字符串常量池 和 静态变量移出永久代。
- 对于class文件,编译期会生成字面量和符号引用,加载后会放入 运行时常量池
- 通过 String.intern()方法也会把字符串常量放入运行时常量池。
- 直接内存
- 直接说明它是绕过JVM通过操作系统直接申请的一块内存区域。
- 通过堆中的DirectByteBuffer对象引用/指向直接内存。
- 避免了内存在虚拟机内外来回拷贝。
线程独有的区域
- 虚拟机栈
- 某一时刻的JAVA程序是一定执行在一个线程中的
- 把代码分解为一个个方法嵌套调用,方法执行就是在对应线程的虚拟机栈中入栈出栈。
- 每个方法对应一个栈帧,存有局部变量(如果是对象,则存的是对象指针)
- 栈无法扩展,且方法进一步执行申请的栈深度超过最大深度,抛出StackOverflowError
- 程序计数器 虚拟机栈是线程独立的,所以程序计数器显然也需要是线程独立的,记录当前执行到虚拟机栈的哪个位置了。
- 本地方法栈 为native方法服务,没有强制规定,HotSpot就把本地方法栈和虚拟机栈合二为一。
光看线程区域的划分,我们就能很简单地想到,平时注意线程安全时,主要是保障线程共享的内存区域,线程隔离的内存区域本来就是安全的呀。