深入理解JVM: chp2 Java内存模型

参考: 

https://www.cnblogs.com/JesseP/p/11750847.html#autoid-0-7-0 

https://www.cnblogs.com/JesseP/p/11750847.html#autoid-0-12-0 

《深入理解Java虚拟机》周志明-第三版 

 

2.2  运行时数据区域 

 

深入理解JVM: chp2 Java内存模型_第1张图片

 

 

运行时数据区: 

Java虚拟机在执行java程序的过程中将所管理的内存划分为若干个不同的数据区域。 

 

2.2.1 程序计数器 

 

可以看作是当前线程所执行的字节码的行号指数器。 

 

每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。 

 

 

若线程执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址。 

若线程执行的是一个本地(Native)方法,这个计数器值为空 

 

程序计数器,这个内存区域是唯一一个不会出现OOM的区域。 

 

2.2.2 Java虚拟机栈 

 

是线程私有的,它的生命周期与线程相同。 

 

虚拟机栈描述的是Java方法执行的线程内存模型: 

每个方法执行的时候,Java虚拟机会同步创建一个栈帧(Stack Frame),用于存储局部变量表,操作数栈,动态连接,方法出口等信息。 每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。 

 

虚拟栈中局部变量表

局部变量表存放了编译器可知的各种Java虚拟机基本数据类型,对象引用和returnAddress类型。 

 

这些数据类型在局部变量表中的存储空间以局部变量(Slot)来表示。 

 

局部变量表所需的内存空间在编译期间完成分配。 

 

当进入一个方法时,这个 方法需要在栈帧中分配确定大小局部变量空间,在方法运行期间不会改变局部变量表的大小(指的是槽的数量)。 

 

规定的两类异常情况: 

1. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常 

2. 如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OOM异常 

 

动态扩展: 

HotSpot虚拟机的栈容量是不可以动态扩展的,只要线程申请栈空间成功了就不会由OOM,但是如果申请时就失败,仍会出现OOM。 

 

2.2.3 本地方法栈 

 

与虚拟机栈的区别:虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地方法服务。 

 

OOM与StackOverflow Error: 与虚拟机栈一样,若遇到栈深度溢出或栈扩展失败,则会报以上错误或异常。 

 

 

 

2.2.4 Java 

Java堆被所有线程共享,在虚拟机启动时创建。 

 

Java堆的唯一目的:存放对象实例。 

  

Java堆是垃圾收集器管理的内存区域。 

 

Java堆的划分 

1. 经典分代:新生代(分为一个Eden 两个Survivor),老年代。 

 

分配缓冲区(TLAB): 

从分配内存的角度看,所有线程共享的Java堆中可以划分出多个线程私有的分配缓冲区。 

 

涉及Java堆的参数: 

-Xmx   -Xms 

 

OOM:若java堆中没有内存完成实例分配,且堆也无法再扩展时,会抛出OOM 

 

2.2.5 方法区 

方法区是被所有线程共享的内存区域。 

 

方法区用于:存储已被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存等。 

 

Jdk8以前,方法区称为‘永久代’;jdk8之后永久代概念被废弃,改用在本地内存中实现的元空间来代替。 

 

方法区的内存回收目标: 

针对常量池的回收和对类型的卸载。 

 

OOM:若方法区无法满足新的内存需求时,将抛出OOM异常。 

 

2.2.6 运行时常量池 

 

运行时常量池:是方法区的一部分。 

 

Class文件中的一项信息:常量池表(Constant Pool Table)用于存放在编译器生成的各种字面量与符号引用,这部分内容在类加载后存放到方法区的运行时常量池中。 

 

运行时常量池除了保存Class文件中描述的符号引用外,还会把符号引用翻译出来的直接引用也存储在运行时常量池中。 

 

运行时常量池具备动态性: 

并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中。例如 String.intern()方法。 

 

OOM:当常量池无法再申请到内存时会抛出OOM异常。 

 

2.2.7 直接内存 

本机直接内存的分配。 

 

 

2.3 HotSpot 

 

2.3.1 Java对象创建过程 

 

当jvm遇到一条字节码new指令时: 

 

       首先将去检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,则须先执行相应的类的加载过程 

 

       在类加载检查通过后,接下来虚拟机将为新生对象在Java堆中分配内存。 

 

分配方式: 

1. 指针碰撞:假设Java堆中内存是绝对规整的,即所有被使用过的内存都放在一边,空闲的内存放在一边,中间放着一个指针作为分界点的指示器,分配内存就是把那个指针像向空闲空间方向挪动一段与对象大小相等的距离。 

 

2. 空闲列表:假设Java堆中内存不是规整的,即已被使用的内存和空闲的内存交错在一起。虚拟机就维护一个列表,记录哪些内存块是可用的,在需要为对象划分内存时,就更新列表。 

 

 

规整:取决于所采用的垃圾收集器是否带有空间压缩整理(Compact)的能力决定。如有则系统采用的分配算法时指针碰撞,反之则采用空闲列表。 

 

 

分配内存涉及并发的线程安全问题的解决方案: 

1. 对分配内存空间的动作进行同步处理:采用CAS配上失败重试的方式保证更新操作的原子性。 

 

2. 内存分配的动作按照线程划分在不同的空间之中进行:即每个线程在Java堆中预先分配一块内存称为TLAB(本地线程分配缓冲),哪个线程要分配内存,就在哪个线程的本地缓冲区中分配。 

 

       内存分配完成后,虚拟机必须将分配到的内存空间(但不包括对象头)都初始化为零值,如果使用了TLAB的话,这一项工作也可以提前至TLAB分配时顺便进行。 

        

      接下类,对对象头进行必要的设置: 

 

      New指令后会接着执行()方法,按照程序员的意愿对对象进行初始化。这样一个真正可用的对象才算完全构造出来。 

 

 

你可能感兴趣的:(Java,Java内存模型,Java堆,运行时常量池,java,jvm)