java虚拟机之内存模型篇

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010339039/article/details/88411375

最近在准备跳槽中,整理了下自己的一些知识点,把文档也copy在博客上,不过图片很多是网络上下载的。
先写java虚拟机篇
java虚拟机之内存模型篇
java虚拟机之垃圾回收篇
java虚拟机之垃圾收集器篇
java虚拟机之虚拟机类加载机制
java虚拟机之Java内存模型与线程
内存模型

1.Java 内存区域与内存溢出异常

程序计数器

是一块较小的内存空间,用来指定当前线程执行字节码的行数,每个线程计数器都是私有的,因为每个线程都需要记录执行的行数;这里解释一下为什么每个线程都需要一个线程计数器,JVM的多线程是通过线程轮流切换分配执行时间来实现的,在任何时刻,每个处理器都只会执行一个线程中的指令,当线程进行切换的时,为了线程能恢复当正确的位置,所以每个线程必须有个独立的线程计数器,这样才能保证线程之间不互相影响。
  这里注意下,如果线程执行是一个Java方法的时候,计数器记录的是虚拟机字节码指令的地址;当执行的是Native的方法的时候,计数器指令为空;该内存区域是Java虚拟机唯一没有规定任何OutOfMemoryError的区域。
  
Java虚拟栈
这个也是一个线程私有的,生命周期与线程是同步的,每个方法在执行的同时,都会创建一个栈帧,用于存储局部变量表(基本数据类型,对象引用,returnAddress(指向了一条字节码指令的地址)),操作数栈,动态链接,方法出入口等信息,每个方法的调用到执行完成的过程就是一个栈帧入栈到出栈的过程
当线程请求的栈深度大于虚拟机允许的深度就要报stackOverflowError,如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError
** 注意 : 局部变量表是在编译时就已经确定好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变**

本地方法栈

也是线程私有的,与虚拟机栈执行的基本相同,唯一的区别就是虚拟机栈是执行Java方法的,本地方法栈是执行native方法的

Java堆

所有线程共享的区域,在虚拟机启动的时候创建,所有对象的实例以及数组都要在堆上分配。当堆中没有内存完成实例分配,并且堆无法扩展的时候,将会抛出OutOfMemoryError异常。
Java堆可以在物理上不连续的内存空间中,只要逻辑上是连续的即可。

方法区

所有线程共享的区域
存储被虚拟机加载的类信息、常量、静态变量、即时编译的代码数据等
Jdk1.7开始逐步“去永久代”的事情,比如String.intern()就是往永久代放入String数据

运行时常量池

是方法区的一部分,常量池主要用于存放编译生成的各种字面量和符合引用,这部分内容将在类加载后进入方法区的运行时常量池中存放,由于常量池属于方法区的一部分,所以当常量池没有内存空间的时候就抛出OutOfMemoryError异常;
这个地方是也是动态性的,并不只是保存编译期间产生的常量,在运行期间也可以进入,比如String.intern()

扫描二维码关注公众号,回复: 5552696 查看本文章

直接内存

并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中的一部分,但是这部分内存也被频繁的使用,但是也可能导致oom出现。
可以直接通过native函数分配堆外的内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作;所以当内存空间无法动态扩展的时候就会出现OutOfMemoryError异常;

对象的创建

当虚拟机遇到new(克隆,反序列化)等指令的时候,会去常量池里找类的符号引用,并且检查这个类的符号引用是否已经被加载,解析和初始化过。如果没有就先执行类加载过程(相当于做一个模板,不然一堆二进制数据,并不知道它是什么意思)。
然后在java堆上分配类的内存,这里分配内存又有不同的方式,如果只是移动指针,空余出所需要的空间,但是会有线程竞争的问题,所以需要同步这个指针(cas配上失败重试的方式保证更新操作的原子性) , 另一种是按照线程划分在不同的空间中进行,每个线程在堆中预先分配一小块内存,称为本地线程分配缓冲。只有TLAB(本地线程分配缓冲)用完并且分配新的TLAB时才需要同步锁定。

对象的内存布局

对象在内存中的布局可以分为3块区域:对象头((哈希码,gc分代年龄,锁状态标志,线程持有的锁,偏向线程id,偏向时间戳)(类型指针,就是指向类元数据的指针用来确定是哪个对象的实例)),实例数据(存储真正有效信息),对齐补充。

句柄访问(堆中划分出一块内存作为句柄池)
句柄访问
直接指针访问:
直接指针访问
两者都有优劣:
句柄是reference存储的是稳定的句柄地址,对象移动(垃圾回收时候)只会改变句柄中实例数据指针,而reference自己不需要修改
直接指针访问的好处就是快,节省了一次指针定位的开销
我整理的文档下载

猜你喜欢

转载自blog.csdn.net/u010339039/article/details/88411375