- 线程私有的内存区域,记录的是正在执行的虚拟机字节码指令的地址,通过改变程序计数器的值来选取下一条需要执行的字节码指令。
分支,循环、跳转、异常处理、线程恢复等基础功能都需要依赖程序计数器来完成。 - 唯一不会出现 OutOfMemoryError 的内存区域。
-
线程私有,生命周期和线程一致
-
描述的是java方法执行的内存模型,即栈帧;
-
方法在执行时会创建一个栈帧,存储的是局部变量表、 *** 作数栈、动态链接、方法出口等信息。
-
方法从执行到结束对应着一个栈帧从虚拟机栈中入栈到处出栈的过程。
-
局部变量表:存储的是编译器可知的各种基本数据类型、对象引用类型和返回地址类型
可能会出现两种异常:
- 线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常
- 虚拟机栈可以动态扩展,当无法申请到足够的内存时,抛出OutOfMemoryError异常。
与虚拟机栈的作用相似。区别在于虚拟机栈为虚拟机执行Java方法服务,本地方法栈为虚拟机执行本地方法服务。本地方法指的是不是由java代码编写的方法。也就是用native修饰的方法,比如底层是c或c++实现的方法。
有的虚拟机(如 HotSpot 虚拟机)把本地方法栈和虚拟机栈合二为一。
和虚拟机栈一样,本地方法栈也会出现 StackOverflowError 和 OutOfMemoryError 两种异常。
堆- java堆是JVM所管理的内存中的最大的一块内存,存放对象实例。
- 被线程共享,但是也会划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB)
- 垃圾收集器管理的主要区域
- OutOfMemoryError:如果堆中没有内存完成实例分配,并且堆也无法再扩展时,抛出该异常。
- 存放的是已加载的类信息、常量、静态变量、编译后的热点代码
- 被线程共享
- 会被垃圾收集器回收,但条件较为苛刻
- 如果方法区无法满足新的内容分配需求时,将抛出OutOfMemoryError 异常
- 当java虚拟机遇到一条字节码new指令时,检查指令的参数是否能在方法区的常量池中定位到一个类的符号应用,再检查类是否已被加载、解析和初始化过。如果没有,执行类加载过程。
- 执行完类加载过程,对象所需的内存大小便已确定,在java堆中划分一块空闲的内存块分配给它。
- 将分配的内存空间(不包括对象头)初始化为零值
- 填充对象头,设置对象的相关信息。例如:对象属于哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。
- 虚拟机的视角对象已经产生,从程序代码的视角看,还需要通过调用字节码文件的()方法执行构造函数。
注:分配内存的方式是采用改变指针的所指向的位置,存在两个线程都在修改指针指向的情况。并发情况下非线程安全。
解决方式:
- 采用CAS(Compare and Swap)乐观锁配上失败重试来解决
- 将分配内存的动作按照线程划分在不同的空间之中,给每个线程预先分配内存,成为本地线程内存分配缓冲(TLAB)
堆内存中对象的存储布局可划分为三个部分:
1、对象头 2、实例数据 3、对齐填充
- 对象头包含两部分:
- 存储对象自身的运行时数据:如哈希码,GC分代年龄、线程持有的锁、锁状态标志、偏向线程ID、偏向时间戳等
- 类型指针,指向对象类型元数据的指针。虚拟机通过这个指针来确定该对象是哪个类的实例。
- 实例数据:程序代码中各种类型的字段内容(包括父类继承下来的和子类中定义的)
- 对齐填充:非必要的占位符,在HosSpot虚拟机中,保证对象的大小必须是8字节的整数倍。
程序中通过栈上的reference数据来 *** 作堆上的具体对象。具体实现是由虚拟机完成的,主流的访问方式主要有句柄和直接指针两种:
- 句柄访问
使用句柄访问,java堆中划分一块内存作为句柄池,reference中存储的就是对象的句柄地址。句柄中包含了对象实例数据的指针和到对象数据类型数据的指针。
优点:reference中存储的就是对象的句柄地址,在对象被移动(垃圾收集)时,只需要改变句柄中的实例数据指针,而 reference本身不需要修改
- 直接指针访问
Java堆中对象的内存布局必须考虑如何放置访问访问类型数据的相关信息,reference中存储的直接就是对象地址
优点:由于直接存储对象地址,直接访问对象。节省了实例数据指针定位的时间开销,速度更快。对象访问在java中非常频繁,在HotSpot虚拟机中,主要使用的就是直接指针进行对象访问
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)