java8堆内存模型_「GC系列」JVM堆内存分代模型及常见的垃圾回收器
1. 内存分代模型
为什么要说JVM的内存分代模型呢,因为内存分代和垃圾回收器的运行是有关系的。
现在大部分用到的垃圾回收器在逻辑上是分代的,除了G1之外的其他垃圾回收器在逻辑上和物理上都是分代的。
除了Epsilon、ZGC、Shenandoah之外的GC都是是逻辑分代模型
G1是逻辑分代,物理上不分代
除此之外的不仅逻辑分代,而且物理分代
逻辑分代是「给内存做一些概念上的区分」,物理分代是真正的物理内存。
「具体划分」
新生代(young)和老年代(old/tenured)。
「新生代」:刚new出来的那些对象
「老年代」:垃圾回收了很多次都没有把它回收掉的老对象
新生代又分为:
- 「eden」 默认比例是8。新new出来的对象放在eden区。
- 「survivor」(s1) 默认比例是1。垃圾回收一次之后跑到这个区域,该区域存放的对象不同,采取的垃圾回收算法也不同。
- 「survivor」(s2) 默认比例是1。
新生代存活的对象较少,使用的垃圾回收算法是拷贝算法(Copying)。
老年代活着的对象较多,垃圾回收算法适用标记压缩(Mark Compact)或者标记清除(Mark Sweep)。
「几个GC的概念」
- MinorGC/YGC 新生代空间耗尽时触发的垃圾回收。
- MajorGC/FullGC 在老年代无法继续分配空间时触发,新生代、老年代同时进行垃圾回收。
「分代空间参数配置」
-Xms-Xmx-Xmn复制代码
X是非标参数,m是memory内存,s是最小值,x是最大值,n是new
分代空间大小参数配置
2. 一个对象的生命历程-从出生到消亡
一个对象被new出来之后,首先尝试进行栈上分配,栈上如果分配不下才会进入eden区;
eden区经过一次垃圾回收之后进入一个survivor区-s1区;
survivor区(s1)经过一次垃圾回收之后又进入另一个survivor区-s2区,同时eden区的某些对象也会跟着进入s2;
当对象年龄到某一个值后,会进入到old区。这个值可以通过参数:
-XX:MaxTenuringThreshold复制代码
进行配置。
下面这个图能够帮助我们了解JVM中内存分区的概念。
一个对象从出生到消亡的过程
3. 对象如何进行栈上分配
C语言中的struct结构体就可以直接在栈上分配,在Java中也有栈上分配的理念。
在JVM中,「堆是线程共享的」,因此堆上的对象对于各个线程都是共享和可见的,「只要持有对象的引用,就可以访问堆中存储的对象数据」。虚拟机的垃圾收集系统可以「回收堆中不再使用的对象」,但对于垃圾收集器来说,无论筛选可回收对象,还是回收和整理内存都「需要耗费时间」。
「如果确定一个对象的作用域不会逃逸出方法之外,那可以将这个对象分配在栈上」,这样,对象所占用的内存空间就可以「随栈帧出栈而销毁」。在一般应用中,不会逃逸的局部对象所占的比例很大,如果能使用栈上分配,那大量的对象就会随着方法的结束而自动销毁了,无须通过垃圾收集器回收,可以「减小垃圾收集器的负载」。
JVM允许将「线程私有的对象打散分配在栈上」,而不是分配在堆上。分配在栈上的好处是「可以在函数调用结束后自行销毁,而不需要垃圾回收器的介入,从而提高系统性能」。
综上,「栈上分配:」
- 「线程私有小对象」
- 「无逃逸」
在某一段代码中使用,除了这段代码就没有其他的代码认识它。
比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
- 「支持标量替换」
用普通的属性、普通的类型代替对象就叫标量替换。
意思是说JVM允许将对象打散分配在栈上,比如若一个对象拥有两个字段,会将这两个字段视作局部变量进行分配。
- 「无需调整」
站上分配的对象随栈帧出栈而销毁,无需内存调整。
4. 线程本地分配
对象在栈上分配不下了,会优先进行本地分配。
线程本地分配,Thread Local Allocation Buffer,简称TLAB。
很多线程都向Eden区分配对象,分配对象的线程会进行内存空间的征用,谁抢到就算谁的。出现多线程的同步,效率就会降低,因此设计了这个TLAB机制-线程本地分配。
TLAB特征:
- 占用Eden区的大小模式是1%,这1%的空间时线程独有,分配对象的时候先向这块空间进行分配。
- 多线程的时候不用竞争Eden区就可以申请空间,效率提高。
- 分配的是小对象。
- 无需调整。
我们来测试一下,在栈上分配和TLAB是否提升了效率。
public class TLABTest { // -XX:-DoEscapeAnalysis 去掉逃逸分析 // -XX:-EliminateAllocations 去掉标量替换 // -XX:-UseTLAB 去掉TLAB public static void main(String[] args) { TLABTest t = new TLABTest(); long start = System.currentTimeMillis(); //执行1000万次alloc for (int i = 0; i < 1000_0000; i++) { t.alloc(i); } long end = System.currentTimeMillis(); System.out.println("spends " + (end - start)); } // 该方法只new一个对象出来,没有任何引用指向他, // 除了这个方法就没人认识它,所以没有逃逸 void alloc(int id) { new User(id, "name" + id); } class User { int id; String name; public User(int id, String name) { this.id = id; this.name = name; } }}复制代码
执行时加上
-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB复制代码
- -XX:-DoEscapeAnalysis 去掉逃逸分析
- -XX:-EliminateAllocations 去掉标量替换
- -XX:-UseTLAB 去掉TLAB
运行结果:
spends 1237复制代码
然后再去掉-XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:-UseTLAB,也就是默认情况下执行:
spends 643复制代码
效率很明显提升了!
5. 常见的垃圾回收器
5.1 「Serial」
当Serial工作的时候,所有正在工作的线程全部停止。
线程停止有一个「safe point」(安全点),需要找到一个安全点上进行线程停止,该垃圾回收器停顿时间较长(「STW - Stop The World」),现在用的较少。
Serial
5.2 「Serial Old」
用于老年代,也是「STW」,采用的是标记清除算法(Mark-Sweep),单线程。
5.3 「Parallel Scavenge」
如果JVM没有做过任何参数设定的话,默认就是「Parallel Scavenge」和「Parallel Old」(PS+PO)。
Parallel Scavenge和Serial的区别是PS是多线程清理垃圾。
Parallel Scavenge
5.4 「Parallel Old」
用于老年代,STW,多线程执行,采用标记压缩整理算法(Mark-Compact)。
5.5 「ParNew」
Parallel NEW的意思,就是Parallel Scavenge的新版本。它就是在PS的基础上做了一些增强一遍它能和「CMS」配合使用,CMS在某一个特定阶段的时候会和ParNew同时运行。
ParNew工作的时候其余线程不能工作,必须等GC结束才行。
它工作时也STW,采用的是Copying算法,多线程执行。
5.6 「CMS」
前面几种垃圾回收器有一个共性就是STW,就是我垃圾回收器在工作的时候其他人都不许动,得等着,我干完了,你们才能继续工作。
CMS的诞生就是试图解决这个问题。然而CMS本身的问题很多,目前任何JDK版本默认的垃圾回收器都不是CMS。
CMS - Concurrent Mark Sweep,并发标记清除。从线程的角度看,CMS进行垃圾回收的时候和工作线程同时进行,但是它依然很慢。
在以往内存较小的时候,速度很快,但现在服务器内存已经很大了,相当于原来在10平米的房间内清理垃圾,现在需要在100平米甚至更大的空间内清理垃圾,即使使用多线程来清理也需要很长的时间。
「CMS的四个阶段」
- 「CMS initial mark 初始标记阶段」
该阶段「STW」,「直接找到最根上的对象并标记」,其他对象不标记。
- 「CMS concurrent mark 并发标记阶段」
GC 「80%的时间浪费在并发标记阶段」。所以该阶段和工作线程同时运行,客户端可能感觉响应变慢了,但是至少还有点反应。
工作线程一边产生垃圾,一边对垃圾进行标记,这个过程不可能标记完,所以得重新标记阶段。
- 「CMS remark 重新标记阶段」
这也是一个「STW」,在并发标记阶段产生的新垃圾,在该阶段进行重新标记一下,需要工作线程停下来,时间不是很长。
- 「CMS concurrent sweep 并发清理阶段」
该阶段由于有工作线程也在运行,因此在执行过程中会产生新的垃圾,这时候的垃圾叫「浮动垃圾」,「浮动垃圾在下一次CMS运行的时候再把它清理掉」。
CMS
「CMS的触发条件」
老年代分配不下了会触发CMS,其初始标记是单线程,重新标记是多线程。
「CMS的缺点」
- Memory Fragmentation 内存碎片
如果内存很大,一旦老年代产生了很多内存碎片的时候,从年轻代进入到老年代的对象就找不到空间了-PromotionFailed。
这时,CMS请出了Serial Old这个上古时代的回收器使用单线程进行标记压缩,那效率就可想而知了。
-XX:+UseCMSCompactAtFullCollection-XX:CMSFullGCsBeforeCompaction 默认为0 指经过多少次FGC才进行压缩复制代码
- Floating Garbage 浮动垃圾
可以通过参数配置降低触发CMS的阈值。
-XX:CMSInitiatingOccupancyFraction 92% 可以降低该值,让老年代有足够的空间复制代码
5.7 「G1」
G1也能并发进行垃圾回收,与CMS相比,其优点如下:
- G1在GC过程中会进行整理内存,不会产生很多内存碎片
- G1的STW更可控,可以指定可期望的GC停顿时间
在G1中将内存区域划分为多个不连续的区域(Region),每个Region内部是连续的。
G1内存区域划分
在划分的区域中H区(Humongous),这表示这些Region存储的是巨大对象(humongous object,H-obj)-「大小大于等于region一半的对象」。
一个Region的大小可以通过参数
-XX:G1HeapRegionSize复制代码
设定,取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定。
5.8 「ZGC」
ZGC是一种「并发的」、「不分代的」、「基于Region且支持NUMA的压缩收集器」。因为只会在枚举根节点的阶段STW, 因此「停顿时间不会随着堆大小或存活对象的多少而增加」。
「ZGC的目标」
- 垃圾回收停顿时间不超过10ms
- 无论是相对小的堆(几百MB)还是大堆(TB级)都能应对自如
- 与G1相比,吞吐量下降不超过15%
- 方便日后在此基础上实现新的gc特性、利用colored pointers和读屏障进一步优化收集器
作者:行百里er
链接:https://juejin.im/post/6889250071427153934
java8堆内存模型_「GC系列」JVM堆内存分代模型及常见的垃圾回收器相关推荐
- 根可达算法的根_好屌好屌的「GC系列」JVM垃圾定位及垃圾回收算法浅析
0x01 什么是垃圾 很简单,没有引用指向的任何对象都叫做垃圾(garbage). 什么是garbage 在某一内存空间中,Java程序制造了很多对象被引用,有的对象还引用别的对象,中途有对象不被需要 ...
- java 查看堆外内存占用_如何监控和诊断JVM堆内和堆外内存使用?
上一讲我介绍了 JVM 内存区域的划分,总结了相关的一些概念,今天我将结合 JVM 参数.工具等方面,进一步分析 JVM 内存结构,包括外部资料相对较少的堆外部分. 今天我要问你的问题是,如何监控和诊 ...
- 内存溢出案例_「性能优化」纳尼?内存又溢出了?是时候总结一波了
写在前面 相信小伙伴们在平时工作的过程中,或多或少都会遇到一个场景:内存溢出.如果你没有遇到过这个场景,那就说明你是个假的程序员.哈哈,开个玩笑,平时工作过程中,我们确实会遇到这个问题.今天,我就将平 ...
- JVM专题之分代模型:年轻代、老年代、永久代
目录 一 什么是GC 分代 二 GC 为什么需要分代 三 GC 如何分代,每一个代具体是怎么工作的 3.1 年轻代 3.1.1 Eden Space 3.1.2 Survivor 3.2 老年代 3. ...
- mongdb 建立了索引唯一性还能重复插入?_「数据库系列」Postgres性能调优——Index...
在本文中,我们将探讨如何通过使用Explain和Analyze来分析慢查询,以及使用索引来修改和增强查询时间来解决慢查询. Postgres支持在表上使用各种索引,以加快查询速度. 多列索引 多列B树 ...
- flask中文文档_「Flask系列」 初识Flask
引子 作者有多年的编程打杂经验,之前一直参与基于Java的各种项目以及产品规划与设计,后因自己创业维持一家小公司,有些项目与产品,想降低开发成本,故在公司内部推行基于Python Flask的后端开发 ...
- golang 接口_「Golang系列」 深入理解Golang Empty Interface (空接口)
空接口可用于保存任何数据,它可以是一个有用的参数,因为它可以使用任何类型. 要理解空接口如何工作以及如何保存任何类型,我们首先应该理解名称背后的概念. 接口 这是Jordan Oreilli对空接口的 ...
- arcpy实现空间查询_「实战系列」GP+Roaringbitmap,亿级会员十万级标签毫秒级查询...
在大数据处理和应用场景中经常需要从亿级甚至十亿级会员中搜索出符合特定标签的会员.很多企业都会使用 HBase 或者 Hive + Hadoop 的方式,这样的方式查询效率非常慢,在标签非常多的情况下计 ...
- mysql显示表已存在_「Docker系列」 如何在Docker中部署MySQL数据库?
Docker为部署和测试应用程序和数据库提供了许多优势,这些应用程序和数据库是应用程序不可或缺的一部分,因此很值得学习如何在Docker容器中部署和运行数据库. 本文中,我们会重点关注如下重点: 为M ...
最新文章
- 别再说“我已经努力了”,你的“努力”一文不值!
- 简述人工智能的发展历程图_简述墙体彩绘机发展历程
- redis中的改操作
- structure101_使用structure101分析软件包的依赖关系
- php如何制定跳转到app原生页面,js实现界面向原生界面发消息并跳转功能
- vue如何新建一个项目(超详细哦--转)
- 【保存】java学习全套视频下载地址
- BIEE入门(一)架构
- 数值计算方法(六)——迭代法求解线性方程组
- 【Java】Java Object对象
- 不吐不快之EJB演练——开篇概述
- java 模糊查询 分页(自定义标签)
- Android复习强化笔记(二)
- pythonos文件目录方法_python12-OS模块(文件/目录方法)
- [渝粤教育] 西安交通大学 土力学 参考 资料
- 什么是UV贴图和展开?游戏建模纯干货,UV的详细解释,不懂得赶紧看过来!
- 中国首次包揽2021年国际信息学奥赛(IOI 2021)前四名
- 全国青少年编程等级考试python一级真题2020年6月(含题库答题软件账号)
- 解决typescript 提示 Object is possibly ‘null‘
- 代码优化大盘点:35 个 Java 代码优化魔鬼细节