1.运行时数据区域
程序计数器:改变该值来选取下一条执行的字节码指令
这是此内存趋于唯一一个不会OutOfMemoryError的区域
虚拟机栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息
本地方法栈:存储Native方法
堆:存放对象实例,也是垃圾回收的主要区域
方法区:也称为老年代,存储已加载的类信息、常量、静态变量等。
该区域GC可实现也可不实现,GC效率低
对象的访问定位
通过栈上的reference来操作堆上的具体对象
有两种主流的访问方式:①使用句柄②直接指针
各自优势:
句柄访问:reference存储稳定的句柄地址,在对象被移动后只会修改句柄中的指针,
reference本身不需要修改
直接指针:速度快,少一次指针定位的时间
OutOfMemoryError异常
java堆溢出
内存泄漏,通过工具查看泄漏对象到GC Roots的引用链,就可以找到泄漏位置。
内存溢出,对象都存活着,查看堆参数是否能调大,或者对象生命周期过长或者持有时间过长
虚拟机栈和本地方法栈溢出
在单线程的情况下抛出都是StackOverflowError异常,多线程才可能会OutOfMemoryError
每个线程分配到的栈容量越大,建立线程越容易把剩下的内存耗尽
因为建立过多线程导致内存溢出,可以减少最大堆和减少栈容量来换取更多线程
方法区和运行时常量池溢出
当spring、hibernate对类增强时,需要大的方法区保证生成class能载入内存
本机直接内存溢出
如果OOM后Dump文件很小,而程序中直接或间接使用了NIO就有可能是这个问题
2.垃圾收集器与内存分配策略
判断对象存活还是死亡
引用计数算法,给对象添加一个引用计数器,每有一个地方引用计数器就+1,
引用失效计数器就-1,到0的时候就可以判定死亡
缺点:很难解决对象之间的循环引用
可达性分析算法,一系列称为“GC Roots”对象作为起始点,作引用链遍历,
当一个对象到GC Roots没有任何引用链,则判断死亡
哪些对象可称为GC Roots:
虚拟机栈中引用的对象,方法区中静态属性引用的对象,方法区中常量引用的对象
垃圾收集算法
标记清除法,先标记需回收对象,然后统一进行回收。
不足:标记和清除两个过程效率不高,清除后产生大量不连续的内存碎片
复制算法,将内存划分大小相等两块,每次只使用一块,用完了,把活着的复制到另一块上,
清空当前这块。不用担心内存碎片。适合新生代
不足:浪费一半的内存。
标记整理,让存活的对象向一端移动,清理掉端边界外的内存,适合于老年代。
分代收集,把堆分为老年代和新生代,采用不同的收集算法。
HotSpot算法实现
枚举根节点,从GC Roots的引用中查找非常耗时,而且需要stop world来确保一致性
OopMap使得可直接得到引用地址,省时。
安全点,只有在安全点才会记录OopMap,所以在安全点才会GC。
是否具有让程序长时间执行的特征是选择安全点的依据。
两种方案使所有线程在最近的安全点上停顿:
抢先式中断,发生GC时,先全部中断,如果有线程没在安全点上,就恢复使其跑到安全点。不适用。
主动式中断,发生GC时,设置一个标志,所有线程主动轮询这个标志,为真时中断挂起。
安全区域,是安全点的扩展,在一段代码中引用关系不会变化,在这个区域GC是安全的。
垃圾收集器
Serial收集器,只使用一个线程去收集垃圾,每次收集都要stop world。
单线程情况下,简单高效,没有线程交互开销,适合新生代
ParNew收集器,serial的多线程版,配合CMS使用,在线程少的情况下,效率还不如serial
Parallel Scavenge收集器,为了达到一个可控的吞吐量,和ParNew类似的多线程收集器
GC停顿缩短是以牺牲吞吐量和新生代空间来换取的,如果把新生代内存调小,停顿时间确实下降,但吞吐量也下降了。
相同时间内停顿的次数多了,每次停顿时间少了,利于用户交互,但停顿造成的总时间变大了
Serial Old收集器,serial的老年代版本,
Parallel Old收集器,在注重吞吐量以及CPU敏感的场合可以考虑Parallel Scavenge和Parallel Old
CMS收集器(重要),使得停顿时间最短,基于标记清除
①初始标记,stop world,标记GC Roots能直接关联到的对象
②并发标记,GC RootsTracing,与用户线程一起工作
③重新标记,stop world,修正并发标记期间用户线程继续运作导致标记变动的对象标记
④并发清除,与用户线程一起工作
缺点:对CPU资源敏感,在并发阶段占用一部分CPU资源,总吞吐量降低。
无法处理浮动垃圾,清理阶段和用户线程并发,还会有垃圾产生,只能在下次GC清理这些垃圾。
G1收集器(重要),追求低停顿,
①初始标记,stop world,标记GC Roots能直接关联到的对象,修改TAMS值,使得下一阶段能在正确的Region创建对象
②并发标记,GC Roots可达性分析,找出存活对象
③最终标记,stop world,修正并发标记时程序运行导致标记的改变
④筛选回收,对各个Region的回收价值和成本进行排序,根据用户期望GC停顿时间来制定回收计划
内存分配与回收策略
①对象优先在新生代Eden区分配,如果没有足够空间则会进行一次Minor GC(新生代GC)
②大对象直接进入老年代,
③长期存活的对象将进入老年代,给对象添加一个age计数器,每过一个Minor GC,年龄+1,到一定程度晋升到老年代
④动态对象年龄判定,如果Survivor空间相同年龄对象总和大于Survivor空间的一半,那么大于或等于这个年龄的对象,
可以直接进入老年代。
⑤空间分配担保,Minor GC前检查老年代最大可用连续空间是否大于新生代所有对象总空间,如果可以,则可保证GC安全
否则,查看配置HandlePromotionFailure决定是否冒险,如果老年代连续空间大于历次晋升到老年代对象的平均大小,则冒险,
否则进行一次Full GC。