当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。每条线程都需要有一个独立的PCR,各条线程之间PCR互不影响,独立存储。此内存区域是唯一一个在Java虚拟机规范里没有规定任何OOM情况的区域。
如果线程执行的是一个Java方法:PCR记录的是正在执行的虚拟机字节码指令的地址。
如果线程执行的是一个Native方法:PCR则为空(Undefined)。
此部分也是线程私有的,它的生命周期与线程相同。用于存储局部变量表、操作数栈、动态链接、方法出口等信息。Java虚拟机规范对这个区域定义了两种异常状况:
StackOverflowError:线程请求的栈深度大于虚拟机所允许的深度。
OutOfMemoryError:如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存。
与JVMS功能相同,不过是为虚拟机的Native方法服务,而JVMS是为虚拟机的Java方法服务。
被所有线程共享的一块内存区域,用来存放对象实例。Java堆中可以细分为:新生代、老年代。
当堆里没有内存,且无法再扩展时,抛出OOM异常。
用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。这个区域的内存回收目标主要是对常量池的回收和对类型的卸载。
此区域还包括运行时常量池(Runtime Constant Pool)。
并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
NIO类里的I/O方式,使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。避免了在Java堆和Native堆中来回复制数据。
Java堆溢出:对象数量到达最大堆的容量限制后就会产生内存溢出异常。
虚拟机栈和本地方法栈溢出:见上 ↑。
方法区和运行时常量池溢出:内存不够了呗。
本机直接内存溢出:内存不够了呗。
判断方法:
引用计数算法:给对象中添加一个引用计数器,它很难解决对象之间循环引用的问题。可达性分析算法:一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。在这张图里,加载、验证、准备、初始化、卸载这5个阶段的顺序是确定的。解析阶段则不一定:它在某些情况下可以在初始化之后再开始。
有且只有5种情况必须立即对类进行“初始化”。
使用new关键字实例化对象、读取或设置一个类的静态字段(被final修饰、已在编译器把结果放入常量池的静态字段除外)、调用一个类的静态方法。使用java.lang.reflect包里的方法对类进行反射调用的时候,若类未初始化,则初始化。初始化一个类时,若其父类未初始化,则先出发其父类的初始化。虚拟机启动时,用户需指定一个主类进行初始化。使用JDK7的动态语言支持时,如果……为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
正式为类变量分配内存并设置类变量初始值,这些变量所使用的内存都将在方法区中进行分配,但要注意:
这里指的变量仅包括类变量(被static修饰的变量),而不包括实例变量。这里指的初始值“通常情况”下是数据类型的零值。虚拟机将常量池内的符号引用替换为直接引用的过程。
根据程序员通过程序制定的主观计划去初始化类变量和其他资源。
主内存存储了所有的变量。
工作内存保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
住内存主要对应Java堆中的对象实例数据部分,而工作内存则对英语虚拟机栈中的部分区域。
Java内存模型中定义了以下8种操作来完成:
lockunlockreadloadusestoreassignwrite当一个变量定义为volatile之后,它将具备两种特性:
保证此变量对所有线程的可见性。可见性是指一旦工作内存中此变量的值发生了改变,将会立即同步到主内存,当主内存里的此变量被使用之前,都会刷新主内存。禁止指令重排序优化。此语义可以被用来避免某行代码被提前执行。Java语言中,如果一个变量要被多线程访问,可以使用volatile关键字声明它为“易变的”;如果一个变量要被某个线程独享,可以通过java.lang.ThreadLocal类来实现线程本地存储的功能。