方法区的各版本差异
JDK 1.6以及之前,方法区的实现为 永久代(Permanent Gen)的方式,目的是为了垃圾收集器能像管理java堆一样管理这部分内存.垃圾回收目标是针对常量池的回收和对类型的卸载.
JDK 1.7中,存储在永久代的部分数据就已经转移到Java Heap或者Native Heap。但永久代仍存在于JDK 1.7中,并没有完全移除,如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了Java heap;类的静态变量(class statics)存放于定义类型的class对象中,存放在Java heap中.
JDK 1.8中, 完全移除了永久代,取而代之的实现方式成为元空间(Metaspace),将类元数据放到本地内存中,将字符常量池和静态变量放到Java堆里。虚拟机会为类的元数据明确分配和释放本地内存。
JDK 1.8 设置metaspace的参数
-XX:MetaspaceSize=N和-XX:MaxMetaspaceSize=N,对于64位JVM来说,元空间的默认初始大小是20.75MB,默认的元空间的最大值是无限。MaxMetaspaceSize用于设置metaspace区域的最大值,这个值可以通过mxbean中的MemoryPoolBean获取到,如果这个参数没有设置,那么就是通过mxbean拿到的最大值是-1,表示无穷大。
MetaspaceSize表示metaspace首次使用不够而触发FGC的阈值,只对触发起作用
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M(PS:读者可以根据自己的实际情况再调整)。
垃圾回收器
垃圾回收需要关注三部分:
- 内存如何分配?
- 垃圾回收过程?
- 各垃圾回收器的优劣 目前常用垃圾回收器:
- 新生代Parallel Scavenge(PSNew) + 老年代(ParOld) 追求的是CPU利用率和吞吐量,也就是GC占比时间少,一般是服务器首选;
- 新生代ParNew+老年代CMS
追求的是STW的时间短,这样用户体验好,追求用户体验的可以用这组。
3.G1
4.ZGC
CMS
内存分配过程:
经典内存分配过程
垃圾回收过程分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与 root 相连的对象,速度很快 ;
- 并发标记: 同时开启 GC 和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC 线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短
- 并发清除: 开启用户线程,同时 GC 线程开始对未标记的区域做清扫。
三色标记法
mp.weixin.qq.com/s?__biz=Mzg…
并发标记带来的问题:
浮动垃圾的产生:
在并发标记阶段,用户线程修改了引用关系,尤其是删除了某个引用,会导致本应该被标记回收的对象没有回收。
对象消失:
在并发标记阶段,用户线程修改了引用关系,导致存活的对象被标记为死亡【未被可达性跟踪】,被回收了
解决方案:
增量更新、原始快照
juejin.cn/post/699612…
CMS优点:并发收集、低停顿。
但是它有下面三个明显的缺点:
- 对 CPU 资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
G1
cloud.tencent.com/developer/a…
juejin.cn/post/701003…
三色标记法,增量更新,原始快照
什么是"三色标记"?
在遍历对象图的过程中,把访问都的对象按照 "是否访问过"这个条件标记成以下三种颜色:
白色: 表示对象尚未被垃圾回收器访问过。 显然,在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
黑色: 表示对象已经被垃圾回收器访问过,且这个对象的所有引用都已经扫描过。 黑色的对象代表已经扫描过,它是安全存活的,如果有其它的对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
灰色: 表示对象已经被垃圾回收器访问过,但这个对象至少存在一个引用还没有被扫描过。
读完上面描述,再品一品下面的图:
可以看到,灰色对象是黑色对象与白色对象之间的中间态。当标记过程结束后,只会有黑色和白色的对象,而白色的对象就是需要被回收的对象。
并发标记带来的问题
垃圾回收器在对象图上面标记颜色,而同时用户线程在修改引用关系,引用关系修改了,那么对象图就变化了,这样就有可能出现两种后果:
一种是把原本消亡的对象错误的标记为存活,这不是好事,但是其实是可以容忍的,只不过产生了一点逃过本次回收 的浮动垃圾而已,下次清理就可以。
一种是把原本存活的对象错误的标记为已消亡,这就是非常严重的后果了,一个程序还需要使用的对象被回收了,那程序肯定会因此发生错误。
有一个大佬叫Wilson,他在1994年在理论上证明了,当且仅当以下两个条件同时满足时,会产生"对象消失"的问题,原来应该是黑色的对象被误标为了白色:
条件一:赋值器插入了一条或者多条从黑色对象到白色对象的新引用。
条件二:赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
什么是增量更新呢?
增量更新要破坏的是第一个条件(赋值器插入了一条或者多条从黑色对象到白色对象的新引用),当黑色对象插入新的指向白色对象的引用关系时,就将这个新插入的引用记录下来,等并发扫描结束之后,再将这些记录过的引用关系中的黑色对象为根,重新扫描一次。
什么是原始快照
原始快照要破坏的是第二个条件(赋值器删除了全部从灰色对象到该白色对象的直接或间接引用),当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,在并发扫描结束之后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。
这个可以简化理解为:无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照开进行搜索。
需要注意的是,上面的介绍中无论是对引用关系记录的插入还是删除,虚拟机的记录操作都是通过写屏障实现的。
CMS和G1的区别
CMS有以下几个缺点:
- 无法处理浮动垃圾(因为采用增量更新的原因)
- 复制-清楚算法会导致内存碎片
- CMS收集器对CPU资源非常敏感。
CMS解决对象消失方法:增量更新
G1解决对象消失方法:原始快照
关于增量更新、原始快照、记忆集、卡表的原理:
www.cnblogs.com/hongdada/p/…
cloud.tencent.com/developer/a…
ZGC
对象创建过程
1.类加载检查
2.分配内存
3.初始化零值
4.设置对象头
5.执行init方法(初始化)
类加载过程
1.加载 2.验证 3.准备 4.解析 5.初始化
golang gc
golang不分代,内存分配由golang自己控制,采用三色标记法,标记清除算法,特点是采用混合写屏障实现,增量更新+原始快照,其他都和jvm差不多。
juejin.cn/post/684490…
juejin.cn/post/715243…
内存溢出和内存泄露
内存泄露
内存泄漏是指程序在申请内存后,无法释放已申请的内存空间。
案例:
- ThreadLocal
- 静态集合类 ,集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
- 各种连接,如数据库连接、网络连接和IO连接等
内存溢出
内存溢出是指程序申请内存时,没有足够的内存供申请者使用;或者说提供一块存储int数据的存储空间,但存储了long数据,则结果是内存不够用,报错OOM。内存泄漏的堆积最终会导致内存溢出。
内存溢出原因:
- 内存中加载的数据量过于庞大,如一次从数据库取出过多数据,导入过程
- 代码中存在死循环或循环产生过多重复的对象实体;
- 启动参数内存值设定的过小
字符串常量池、运行时常量池、类静态变量
字符串常量池、类静态变量、class对象1.7及之后存放在堆里,
运行时常量池1.8大部分文章是说放在方法区也就是metaspace里,虚拟机规范也是这么规定的,但是有说Hotspot实际上也把这部分挪到了堆里,待求证
www.cnblogs.com/cosmos-wong…
www.cnblogs.com/shoshana-ko…
ask.csdn.net/questions/1…
sync实现原理和锁膨胀过程
www.cnblogs.com/aspirant/p/…
tech.youzan.com/javasuo-yu-…
动态年龄的计算
动态年龄计算:Hotspot遍历所有对象时,按照年龄从小到大对其所占用的大小进行累积,当累积的某个年龄大小超过了survivor区的一半时,取这个年龄和MaxTenuringThreshold中更小的一个值,作为新的晋升年龄阈值。在本案例中,调优前:Survivor区 = 64M,desired survivor = 32M,此时Survivor区中age<=2的对象累计大小为41M,41M大于32M,所以晋升年龄阈值被设置为2,下次Minor GC时将年龄超过2的对象被晋升到老年代。
空间担保机制
在发生 Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间,如果老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行 Minor GC,否则将进行 Full GC。
new对象时内存分配并发控制
在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:
- CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
- TLAB: 为每一个线程预先在 Eden 区分配一块儿内存,JVM 在给线程中的对象分配内存时,首先在 TLAB 分配,当对象大于 TLAB 中的剩余内存或 TLAB 的内存已用尽时,再采用上述的 CAS 进行内存分配
minor gc会发生stw吗
会的,就是时间短
JVM怎么解决跨代引用
卡表记录跨代引用的映射 zhuanlan.zhihu.com/p/651799722