JVM内存模型
如下图所示:
线程私有:
程序计数器:
字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完。
1:用于顺序调用程序指令,完成程序的流程控制(如:顺序执行、选择、循环、异常处理。)
2:多线程状态下,用于记录当前线程运行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
虚拟机栈:
java 内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。(实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)
Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。
- StackOverFlowError:若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。
- OutOfMemoryError:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
本地方法栈:
虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
- 本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。
- 方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。
线程共享:
堆:
java管理内存中最大的一块,Java 堆是所有线程共享的一块内存区域,用于存储对象的实例;
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:再细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
在JDK1.8中,移除整个永久代,取而代之的元空间(Metaspace):永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制。
方法区:
各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。主要目的是为了与堆(Heap)区分开来,方便GC(垃圾收集器)。
运行时常量池:
是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
JDK1.7版本及其以后, JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
字面量:
字符串(final类);final修饰常量值;基本数据类型;其他
符号引用:
类和结构的完全限定名(访问限定修饰符);字段名称和描述符;方法名称与描述符。
额外内容
直接内存:
并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。
JDK1.4中新加入的NIO(New Input/Output) 类,引入了一种基于通道(Channel)与缓存区(Buffer)的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
惯例,二次元老婆来了!