jdk&jre
Java程序设计语言、Java虚拟机、Java API类库这三部分统称为JDK(Java Development Kit)。
把Java API类库中的Java SE API子集 [1] 和Java虚拟机这两部分统称为JRE(Java Runtime Environment),JRE是支持Java程序运行的标准环境
HotSpot VM:
HotSpot指的就是它的热点代码探测技术
垃圾回收中的空间分配担保
为了减少垃圾回收时间
minor gc前,检查老年代最大连续可用空间是否大于新生代对象总大小,如大于,则进行minor gc;
如小于,检查是否开启了分配担保,未开启,直接full gc;
开启后,检查老年代最大连续可用空间是否大于历次晋升老年代对象大小,小于则进行minorgc,同时将新生代放不下的对象提前放到老年代中,大于则full gc
根节点枚举
在特定位置时就记录对象关系到OopMap中
暂停用户线程;使用OopMap存储:类加载完成时,将什么对象存储在什么位置取出来;即时编译时,将对象存储取出放到特定位置;不需要一字不漏的将gcroot从方法区等位置取出
安全点
在特定的位置生成OopMap记录,称为安全点;到安全点时,用户线程才停止,进行垃圾回收
用户线程主动式中断:主动轮询中断标识,为true时,中断
- 循环的末尾
- 方法返回前
- 调用方法的 call 之后
- 抛出异常的位置
安全区
用户线程sleep或者blocked状态时,无法响应系统的中断请求,挂起自己线程
某个区域内,引用关系不会发生变化,进行垃圾回收是安全的
记忆集和卡表
存在跨区域垃圾回收时,GC root并不是包含所有区域的root节点,非回收区域存在回收区域的指针时,才需要加入gc root节点;用于缩小gc root扫描范围
使用以下结构存储了这些引用关系
记忆集:是否存在非回收区域指向回收区域的指针
卡表是记忆集(一种抽象概念)的实现,一个卡表存在多个卡页,一个卡页存在一个(或多个对象)对象存在跨代指针时,记录变脏标志为1,后续将这个内存页的数据加入GC ROOT一并扫描
写屏障
卡表变脏时间:引用类型对象赋值时,卡表可能变脏
引用类型对象赋值后,使用写后屏障,更新卡表。
写前屏障在G1垃圾处理器后才使用到
三色标记:并发标记阶段(可达性分析)
gcroot向下遍历对象时的算法
按照是否被垃圾回收器访问过,分为白色(没被访问过,不可达),黑色(被访问过,所有引用都被访问过,不能直接指向白色,需通过灰色间接指向白色,原因是黑色是被遍历完成的,下次标记不能重新扫描引用,此时白色会被误清理),灰色(至少一个引用未被访问过,正在枚举过程中)
假设访问对象A,访问对象A的所有引用,变为灰色对象,访问完成时,将A变为黑色对象;所有对象遍历完成时,剩余的白色对象即为垃圾;
并发标记阶段,用户线程同步进行,对象的引用关系发生了变化,因此在重新标记阶段需要对变化的引用进行处理;
引用关系两种变化场景:
1 删除引用,黑色对象引用被删除,成为浮动垃圾,下次垃圾回收时回收即可;
2 新增引用,黑色对象下添加白色对象A->F,当原来引用白色的关系被删除时B.f=null,此时进行重新标记时,B变为黑色,F还是白色,会被误清理
解决方案
增量更新
从增量角度,A.f=F,增加引用时,添加写屏障,将黑色引用白色的引用关系记录一下,重新标记时,将引用关系重新扫描,实现方案是将A变为灰色,a的引用关系重新扫描
原始快照
从删除角度,在执行B.f=nul,插入一个写屏障,记录B.f,再进行置空操作,重新标记时,将B.F变为黑色对象,不管吧B.F是否还有引用都不会被清理,如果没有引用下次垃圾回收会清理掉。宁可放过,不可杀错的思想。
引用逃逸
如果一个对象的指针被多个方法或者线程引用时,那么我们就称这个对象的指针发生了逃逸。
gcroot包含静态类,synch锁定对象、常量池中对象
垃圾回收
fullgc场景
大对象(sql未分页等)分配
metaspace溢出,classloader未回收导致元空间溢出,发生full gc
内存泄漏,由于内存泄漏会导致内存溢出
jvm参数设置不合理
内存溢出
不断创建对象时,gc root存在引用关系时,堆空间不足时会发生内存溢出
堆
确认oom对象是否是必要的,即内存泄漏还是内存溢出,内存泄漏需要使用工具判断引用链,内存溢出需要判断堆大小相关配置
栈
StackOverflow
虚拟机容量太小,栈帧太大,都会引起新的栈帧无法分配内存
线程请求栈的深度大于虚拟机允许的最大深度时
oom
创建线程时无法获得内存时会出现oom(jvm实现上不允许栈自动扩容,理论上允许)
方法区(类型信息(类名,父类,修饰符,实现的接口列表等)、域(属性)、方法、常量、静态变量,运行时的常量池,编译后的代码缓存)
垃圾回收主要包含:常量池和类的卸载
jdk1.6 PerGen OOM
jdk1.7 java heap
jdk1.8 metasapce oom
本地直接内存
使用NIO时,日志内容较少
永久代和方法区的关系?和元空间的关系?
收集器的分代设计扩展到方法区时,用永久代的概念实现方法区的垃圾回收,效果不理想,因此从jdk7开始逐步将永久代的字符串常量池和静态变量等移出(堆中),jdk8中永久代消失,将剩余内容(主要是类型信息)移动到直接内存metaspace元空间中
gc和内存溢出的关系
分配空间时,发现空间不足(应用空闲时也会)时会进行gc,gc后空间还不够,抛出内存溢出相关异常
减少GC开销的措施
(1)不要显式调用System.gc() 此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。
(2)尽量减少临时对象的使用 临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。
(3)对象不用时最好显式置为Null 一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
(4)尽量使用StringBuffer,而不用String来累加字符串 由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,如Str5=Str1+Str2+Str3+Str4,这条语句执行过程中会产生多个垃圾对象,因为对次作“+”操作时都必须创建新的String对象,但这些过渡对象对系统来说是没有实际意义的,只会增加更多的垃圾。避免这种情况可以改用StringBuffer来累加字符串,因StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
(5)能用基本类型如Int,Long,就不用Integer,Long对象 基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
(6)尽量少用静态对象变量 静态变量属于全局变量,不会被GC回收,它们会一直占用内存。
(7)分散对象创建或删除的时间 集中在短时间内大量创建新对象,特别是大对象,会导致突然需要大量内存,JVM在面临这种情况时,只能进行主GC,以回收内存或整合内存碎片,从而增加主GC的频率。集中删除对象,道理也是一样的。它使得突然出现了大量的垃圾对象,空闲空间必然减少,从而大大增加了下一次创建新对象时强制主GC的机会。