深入理解JVM虚拟机-运行时数据区域
JAVA 运行时内存数据区域
java虚拟机执行JAVA程序时 ,会将内存按照用途不同进行分区。有的区域随虚拟机生存时间创建和销毁,有的随线程启动而销毁。分区图如下:
程序计数器
程序计数器是当前线程所执行字节码的行号zh指示器,字节码解释器通过计数器来选取下一条需要执行的字节码,实现分支、循环、跳转、异常处理、线程恢复等。
每个线程都拥有自己的程序计数器,这个很容易理解,因为每个线程的执行动作都是独立的。独立线程的计数器,还作用于线程切换时恢复到正确的执行位置。
虚拟机栈
同样是线程私有,每个方法在执行的同时 ,会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等,方法调用到完成,对应着一个栈帧入栈 、出栈。
局部变量存放编译期间可知的数据,所需的空间在编译期间完成分配,运行期间不会改变大小。
如果方法调用的过深,会出现*Error,如果总栈帧占用内存过多,会OutOfMemoryError。
本地方法栈
为虚拟机提供使用到的Native方法服务。
JAVA堆
JAVA Heap,所有线程共享区域,用于存放对象实例(和数组)。
因为逃逸分析、栈上分配、标量替换的技术,对象也有可能不在堆上分配。
堆从内存回收角度可以分为:新生代和老年代。
堆在物理内存中可以不是连续的。
GC主要清理区域。
方法区
用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
hotspot虚拟机上面习惯叫做 永久代。JDK 1.7已经将 字符串常量移出永久代。
比较少GC,主要是对常量池的回收和对类的卸载。
运行时常量池
属于方法去的一部分。class文件除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息,用于存放编译编译器生成的各种字面量和符号引用,这部分内容在类加载后进入方法区的运行时常量池存放。
直接内存
不属于运行时数据区的一部分,不属于虚拟机规范定义的内存区域。
本机直接内存分配不受java堆大小的限制,但受本机总内存和操作系统规定的进程可用内存大小限制。
如NIO类,使用Native直接分配堆外内存。
该区域的内存回收需要特别注意,虽然虚拟机会对直接内存区进行回收,但是该区域不能像新生代、老年代通知收集器进行回收,只有在full gc的时候 “顺便的”清理废弃的对象。