主要有以下几种:
程序计数器:当前线程所执行的字节码的行号指示器。线程私有
Java虚拟机栈:线程私有,生命周期与线程相同。是描述Java方法执行的内存模型
本地方法栈:为虚拟机使用的本地Native方法服务
Java堆:是虚拟机管理的最大内存,被所有线程共享,存放对象实例。
方法去:各个线程共享,用于存储已被虚拟机加载的类信息,常量,静态变量,及时编译器编译后的代码数据。
运行时常量池:Class文件中的常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
直接内存
2.3.1 对象的创建
1)类加载检查:
虚拟机遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否被加载,解析,初始化过。(类加载检查);
2)为新生对象分配内存
为新生对象分配内存,主要有两种方法:(1)内存绝对规整,指正碰撞(2)内存不规整(已使用内存和空闲内存相互交错)空闲列表,记录哪些内存快可以使用。
Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。指针碰撞:Serial,ParNew 空闲列表:CMS(基于Mark-Sweep)
对象创建在虚拟机中非常频繁,在并发情况下线程不安全,两种解决方法:
(1)对分配内存空间的动作记性同步处理
(2)把内存分配的动作按照线程划分到不同空间中进行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配。
3)将分配到的内存空间初始化为0值,接着对对象进行必要的设置。
在执行new指令之后会接着执行
2.3.2 对象的内存布局
对象在内存中的存储布局可以分为三个区域:对象头(Header),实例数据(Instance Data),对齐填充(Padding)。
对象头中存储两部分信息
1)、类型指针,即对象指向他的类元数据,告诉虚拟机对象属于哪个类
2)、存储对象自身运行时数据。gc分代年龄、哈希吗、锁状态、线程持有锁、偏向线程ID偏向时间戳等。称为“mark word”是非固定的数据结构。
实例数据:存储对象真正的数据,也就是程序中定义的各字段内容。包括父类、子类的字段内容。存储顺序会受定义顺序和分配策略参数影响,HotSpot中默认为longs/doubles,ints,shorts/chars,bytes/bolleans,oops。相同宽度的字段总是被分配在一起,满足前提下父类字段会在子类字段之前,如果CompactFields设置为true时,子类字段有可能穿插在父类中.
对齐填充:仅仅起占位符的作用,保证对象大小必须是8 字节的整数倍
2.3.3 对象的访问定位
建立了对象后,依据java栈中本地变量表中的reference数据来操作堆上的具体对象。reference类型是一个指向对象的引用,但并没有定义这个引用以何种方式去定位。
主流采用两种方式实现reference的定位:
1)句柄。在堆中维护一个句柄池(其中包含对象实例数据指针和对象类型数据指针。),在reference保存着对象的句柄地址。优点在于reference中存储的是稳定的句柄地址,对象改变时只要改变句柄池中的对象位置。
2)直接定位。在reference中存储的是堆中对象的直接位置(对象的实例数据),在对象中存储的有对象类型数据指针。优点在于速度快。节省了一次指针定位开销。(HotSpot采用)