JAVA虚拟机规范
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
2.直接指针访问,如下:
两种方式各有优势,句柄访问最大好处是 reference 存储的是稳定的句柄地址,对象在 垃圾收集被移动时只会改变句柄中的实例数据指针,而 reference 不会被修改,而直接指针 访问速度快,节省了一次指针定位的时间开销。而 Sun HotSpot 采用第二种方式进行对象访 问。
在虚拟机规范中,这个区域规定了两种异常:如果线程请求的栈深度大于虚拟机允许的 深度,将抛出 StackOverflowError 异常;如果栈可以动态扩展,当扩展无法申请到足够的内 存时抛出 OutOfMemoryError 异常。
本地方法栈
Native Method Stack 与虚拟机栈作用类似,不过是虚拟机栈为虚拟机执行的 Java 方法 (即字节码)服务,而本地方法栈为虚拟机使用的 Native 方法服务。与虚拟机栈一样,本 地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
由于 Java 虚拟机的多线程时通过线程轮流切换并分配处理器执行时间的方式来实现的, 在任何时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。 因为,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器, 各线程之间的计数器不影响,独立存储,因此这类区域为“线程私有”的内存。
行的内存模型:每个方法被执行时会创建一个栈帧 Stack Frame 用于存储局部变量表、操作 栈、动态链接、方法出口等信息。每个方法被调用到执行完成,对应一个栈帧在栈中从入栈 到出栈的过程。
局部变量表存放了编译期可知的 8 种基本数据类型、对象引用和 returnAddress 类型(指 向一条字节码指令的地址)。其中 64 位长度的 long 和 double 类型的数据占用 2 个局部变量 空间(Slot),其余数据类型占用 1 个。局部变量表所需的内存空间在编译期间完成分配, 在方法运行期间不会改变局部变量表的大小。
方法区
Method Area 是线程共享的,用于存储被虚拟机加载的类信息、常量、静态变量、即时 编译Baidu Nhomakorabea编译后的代码等数据。
Java 虚拟机规范对方法区限制比较宽松,除了和堆一样不需要连续的内存和可用选择固 定大小或者可扩展外,还可以选择不实现垃圾收集。一般来说,垃圾收集行为在这个区域比 较少见,但并非数据进入了方法区就永久存在了。这个区域的内存回收目标主要针对常量池 的回收和类型的卸载。该区域无法分配内存时,抛出 OutOfMemoryError 异常。
运行时常量池 Runtime Constant Pool 是方法区一部分,Class 文件除了有类的版本、字 段、方法和接口等描述信息外,还有常量池 Constant Pool Table,用于存放编译期生成的字 面量和符号引用。
Java 语言不要求常量一定只能在编译期生成,也就是并非预置入 Class 文件中常量池的 内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,比如 String 类的 intern()方法。
Java 堆
Java 虚拟机规范描述是:所有对象实例以及数组都在堆上分配。堆是所有线程共享的, 但从内存分配角度看,线程共享的堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储 的都仍然是对象实例。
内存间交互操作
一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存的实现细节, Java 内存模型定义八种操作:
1.lock:作用于主内存的变量,把一个变量标识为一条线程独占的状态。 2.unlock:作用于主内存的变量,把一个处于锁定状态的变量释放出来,使得变量可以 被其他线程锁定; 3.read:作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,便 于随后的 load 动作使用 4.load:作用于工作内存的变量,把 read 操作从主内存中得到的变量值放入工作内存的 变量副本中。 5.use:作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚 拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。 6.assign:作用于工作内存的变量,把一个从执行引擎接收到的值赋值给工作内存的变 量,每当虚拟机遇到一个给变量赋值的字节码指令时执行此操作。 7.store:作用于工作内存的变量,把工作内存中变量的值传送到主内存中,便于后面的 write 操作使用。 8.write:作用于主内存变量,把 store 操作从工作内存中得到的变量值放入主内存的变
运行时数据区域
Java 虚拟机所管理的内存将会包括以下几个运行时数据区域:
程序计数器
Program Counter Register 是一块较小的内存空间,作用可以看做是当前线程所执行的字 节码的行号指示器。在虚拟机规范里,字节码解释器工作时就是通过改变计数器的值来选取 下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等基础功能都依赖 程序计数器完成。
Reference 引用类型在 Java 虚拟机规范里只规定了一个指向对象的引用,并没有定义这 个引用如果去定位和访问堆中对象的具体位置,目前有两种实现方式:
1.使用句柄访问方式,在堆中划分出一块内存作为句柄池,reference 存储就是对象的句 柄地址,而句柄包含了对象实例数据和类型数据各自的具体地址信息,如下:
Java 内存模型
主内存和工作内存
线程、主内存和工作内存三者关系如下:
这里的主内存、工作内存与堆、栈、方法区划分的内存不一样的,勉强对应起来,主内 存对应于 Java 堆中对象的实例数据部分,而工作内存对应栈中部分区域。更低层次来说, 主内存就是硬件的内存,而为了获取更好的运行速度,虚拟机及硬件系统可能会让工作内存 优先存储于寄存器和告诉缓存中。
对象访问
Object obj = new Object()
上面语句,obj 会被存放入栈的本地变量表中,而 new Object 会反映到堆中,形成一 块存储了 Object 类型所有实例数据值的结构化内存。另外,Java 堆中必须包含能查找此对 象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些存储在方法区。
量中。
线程状态转换
Java 定义了 5 种线程状态: 1.New:创建线程后尚未启动的线程处于此状态; 2.Runable:包括操作系统线程状态中的 Running 和 Ready,即此状态线程可能正在执行, 也可能等待 CPU 为它分配执行时间。 3.Waiting(无限期等待):这种状态线程不会被分配 CPU 执行时间,需要等待被其他线 程显式唤醒,以下方法会让线程陷入无限期的等待状态:
i. 没有设置 Timeout 的 Object.wait()方法 ii. 没有设置 Timeout 的 Thread.join()方法 iii. LockSupport.park()方法。 4.Timed Waiting(限期等待):这种状态的线程也不会被分配 CPU 执行时间,但是也不 需要被其他线程显式唤醒,在一定时间后它们会由系统自动唤醒,以下方法会使线程进入此 状态: i. 设置 Timeout 的 Object.wait()方法 ii. 设置 Timeout 的 Thread.join()方法 iii. LockSupport.parkNanos()方法 iv. LockSupport.parkUntil()方法 5.Blocked:进程被阻塞与等待区别是:阻塞状态在等待获取一个排它锁,这个事件将 在另外一个线程放弃这个锁的时候发生,而等待状态则是等待一段时间,或者唤醒动作的发 生。在程序等待进入同步区域的时候,线程进入这种状态。 6.Terminated:已终止线程的状态,线程已经结束执行。 上面 5 种状态转换关系如下:
如果线程正在执行一个 Java 方法,计数器记录的是正在执行的虚拟机字节码指令的地 址:如果执行的是 Native 方法,计数器值为空 Undefined。此区域是虚拟机规范中没有规定 任何 OutOfMemoryError 情况的区域。
Java 虚拟机栈
Java 虚拟机栈也是线程私有的,生命周期与线程相同。虚拟机栈描述的是 Java 方法执
两种方式各有优势,句柄访问最大好处是 reference 存储的是稳定的句柄地址,对象在 垃圾收集被移动时只会改变句柄中的实例数据指针,而 reference 不会被修改,而直接指针 访问速度快,节省了一次指针定位的时间开销。而 Sun HotSpot 采用第二种方式进行对象访 问。
在虚拟机规范中,这个区域规定了两种异常:如果线程请求的栈深度大于虚拟机允许的 深度,将抛出 StackOverflowError 异常;如果栈可以动态扩展,当扩展无法申请到足够的内 存时抛出 OutOfMemoryError 异常。
本地方法栈
Native Method Stack 与虚拟机栈作用类似,不过是虚拟机栈为虚拟机执行的 Java 方法 (即字节码)服务,而本地方法栈为虚拟机使用的 Native 方法服务。与虚拟机栈一样,本 地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError 异常。
由于 Java 虚拟机的多线程时通过线程轮流切换并分配处理器执行时间的方式来实现的, 在任何时刻,一个处理器(对于多核处理器来说是一个内核)只会执行一条线程中的指令。 因为,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器, 各线程之间的计数器不影响,独立存储,因此这类区域为“线程私有”的内存。
行的内存模型:每个方法被执行时会创建一个栈帧 Stack Frame 用于存储局部变量表、操作 栈、动态链接、方法出口等信息。每个方法被调用到执行完成,对应一个栈帧在栈中从入栈 到出栈的过程。
局部变量表存放了编译期可知的 8 种基本数据类型、对象引用和 returnAddress 类型(指 向一条字节码指令的地址)。其中 64 位长度的 long 和 double 类型的数据占用 2 个局部变量 空间(Slot),其余数据类型占用 1 个。局部变量表所需的内存空间在编译期间完成分配, 在方法运行期间不会改变局部变量表的大小。
方法区
Method Area 是线程共享的,用于存储被虚拟机加载的类信息、常量、静态变量、即时 编译Baidu Nhomakorabea编译后的代码等数据。
Java 虚拟机规范对方法区限制比较宽松,除了和堆一样不需要连续的内存和可用选择固 定大小或者可扩展外,还可以选择不实现垃圾收集。一般来说,垃圾收集行为在这个区域比 较少见,但并非数据进入了方法区就永久存在了。这个区域的内存回收目标主要针对常量池 的回收和类型的卸载。该区域无法分配内存时,抛出 OutOfMemoryError 异常。
运行时常量池 Runtime Constant Pool 是方法区一部分,Class 文件除了有类的版本、字 段、方法和接口等描述信息外,还有常量池 Constant Pool Table,用于存放编译期生成的字 面量和符号引用。
Java 语言不要求常量一定只能在编译期生成,也就是并非预置入 Class 文件中常量池的 内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中,比如 String 类的 intern()方法。
Java 堆
Java 虚拟机规范描述是:所有对象实例以及数组都在堆上分配。堆是所有线程共享的, 但从内存分配角度看,线程共享的堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer TLAB)。不过,无论如何划分,都与存放内容无关,无论哪个区域,存储 的都仍然是对象实例。
内存间交互操作
一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存的实现细节, Java 内存模型定义八种操作:
1.lock:作用于主内存的变量,把一个变量标识为一条线程独占的状态。 2.unlock:作用于主内存的变量,把一个处于锁定状态的变量释放出来,使得变量可以 被其他线程锁定; 3.read:作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,便 于随后的 load 动作使用 4.load:作用于工作内存的变量,把 read 操作从主内存中得到的变量值放入工作内存的 变量副本中。 5.use:作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚 拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。 6.assign:作用于工作内存的变量,把一个从执行引擎接收到的值赋值给工作内存的变 量,每当虚拟机遇到一个给变量赋值的字节码指令时执行此操作。 7.store:作用于工作内存的变量,把工作内存中变量的值传送到主内存中,便于后面的 write 操作使用。 8.write:作用于主内存变量,把 store 操作从工作内存中得到的变量值放入主内存的变
运行时数据区域
Java 虚拟机所管理的内存将会包括以下几个运行时数据区域:
程序计数器
Program Counter Register 是一块较小的内存空间,作用可以看做是当前线程所执行的字 节码的行号指示器。在虚拟机规范里,字节码解释器工作时就是通过改变计数器的值来选取 下一条需要执行的字节码指令,分支、循环、跳转、异常处理和线程恢复等基础功能都依赖 程序计数器完成。
Reference 引用类型在 Java 虚拟机规范里只规定了一个指向对象的引用,并没有定义这 个引用如果去定位和访问堆中对象的具体位置,目前有两种实现方式:
1.使用句柄访问方式,在堆中划分出一块内存作为句柄池,reference 存储就是对象的句 柄地址,而句柄包含了对象实例数据和类型数据各自的具体地址信息,如下:
Java 内存模型
主内存和工作内存
线程、主内存和工作内存三者关系如下:
这里的主内存、工作内存与堆、栈、方法区划分的内存不一样的,勉强对应起来,主内 存对应于 Java 堆中对象的实例数据部分,而工作内存对应栈中部分区域。更低层次来说, 主内存就是硬件的内存,而为了获取更好的运行速度,虚拟机及硬件系统可能会让工作内存 优先存储于寄存器和告诉缓存中。
对象访问
Object obj = new Object()
上面语句,obj 会被存放入栈的本地变量表中,而 new Object 会反映到堆中,形成一 块存储了 Object 类型所有实例数据值的结构化内存。另外,Java 堆中必须包含能查找此对 象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些存储在方法区。
量中。
线程状态转换
Java 定义了 5 种线程状态: 1.New:创建线程后尚未启动的线程处于此状态; 2.Runable:包括操作系统线程状态中的 Running 和 Ready,即此状态线程可能正在执行, 也可能等待 CPU 为它分配执行时间。 3.Waiting(无限期等待):这种状态线程不会被分配 CPU 执行时间,需要等待被其他线 程显式唤醒,以下方法会让线程陷入无限期的等待状态:
i. 没有设置 Timeout 的 Object.wait()方法 ii. 没有设置 Timeout 的 Thread.join()方法 iii. LockSupport.park()方法。 4.Timed Waiting(限期等待):这种状态的线程也不会被分配 CPU 执行时间,但是也不 需要被其他线程显式唤醒,在一定时间后它们会由系统自动唤醒,以下方法会使线程进入此 状态: i. 设置 Timeout 的 Object.wait()方法 ii. 设置 Timeout 的 Thread.join()方法 iii. LockSupport.parkNanos()方法 iv. LockSupport.parkUntil()方法 5.Blocked:进程被阻塞与等待区别是:阻塞状态在等待获取一个排它锁,这个事件将 在另外一个线程放弃这个锁的时候发生,而等待状态则是等待一段时间,或者唤醒动作的发 生。在程序等待进入同步区域的时候,线程进入这种状态。 6.Terminated:已终止线程的状态,线程已经结束执行。 上面 5 种状态转换关系如下:
如果线程正在执行一个 Java 方法,计数器记录的是正在执行的虚拟机字节码指令的地 址:如果执行的是 Native 方法,计数器值为空 Undefined。此区域是虚拟机规范中没有规定 任何 OutOfMemoryError 情况的区域。
Java 虚拟机栈
Java 虚拟机栈也是线程私有的,生命周期与线程相同。虚拟机栈描述的是 Java 方法执