目录
程序计数器
记录每一个线程执行的位置,需要的操作
java虚拟机栈
每个方法执行的时候,java虚拟机栈都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每一个方法被调用直至结束的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
本地方法栈
java堆
java堆是垃圾收集器管理的内存区域。从内存回收的角度看,由于现代垃圾大部分都是基于分代收集理论设计的。所以java堆中经常会出现“新生代”,“老年代”,“永久代”,“eden空间”,“From Survivor空间”,“To Survivor空间”
方法区
方法区(运行时常量池)
直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是《java虚拟机规范》中定义的内存区域,但是这部分内存也被频繁的使用,也有可能导致outofMemoryError异常。
一.head中对象分配的规则,创建的过程,对象的内存布局
1.head中对象分配的规则,和创建的过程:
当java virtual machine 遇到一条字节码new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载,解析和初始化过,如果没有,那必须先执行相应的类加载过程。接下来虚拟机会为新生对象分配内存。对象所需的内存在类加载完成后便可以确定。为新生对象分配内存实际上就是把一块确定大小的内存块从java 堆中划分出来。假设堆中的内存块是完全规整的。所有使用过的内存放在一边,未使用的放到另一边。中间放着一个指针作为内存分界点的指示器,那么分配内存仅仅是将指针向空闲的方向挪动一段与对象大小相等的距离,这种分配方法叫做指针碰撞。
但如果堆内存中不是规整的,已被使用的内存和未使用的内存交错在一块,那么虚拟机就不能使用指针碰撞了,虚拟机必须要维护一个内存使用的列表。记录上那块内存是可用的,在分配的时候找到一块足够大的内存空间划分给对象实例。并更新列表上的记录。这种分配方法称为空闲列表。选择用那种方式由java堆是否规整来决定,而java堆是否规整又由所采用的垃圾收集器是否带有空间压缩整理的能力决定。因此,当使用serial,ParNew等带压缩整理过程的收集器时,系统采用的分配算法是指针碰撞,既简单又高效,当使用CMS这种基于清除算法的收集器时,理论上就只能采用较为复杂的空闲列表来分配内存。
还要考虑为对象分配内存的时候原子性的操作,如果线程A在为对象分配内存,线程B也为对象分配内存会出现竞态条件的问题。虚拟机是通过 theard local allocation buffer TLAB 分配原则(TLAB就是多个线程私有的分配缓冲区),那个线程要分配内存,就在那个线程的本地缓冲区中分配,只有本地缓冲区分配完了,才需要同步锁定。
指针碰撞和空闲列表内存分配方式取决于使用什么样的垃圾收集器。
2.对象的内存布局
在HotSpot虚拟机里,对象在堆内存中的存储布局可以划分为三个部分:对象头,实例数据和对其填充。
对象头包括两类数据:第一类是用于存储对象自身运行的数据,如哈希值,gc分代年龄,锁状态标志,线程持有的锁,偏向线程id,偏向时间戳等。对象头里面的信息是于对象自身定义的数据无关的额外存储成本。
第二类是对象指向常量池中类的元数据指针。
二.对象的访问定位
我们创建对象是为了后期使用对象,我们的java程序会通过栈上的 reference数据来操作堆上的具体对象。主流的访问方式是使用句柄和直接指针两种。
三.OutOfMemoryError异常
1.堆溢出
创建对象产生的溢出。
2.虚拟机栈和本地方法栈溢出
《java虚拟机规范》中描述了两种溢出
3.方法区和运行常量池内存溢出
方法区中保存类名,访问修饰符,常量池,字段描述,方法描述等。对于这部分区域的测试,基本思路是运行时产生大量的类区填满方法区,直到溢出为止。
常量池原来是在方法区中定义,到了jdk8将常量池替换成元空间。
4.本地直接内存溢出
第三章 垃圾收集器与内存分配策略
一.回收算法整理
垃圾回收需要完成的三件事
1.引用计数算法
做什么?
记录每一个对象的引用值,当引用值为0的时候代表这个对象已不再使用。
弊端
在相互引用的情况下,虽然对象已经不在使用,但是引用值还没有设置为0,就不会回收。
2.可达性分析算法
二.分代收集理论
1.标记-清除算法
1) 做什么?
把内存中需要回收的对象进行标记,在标记完成后,统一回收。
2) 缺点:
1.执行效率不稳定,如果java堆中有大量需要回收的对象,这时就需要进行大量的标记和清除动作。导致标记和清除的执行动作都随着对象数量增长而降低。
2.内存空间的碎片化问题,标记和清除后会产生大量不连续的内存碎片,为后续分配较大对象时找不到连续的内存而不得不提前触发另一次垃圾收集动作。
下面的垃圾算法都是基于标记-清除算法衍生而来
2.标记-复制算法
1)出现的原因
为了解决标记-清除算法面对大量可回收对象时执行效率低的问题。
2)怎么做的?
将可用的内存分成两块,每次只使用其中一块。当这一块用完了,就将还存活者的对象复制到另外一块上面。然后把使用过的内存空间清理掉。
3)缺点
1.将可用的内存缩小为原来的一半,空间浪费大。
2.如果复制是有大量存活的对象,在内存中会产生大量内存间的复制开销。
3.标记-整理算法
针对老年代的算法
1)做什么
将所有存活的对象都向一端移动。然后直接清理掉边界以外的内存。
二.hotspot算法的实现细节
1.跟节点枚举
1)做什么?(自己理解)
跟节点枚举就是从gc roots集合中根据引用链查找对象是否在引用的过程。
2)导致stop the world stw 原因
因为跟节点枚举必须在一个能保证一致性的快照中进行。如果跟节点集合对象的引用关系在不断发生变化,会导致分析结果准确性无法保证。但是这个过程不是很长,不用按照gc roots进行排查。因为其中有一个oopmap数据结构。
3)利用oopmap数据结构怎么做?
当发生 stw 之后,其实并不需要一个不漏的检查完所有执行上下文和全局变量的引用位置。hotspot中使用一组oopmap数据结构来达到这个目的。一旦类加载完成的时候,在即时编译过程中,也会在特定的位置记录下栈里和寄存器里那些位置是引用。这样hotspot就会把对象内什么偏移量上是什么类型的数据计算出来,收集器在扫描时就可以直接得到这些信息,不需要真正一个不漏的从方法区等gc roots开始查找。
2.安全点
1)做什么
保证用户线程到达安全点后执行暂停。要达到“是否具有让程序长时间执行的特征”才可以选择为安全点,不能在什么地方都选用安全点。
3.安全区域
1) 做什么
安全点的设计似乎可以完美解决如何停顿用户线程,让虚拟机进入垃圾回收的状态。那么安全区域是解决用户线程处于sleep或者blocked状态。