深入理解Java虚拟机:jvm内存模型jdk1.8

深入理解Java虚拟机:jvm内存模型jdk1.8

  • 一.程序计数器
      • 使用PC寄存器存储字节码指令地址有什么作用?为什么使PC寄存器记录当前线程的执行地址?
      • PC寄存器为什么会被设定为线程私有?
  • 二.Java虚拟机栈
      • StackOverflowError
      • OutOfMemoryError
  • 三.本地方法栈
      • StackOverflowError
      • OutOfMemoryError
  • 四.Java堆
      • OutOfMemoryError
  • 五.方法区
      • OutOfMemoryError
      • 为什么要废除永久代
  • 六、运行时常量池
      • OutOfMemoryError




深入理解Java虚拟机:jvm内存模型jdk1.8_第1张图片

一.程序计数器

  1. 程序计数器,是一块很小的内存空间,可以忽略不计,主要功能就是存储指令地址,即指令代码。可以看做当前线程所执行的字节码的信号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
  2. 每条线程都有自己独立的程序计数器,保证各个线程之间计数器互不影响,独立存储“线程私有”内存中。
  3. 如果正在执行的是 Native方法,则这个计数器值为空,并且此内存区域是在《Java虚拟机规范》中没有规定任何OutOfMemoryError情况的区域。
public class cn.com.demo.utils.Ex {
  public cn.com.demo.utils.Ex();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String hello world
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return
}

使用PC寄存器存储字节码指令地址有什么作用?为什么使PC寄存器记录当前线程的执行地址?

如同上列字节码 在当前线程保证了当前操作,即使CPU不停的切换线程,知道了每个线程的执行位置,也可以明确下一条字节码指令执行。

PC寄存器为什么会被设定为线程私有?

多线程在特定时间执行只会使用一个线程,PC寄存器可以明确执行的地址,设置为私有的也只是为了,准确记录每个一个线程的执行位置,避免线程间的干扰。


二.Java虚拟机栈

  1. java栈也是线程私有的,声明周期也与线程相同。
  2. 栈中存储了 基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置)和 返回地址类型(指向了一条字节码指令的地址)。
  3. 方法代码存再方法区中,当执行的时候,压入栈中,执行完成后再压出。
  4. 特点正是运行速度快,而所用空间小。

StackOverflowError

当线程请求的栈深度大于虚拟机允许的栈深度,则会抛出StackOverflowError栈溢出错误。

OutOfMemoryError

当Java虚拟机栈容量可以动态扩容,并且扩容时无法申请到更多内存,则会抛出OutOfMemoryError


三.本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。

《Java虚拟机规范》对本地方法栈中方法使用的语言、使用方式与数据结构并没有任何强制规定,因此具体的虚拟机可以根据需要自由实现它,甚至有的Java虚拟机(譬如Hot-Spot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会在栈深度溢出或者栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError异常。

StackOverflowError

本地方法栈也会在栈深度溢出

OutOfMemoryError

栈扩展失败时


四.Java堆

jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,在《深入理解Java虚拟机》一书中的描述是:“Java
世界里“几乎”所有的对象实例都在这里分配内存”。
至于为什么是几乎,是因为不同jdk通过java虚拟机的发展,并不是那么绝对。

OutOfMemoryError

如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。


五.方法区

  1. 方法区与堆区都是线程共享空间。
  2. 存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
  3. 1.8以前存放于堆中永久代,但由于这种设计导致了Java应用更容易遇到内存溢出的问题,1.8之后的版本 使用本地内存来实现方法区的存储,也就是元空间。

OutOfMemoryError

方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。

为什么要废除永久代

1、现实使用中易出问题。

由于永久代内存经常不够用或者发生内存泄露,爆出异常 java.lang.OutOfMemoryError: PermGen 。
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
2、永久代会位GC带来不必要的复杂度,而且回收效率偏低。


六、运行时常量池

运行时常量池(Runtime Constant Pool)是方法区的一部分。
Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。String等的引用在运行时常量池中,具体的值“Java”存放于堆中。

OutOfMemoryError

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。






如有错误欢迎指正

你可能感兴趣的:(JVM,java,经验分享,开发语言)