02 内存管理.txt
UP 返回
1. 运行时数据区
运行时数据区分为线程共享区和线程独占区
线程共享区分为:
方法区 存储运行时常量池,已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据
Java堆 存储对象实例,传统意义上的堆内存
线程独占区分为:
虚拟机栈 存储方法运行时所需的数据,称为栈帧。平时栈内存说的就是这个栈中的局部变量表
本地方法栈 为jvm所调用的native即本地方法服务
程序计数器 记录当前程序所执行到的字节码的行号,占用空间非常小
1.1 程序计数器
程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器
如果线程执行的是java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果是native方法,值为undefined
此区域是唯一一个在java虚拟机规范中没有规定任何OutOfMemoryError情况的区域,因为开发者是不会接触到这个地方
1.2 虚拟机栈
虚拟机栈描述的是java方法执行的动态内存模型
每个方法执行,都会创建一个栈帧,伴随着方法从创建到执行完成,用于存储局部变量表,操作数栈,动态链接,方法出口等
局部变量表用于存放编译期可知的各种基本数据类型,引用类型,returnAddress类型。局部变量表的内存空间在编译期完成分配,当进入一个方法时,这个方法在帧中需要分配的内存是固定的,运行期间不会改变。很好理解,相当于只存储了对象的引用,所以对象具体多大并不影响
虚拟机栈溢出就会报StackOverFlow异常。如果栈非常大,那么最终会出现内存溢出的异常
1.3 本地方法栈
本地方法栈和虚拟机栈唯一的区别就是后者为java方法服务,前者为native服务。两者结构其实是一样的。所以Hotspot其实是把两个概念合二为一了,不再区分
1.4 java堆
垃圾收集器管理的主要区域,又可细分为新生代,老年代,Eden空间等。过大时也会抛出OutOfMemory
1.5 方法区
存储的类信息包含:类的版本,字段 ,方法,接口
在Hotspot中,为了方便方法区内存管理的代码编写,会把其当做永久代来处理。在实际的jvm规范中两者不是等价的
方法区也会发生垃圾回收,但是很少,因为一般回收的代价高而回收的容量却很小
运行时常量池存放于方法区,存储字符串常量。常量池可以类比hashset结构,所以没有重复的,同样的字符串是同一个地址。对于通过new创建的字符串,使用intern方法可以将该字符串扔进常量池,返回值也就相当于是常量池的地址了
1.6 堆外内存
虚拟机规范中并不存在这个区域。jdk1.4以后添加了nio,会在堆外直接分配内存用于缓冲数据,这块内存是不受java堆内存的限制的,但是会受到操作系统的内存限制,同样也会抛出OutOfMemory
2. 对象的创建
new 类名
↓
根据new的参数在常量池中定位一个类的符号引用
↓
如果没有找到这个符号引用,说明类还没有被加载,则进行类的加载,解析和初始化
↓
虚拟机为对象分配内存(堆中)
↓
将分配的内存初始化(不包括对象头,基本数据类型给默认值,抽象数据类型为null)
↓
调用对象的init构造方法
给对象分配内存的方式有两种:
指针碰撞 即堆中维护着两个区域,一边是可用的,一边是已经分配的。当需要分配一个新的空间时,移动指针缩小空闲区域即可
空闲列表 空闲和已用的空间可能是不连续的,这个时候虚拟机就需要维护一个列表,用来记录所有可用的空间,用来分配内存
具体使用哪一种方式取决于垃圾回收策略。如果垃圾回收器自带压缩整理功能,保证内存空间是规整的,就可以使用指针碰撞,否则只能使用空闲列表
高并发的时候同一时刻可能会有多个对象创建,上述的分配方式就会存在线程安全问题,有两种解决方法:
线程同步加锁 效率低
本地线程分配缓冲 TLAB,给每一个线程在堆中分配一个自己的区域(大小可参数指定),这样对象分配就不会相互影响
3. 对象的结构
Header(对象头)
自身运行时数据(又称为Mark Word):哈希值 GC分代年龄 锁状态标志 线程持有的锁 偏向线程id 偏向时间戳。占用的长度根据虚拟机的位数来,有32位或者64位
Hotspot虚拟机MarkWord组成大小:
!!@@D:\MyJAR_Project\WordsCollection\refer\202202271.png@@!!
类型指针:指向类的源数据的指针,虚拟机根据此确定他是哪一个类的实例。并不是所有的虚拟机都有这个字段,而数组在这里还会额外存储数组的长度
InstanceData
真正存储对象的有效信息,包括从父类继承的字段和自身的字段。Hotspot的分配策略是相同长度的字段放在一起(比如long和double),所以可能会出现父类的字段出现在子类之前
Padding
对齐字段。Hotspot规定对象的起始地址必须是8字长的整数倍,即对象的长度必须是8的整数倍。而对象头的长度已经是8的整数倍,所以当instancedata的长度不足时就需要该区域来填充
4. 对象的访问定位
一般访问一个对象时,会说栈中存储着堆中对象的地址引用,但是这是不确切的,因为他也可能保存的是堆中的某个区域而并非是对应的对象,所以对象定位分为了两种方式:
使用句柄:这种方式中,栈指向的就是堆中的一个区域,叫做句柄池,在句柄池中会存储对象的地址以及类的信息,这样引用需要经过两次指针,相对来说会慢一点。但是好处就是不论堆中的对象如何移动,栈中的指针始终指向句柄池不会受影响
直接指针:这种方式中,栈指向的就是堆中对象的地址,此时对象本身就需要包含类的信息,可以类比于上述说的类型指针。Hotspot采用的就是这种方
DOWN 返回