运行时数据区
JVM在运行java程序的时候,会把它所管理的内存分成若干块
1.程序计数器
说白了就是当前线程运行到哪里的指标 。如果一个正在运行的java程序,那么计数器里面存的就是正在执行的虚拟机字节码指令的地址。
虚拟机字节码指令?????????????
2.虚拟机栈
生命周期与线程相同。它描述的是java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,栈帧中包含:
(1)局部变量数据
用来存放方法参数和方法内部的局部变量?????????
(2) 动态链接
???????
(3)操作数栈
???????
(4)返回值
???????
如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常
3.本地方法栈
与虚拟机栈差不多,虚拟机栈是为java方法服务的,而本地方法栈是为虚拟机调用Nativa方法(非java语言书写的方法)服务的。
Sun HotSpot把本地方法栈与虚拟机栈合在一起了。
4.堆
是所有线程共享的一块内存区域,存放被实例化的对象。
如果在堆中没有内存完成实例分配,并且堆也不能扩展时,将会抛出OutOfMemoryError异常
????堆内存怎么分配的???
5.方法区(永久代)
是所有线程的共同区域,用于存储已经被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
运行时常量池:
是方法区的一部分,存放编译期间生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放
当方法区/常量池无法满足内存分配需求时,将抛出OutOfMemoryError异常
HotSpot虚拟机对象
1.对象的创建(或者说new关键字干了什么)
1.虚拟机在遇到一条new指令时,首先检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符合引用代表的类是否被加载、解析和初始化。如果没有那么先执行相应的类加载过程。
2.虚拟机为新生对象分配内存。
内存分配线程安全问题:
虚拟机在给A分配内存的同时,指针还没修改,对象B又使用了原来的指针分配内存。
解决办法:
(1)对分配内存空间的动作进行同步处理(虚拟机采用CAS即乐观锁失败重试的方法保证操作的原子性)
(2)把内存分配的动作按照线程划分在不同的空间中进行(每个线程在java堆中预先分配一小块内存:本地线程分配缓冲TLAB,用完TLAB并且分配新的TLAB才会进行同步锁定)
3.虚拟机把分配好的内存空间初始化为零值
4.虚拟机根据对象头存放的信息对新对象进行初始化
2.对象的创建
对象在内存中的分布
(1)对象头
一部分存放对象自身运行时的数据:哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳
另一部分存放的是类型指针,即对象指向它类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
(2)实例数据部分
存储的是在程序代码中定义的各种类型的字段内容(同长度的分配在一起)
(3)对齐填充(可无)
仅仅起着占位符作用,HotSpot 虚拟机自动内存管理要求对象的地址是八字节的整数倍、而对象头正好是八字节的倍数,所以当对象实例数据没对齐是就需要对齐填充
3.对象的访问定位
java程序需要通过栈中的reference数据来操作堆上面的具体对象
1.句柄访问
单独划出一块空间:句柄池(存放的是堆中对象示例数据与方法区中对象类型数据的地址信息)
此时reference中存放的就是对象的句柄地址
(个人理解为间接引用)
2.直接指针访问
顾名思义,reference中存放的直接就是对象地址
两种访问各有优势,句柄访问中垃圾回收只需要改变句柄池中的地址,reference本身不需要修改。而直接访问速度更快。
4.StackOverflowError异常和OutOfMemoryError异常
1.如果线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常
2.如果虚拟机在扩展栈时无法申请到足够的内存空间,抛出OutOfMemoryError异常