JVM内存区域划分

223 阅读3分钟

JVM的规范基本没有很多强制要求,所以,大部分书中介绍的都是简化的理想化的模型。大概可以分为两类区域:

线程共享的区域

  • 堆 Heap
    1. 主要分配给JAVA对象实例和数组。
    2. 堆内存 在操作系统的视角(物理上)未必是连续的,但在JVM中应该视为逻辑上连续的。
    3. 由于内存区域经过不断分配和GC,如果是有碎片整理的GC(Serial、ParNew),可以简单用指针分割未用空间。否则(CMS)需要维护一个可用内存地址的表
    4. 通过参数-Xmx和-Xms可以设置堆内存大小
    5. 如果堆无法给新对象分配内存了,会抛出OutOfMemoryError异常
    6. 通过-XX:+/-UseTLAB参数设定本地线程缓冲区域
    7. 句柄和指针直接引用的区别在于句柄多了一层引用,
    8. 句柄设计好处在于,堆中对象移动时,需要改变的只有句柄的指向,指向句柄的众多变量不需要改变。可以类比面向接口的编程,接口实现可能会迭代变化,但是接口不变,接口调用者也不需要改变。
    9. 然而通过指针直接访问时,每次访问都少一步引用,所以JVM多采用直接引用。
  • 方法区 Method Area
    1. 存储了已经加载过的类型信息,常量,静态变量和即时编译缓存
    2. 不等同于永久代,Hotspot在JDK7就已经把 字符串常量池 和 静态变量移出永久代。
    3. 对于class文件,编译期会生成字面量和符号引用,加载后会放入 运行时常量池
    4. 通过 String.intern()方法也会把字符串常量放入运行时常量池。
  • 直接内存
    1. 直接说明它是绕过JVM通过操作系统直接申请的一块内存区域。
    2. 通过堆中的DirectByteBuffer对象引用/指向直接内存。
    3. 避免了内存在虚拟机内外来回拷贝。

线程独有的区域

  • 虚拟机栈
    1. 某一时刻的JAVA程序是一定执行在一个线程中的
    2. 把代码分解为一个个方法嵌套调用,方法执行就是在对应线程的虚拟机栈中入栈出栈。
    3. 每个方法对应一个栈帧,存有局部变量(如果是对象,则存的是对象指针)
    4. 栈无法扩展,且方法进一步执行申请的栈深度超过最大深度,抛出StackOverflowError
  • 程序计数器 虚拟机栈是线程独立的,所以程序计数器显然也需要是线程独立的,记录当前执行到虚拟机栈的哪个位置了。
  • 本地方法栈 为native方法服务,没有强制规定,HotSpot就把本地方法栈和虚拟机栈合二为一。

光看线程区域的划分,我们就能很简单地想到,平时注意线程安全时,主要是保障线程共享的内存区域,线程隔离的内存区域本来就是安全的呀。