目录
2.程序计数器(Program Counter Register)
3.Java虚拟机栈(Java Virtual Machine Stacks)
1.JVM运行时数据区域简图

2.程序计数器(Program Counter Register)
1.简述
程序计数器是线程私有的,也就是说每个线程都有自己的程序计数器。它可以看做是当前线程正在执行的字节码的行号指示器,也就是jvm执行class文件的时候正在执行多少行,这个程序计数器的值就是多少。
2.作用
第一,实现指令跳转。字节码解释器通过改变程序计数器的值来实现分支,循环,跳转,异常处理等基础功能,程序计数器的值指示字节码解释器要执行的下一条字节码指令的地址。
第二,便于线程切换后的恢复。由于一个处理器(或者说是多核处理器的一个内核)每个时间片都只会执行一条线程中的指令,jvm要实现多线程,就必须不断的切换线程,为了实现线程的切换之后还能够恢复到原来的位置,每个线程都必须有一个独立的程序计数器,指示当前线程执行到的位置,便于切换后恢复。
3.注意
如果正在执行的是Native方法(本地方法,也就是用其他语言实现的方法),则这个计数器的值是为空的。并且这个内存区域是唯一一个Java虚拟机规范中没有规定任何OutOfMemoryError的区域。
3.Java虚拟机栈(Java Virtual Machine Stacks)
1.简介
虚拟机栈描述的是Java方法的内存模型,每个方法执行的时候都会创建一个栈帧,存储局部变量表,操作数栈,动态链接,方法出口等信息。这些区域中大多数人最关注的、与对象内存分配关系最密切的就是虚拟机栈中的局部变量表部分。
2.局部变量表简述
局部变量表中存放的是编译期可知的各种基本数据类型、对象引用和returnAddress类型(指向了一条字节码指令的地址)。
局部变量表所需要的内存空间是在编译期完成分配的,运行时,这个方法在栈帧中分配的变量空间是完全确定的,不会被改变。
3.异常
在Java虚拟机规范中,对这个区域规定了两种异常:
如果线程请求的栈的深度大于虚拟机所允许的深度,则会抛出StackOverflowError异常;
如果虚拟机可以动态扩展,但扩展时无法申请到足够的内存,则抛出OutofMemoryError异常。
4.本地方法栈(Native Method Stack)
本地方法栈与虚拟机栈作用类似,不过描述的是Native方法的内存模型,而非Java方法。与虚拟机栈一样,也会抛出StackOverflowError与OutofMemoryError异常。
5.Java堆(Java Heap)
1.简述
Java堆是线程共享的,在虚拟机启动时创建,它的唯一目的就是存放对象的实例,几乎所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的主要区域,很多时候也称为“GC堆”。
从内存回收角度看,由于垃圾收集器目前采用的基本都是分代收集算法,Java堆便可以根据这个算法划分,详细的算法请读者自行查阅。
从内存分配角度看,Java堆是线程共享的,为了更好地回收内存或者说更快地回收内存,Java堆可能会划分出多个线程私有的分配缓冲区。
2.异常与注意
Java堆的内存空间在物理上可以是不连续的,在逻辑上是连续的即可。在实现时,既可以为固定大小,也可以是可扩展的,如果Java堆中已经没有内存再可以分配,并且堆无法扩展时,则会抛出OutOfMemoryError异常。
6.方法区(Method Area)
1. 简述
方法区是线程共享的,存储的是已经被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据。
运行时常量池(Runtime Constant Pool)是方法区的一部分,存放的是编译期生成的各种字面量和符号引用,类加载之后便被存放。
运行时常量池具备动态性,运行期间也可能将新的常量放入池中,一个例子就是String类的intern()方法。
2.异常
当方法区无法满足内存分配需求时,抛出OutOfMemoryError异常。
7.直接内存(Direct Memory)
1.简述
直接内存并不是虚拟机运行时数据区的一部分,但是经常被使用,而且也会出现OutOfMemoryError异常。
典型实例是NIO类通过Native函数库直接分配java堆以外的内存,然后存储在Java堆中的DirectByteBuffer对象作为这个内存的引用进行操作。由于避免了在Java堆和Native堆中来回复制数据,所以能够在某些场景下提高性能。
2.异常
这部分的异常主要是由于在配置虚拟机参数时忽略了直接内存区域,导致各个内存区域的总和大于了物理内存的限制,从而导致Java堆动态扩展时出现OutOfMemoryError异常。
PS:本文所有要点均为笔者阅读《深入理解Java虚拟机》一书后的简单提炼和总结,若有纰漏,望指正。