12 | 垃圾回收(下)
12 | 垃圾回收(下)
讲述:郑雨迪
时长10:30大小4.81M
Java 虚拟机的堆划分
卡表
总结与实践
附录:Java 虚拟机中的垃圾回收器
赞 16
提建议
精选留言(80)
- 钱置顶2018-08-21写的非常棒,又帮我看到了一个小黑盒中的天地。 小结: 1:二八法则-适用于许多的领域,对象在JVM对内存空间的生命周期也同样符合 2:为了更好的JVM性能以及充分利用对象生命周期的二八法则,JVM的作者将JVM的对内存空间进行了分代的处理 3:堆内存空间=年轻代+老年代 年轻代=Eden+from+to 年轻代用于分配新生的对象 Eden-通常用于存储新创建的对象,对内存空间是共享的,所以,直接在这里面划分空间需要进行同步 from-当Eden区的空间耗尽时,JVM便会出发一次Minor GC 来收集新生代的垃圾,会把存活下来的对象放入Survivor区,也就是from区 注意,from和to是变动的 to-指向的Survivor区是空的,用于当发生Minor GC 时,存储Eden和from区中的存活对象,然后再交换from和to指针,以保证下一次Minor GC 时to指向的Survivor区还是空的。 老年代-用于存储存活时间更久的对象,比如:15次Minor GC 还存活的对象就放入老年代中 4:堆内存分代后,会根据他们的不同特点来区别对待,进行垃圾回收的时候会使用不同的垃圾回收方式,针对新生代的垃圾回收器有如下三个:Serial、Parallel Scavenge、Parallel New,他们采用的都是标记-复制的垃圾回收算法。 针对老年代的垃圾回收器有如下三个:Serial Old 、Parallel Old 、CMS,他们使用的都是标记-压缩的垃圾回收算法。 5:TLAB(Thread Local Allocation Buffer)-这个技术是用于解决多线程竞争堆内存分配问题的,核心原理是对分配一些连续的内存空间 6:卡表-这个技术是用于解决减少老年代的全堆空间扫描展开
作者回复: 好长的总结,赞一个
共 4 条评论59 - 公子_小白2018-08-23老师您好 请问JVM分代收集新生代对象进入老年代,年龄为什么是15而不是其他的? 谢谢
作者回复: HotSpot会在对象头中的标记字段里记录年龄,分配到的空间只有4位,最多只能记录到15
共 5 条评论57 - 素丶2018-11-20可以配合R大的文章 http://rednaxelafx.iteye.com/blog/1042471 http://rednaxelafx.iteye.com/blog/174865 http://rednaxelafx.iteye.com/blog/1044951共 2 条评论20
- javaadu2018-08-17写得真好,搞清楚了之前没掌握透彻的概念: (1)TLAB是为了避免对象分配时对内存的竞争 (2)卡表是为了处理minor gc时老年代对新生代的引用,为了避免整堆扫描而提出了卡表的概念 提个问题:JVM中堆内存根系统的物理内存是如何对应的,这个可以从哪里看?展开
作者回复: 根系统指的是? GC roots分布在HotSpot中的每个子系统里。可以在源码中搜oops_do
共 2 条评论15 - herome2018-08-19老师 建议是能画点图吗 😂 每一篇都是文字。 相信画了图 不仅能让文章通俗易懂,也能让老师的文笔更好。
作者回复: 多谢建议!
共 2 条评论13 - Shine2018-08-17通过GC_roots探索存活对象的时候,有的对象在新生代,有的对象在老年代。如果当前对象处在老年代而且在赃卡中,需要继续深度遍历对象。若不在赃卡中,就没必要进一步深度遍历下去了。不知道我的理解对不?
作者回复: 对的。卡表就是为了避免探索整个老年代,而只将脏表中的对象作为GC roots。
12 - Hi Young2020-02-02个人读书笔记,欢迎批评指教 https://www.yuque.com/zhaohaiyang/notes/jvm-basic-principles#2cqqW 【目录如下】 JVM基本原理 09 | 垃圾回收(GC) 垃圾 如何识别对象的存亡 》引用计数法(reference counting) 》可达性分析法 》Stop-the-world(STW)以及安全点(safepoint) 》》Stop-the-world(STW) 》》安全点(safepoint) 垃圾回收方式 》标记清除(mark-sweep) 》标记压缩(mark-compact) 》复制(copy) JVM的分代回收 》分代回收的前提假设 》分代回收思想 》JVM的堆划分 》TLAB(Thread Local Allocation Buffer,线程本地分配缓冲区) 》堆空间的使用及分代回收 》卡表 》》用途 》》思想 》》逻辑结构 》》卡标识位的赋值 》》存在的问题展开共 3 条评论10
- 袁帅2020-09-101. "该技术将整个堆划分为一个个大小为 512 字节的卡" 卡表是将 整个堆划分为一个个卡,还是把老年代划分为一个个卡? 我的理解是,既然卡表的目的为了标识 老年代的对象指向新生代,那么只在老年代划分就好了呀? 老年代引用新生代的时候,虚拟机将卡对应的卡表元素设置为脏卡。 2. “当完成所有脏卡的扫描之后,Java 虚拟机便会将所有脏卡的标识位清零。” 如果清零的话,那么第二次minor GC 判断新生代对象是否存活的时候,就找不到脏卡的内容了, 从而无法判断对象是否存活。 我的理解是, 老年代 Major GC 的时候,判断如果老年代 对象有指向新生代对象那么 标记对应的卡表 为脏吧? 反正 对我卡表的实现原理 很困惑,请老师给予解惑,谢谢谢谢展开8
- Ben2019-12-23老师,有个疑问想请教一下: 如果Eden区和from指向的Survivor区存活对象大于to指向的Survivor区的大小,那么JVM会如何处理?
作者回复: 会按对象年龄晋升最老的那些至老年代。极端情况,假设survivor 区大小为0,那就是直接把eden区的存活对象晋升过去了
共 2 条评论6 - 愤怒的虾干2019-02-191、当默认开启动态分配时,若ALIVE_OBJECT_SIZE小于33M几乎无FULL GC,大于则出现FULL GC。动态分配时Eden区大小不能小于Survivor区,即最少为新生代内存的1/3,单个Survivor区最大为新生代内存的1/3。故当ALIVE_OBJECT_SIZE大于33M时,Survivor区小于ALIVE_OBJECT_SIZE,导致Minor GC时需要复制到to区的数据大于to区容量,从而使得一部分数据提前晋升到老年区,多次提前晋升导致老年区无多余空间从而导致Full GC;当ALIVE_OBJECT_SIZE小于33M时,Survivor区容量总是近似的接近ALIVE_OBJECT_SIZE大小,使得每次Minor GC有效对象都可以复制到Survivor区,而晋升到老年代的对象大多是年龄达到次数要求,短期内不会挤满老年代空间,在有限时间内运行结束不会引发Full GC。 2、当-XX:-UsePSAdaptiveSurvivorSizePolicy or -XX:SurvivorRatio=N关闭动态分配或指定Eden、Survivor比例时,只要ALIVE_OBJECT_SIZE小于Survivor容量,有限时间运行结束不会引发Full GC。反之会导致Full GC。 @郑老师 是否是这样?展开共 1 条评论5
- Jimbol2018-08-18看某些资料介绍说tlab是将对象创建在栈内存上,并且与逃逸分析一起用,这样在释放栈内存时就直接将这部分内存一起释放了,降低了gc成本。您讲解的说tlab是提前在堆内存中分配空间,这样没有降低gc成本呀!
作者回复: HotSpot不支持在栈上新建对象。 C2里的逃逸分析是静态分析,和TLAB没什么关系。它和标量替换一起使用,能够完全不分配对象,仅在寄存器中维护这个对象的字段。
5 - abs2018-08-29新声代回收的时候会把老年代的所有对象当做gcroot吗
作者回复: 原本是的。但使用了脏卡技术之后,JVM只需要把脏卡中的对象当成GC roots
4 - Alex Rao2018-08-19TLAB 和 工作内存是什么关系?
作者回复: 如果你说的是JMM中的工作内存,那是JMM抽象出来的一个概念,在现实的体系架构中可以映射为CPU中的缓存,属于硬件技术。 TLAB是用来优化多线程分配的,属于软件技术。两者没啥关系。
5 - 郭俊杰2018-08-18老师,你好,一直想问个问题,多线程访问共享变量会存在线程安全问题,那么方法内部通过new的局部变量是不是也存在安全问题呢?希望老师帮忙回答下感谢
作者回复: 如果你把new后的对象发布出去,就是放到其他共享变量中,也会存在线程安全问题。如果new后只是在方法中使用该对象而不作为参数传出去(以它为调用者的实例方法也不会传出去),那么没有问题。
4 - river2019-09-09总结与实践中的代码ObjectOf64Bytes的64=6*8(6个long类型字段)+16(该对象的对象头大小,不是说压缩后是12字节么?)3
- 0xTang2019-06-25如果新生代晋升到老年代失败的时候如何处理?(可能原因:老年代内存不够,老年代碎片过多,晋升的新生代太大)共 2 条评论3
- 王盛武2019-01-19老师好,请问java8开始之后的元数据区的回收是如何呢?这部分属于堆外内存吗?共 4 条评论3
- Geek_9871692018-09-25卡表中将堆划分为大小为512字节的卡,意思是将512字节的数据作为一个卡吗?然后用卡片中的比特位1,0代表是否存在GC root?
作者回复: 1. 可以这么说。 2. 指的是这张卡上的对象很可疑,可能存在GC roots。
3 - Geek_9871692018-09-24老师,GC ROOT到底指的是对象本身,还是引用?
作者回复: 严格来说应该是对象。像局部变量中存放的引用只是导致对象成为GC roots的原因。我个人倾向于将这些引用作为GC roots,因为GC是从这些地方出发开始探索的。看各人理解方便吧。 你可以参考eclipse MAT定义的GC roots: https://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Fconcepts%2Fgcroots.html
3 - 剑八2020-06-121.java将堆分成年轻代及年老代分别应对不同的对象生命周期消亡的特点 年轻代使用标记复制:年轻代对象存活时间短,垃圾回收后相应的存活对象少可用标记复制算法。并且年轻代用于分配新对象,所以要求有连续空的内存用于快速分配。这里涉及TLAB,即java线程会先申请线程单独有的内存。 年老代使用标记清除,年老代对象存活时间长,垃圾回收后会有大量对象还是在的,所以用标记复制的话复制成本比较大,相比直接清除成本理低。且老年代分配新对象的频率较小,是一个trade off的过程。 2.Minor gc垃圾回收涉及如何防止新生代有个对象在线程等gc root没有在用,但实际老年代对象存在新生代对象的引用。这个解法就是卡表,将内存划分512字节的卡页,卡页有标识是否为脏页的字段。 在Minor gc时如果发现新生代对象卡页为脏,则不会对该对象进行回收。 卡页设置的触发点是:在复制对象到老年代时需要更新对应引用,这时将引用所对应的卡页设置为脏标记。eg:对象C中引用了新生代对象B,对象C在Minor gc时需要移到老年代,这时需要更新新生代对象B所在卡表为脏页。展开2