jvm-内存模型

内存模型

方法区
存储已被虚拟机加载的类信息,运行时常量池,静态变量,编译器编译后的字节码等数据。
方法区的实现是永久代(PermGen Space,全称Permanent Generation Space),也就是在方法区实现gc分代收集。如果加载过多的类的话,可能会出现OutofMemory:PermGen space错误。

而从jdk8开始,方法区废弃了PermGen,改用了Metaspace的实现。
Metaspace(元空间)不在虚拟机内存中,而在native memory(本地内存)中,理论上可以无限使用本地机器内存,但一般会设置参数值以防占用过大内存影响到本机其他正在运行的程序,也会通过空闲比参数来释放不必要浪费的空间。

  • 原来类的静态变量(class statics)和interned Strings都被转移到了java堆区
  • 类的元数据(class meta-data)存储在metaspace中
  • 元数据空间与类加载器生命周期一致(即类加载器没死掉,元数据空间里的元数据就不会被gc 掉),元数据空间的大小看类加载器的需求


Java堆是虚拟机所管理的内存中最大的一块,被所有线程共享,唯一目的就是存放对象实例,几乎所有的对象都在这里分配内存,也是垃圾收集器管理的主要区域。
从内存分配角度看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer, TLAB),无论如何划分,都只是为了更方便的回收和分配内存。
Java堆可以分为新生代和老年代,新生代又分为Eden, From Survivor, To survivor。

虚拟机栈
存储当前线程运行方法时所需要的数据,指令,返回地址。每个方法在执行时会创建一个栈帧,每个方法在调用到执行完成的过程,就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。

本地方法栈
虚拟机栈为java方法(也就是字节码)服务,而本地方法栈为虚拟机使用到的native方法服务。

程序计数器
这是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器。
虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,任何一个时间片内一个处理器只会执行一条线程。因此为了线程切换后还能恢复执行位置,每条线程都需要有一个独立的程序计数器,各条线程计数器互不影响。我们称这类内存区域为“线程私有”的内存。

直接内存
不包含在运行时数据区内,native函数unsafe直接分配堆外内存,读写快,分配和销毁内存慢。java堆中的DirectBuffer对象的引用会对这块内存进行操作。

  • 直接内存只受本机内存影响。
  • 老年代内存满触发fullGC时才会清理DirectBuffer的废弃对象。
  • 适用于传输大对象,如传输超过了jvm内存空间大小的文件

garbage collection

下面分析下gc的对象,以及gc的方式

哪些对象被回收

引用计数法
给对象中添加一个计数器,每有一个地方引用它,计数器加1,当引用失效,计数器减1,当计数器为0时即是可以回收的对象。
弊端:如果有两个对象互相引用,计数器始终为1,这两个对象将永远不会被回收。

可达性分析
Java, C#, Lisp等主流语言中,都是通过可达性分析来判断对象是否存活的。
通过GC Roots对象作为起点,从这些起点向下搜索,搜索走过的路径称为引用链(GC Roots Tracing),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

Java语言中,可作为GC Roots的对象有:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI(即Native方法)引用的对象

在可达性分析中,判断不可达的对象并非是非死不可的,它们还需至少经历两次标记过程:如果对象在可达性分析中不可达,它将被第一次标记且进行第一次筛选(当对象没有覆盖finalize()方法,或者finalize()已被调用过,虚拟机将这种情况视为”没有必要执行“),如果这个对象被判断有必要执行finalize()方法,那么这个对象将被放进一个F-Queue队列中,由Finalizer线程去执行它的finalize()。再来下一波GC将对F-Queue中的对象进行第二次标记,如果该对象与GC Root引用链上的对象产生了关联(比如把自己(this关键字)赋值给某个某个变量或者对象的成员变量),那在第二次标记时将被移出”即将回收“的集合,否则就被回收。
言而总之,就是被判定不可达的对象可以通过调用finalize()自救一次,再没变成可达对象就会被回收。

引用
以上两种方法都是判断对象是否被引用,但我们还希望描述这样一种对象:当内存空间还足够时,则可以保留在内存中;如果内存空间在进行垃圾收集后还是很紧张,则可以抛弃这些对象。很多系统的缓存功能都符合这样的场景。
引用分类:

  • 强引用:代码中显式声明的对象,Object obj = new Object(),只要强引用对象GC Roots可达,就不会被回收
  • 软引用:描述一些还有用但并非必需的对象,被软引用关联的对象,在系统发生内存溢出异常前,这些对象会被优先回收,回收完之后还是没有足够内存,就抛出内存溢出的异常。SoftReference类可实现软引用
  • 弱引用:描述非必需的对象,被弱引用关联的对象在GC时会被直接回收。WeakReference类可实现软引用
  • 虚引用:也称幽灵引用或幻影引用,是最弱的一种关系,一个对象的虚引用不会对其生存时间构成影响,也无法通过虚引用获取对象实例。唯一目的就是能在这个对象被回收时收到一个系统通知。PhantomReference类可实现虚引用
垃圾收集算法

标记-清除算法
标记出所有待回收的对象,标记完统一回收。
不足:标记清除效率不高;会产生不连续内存碎片,当碎片较多,程序中需要分配较大对象时,无法找到足够连续的内存就会提前触发一次gc。

复制收集算法
将内存按容量划分为相等两块,一次用一块,回收时将一边还存活的对象复制到另一边连续存放,清除原来那边内存的所有数据。
不足:内存缩小为了原来的一半,代价较高。

标记-整理算法
标记出所有待回收的对象,让所有存活对象都向一端移动,然后清理掉边界以外的内存。
这种算法一般用于老年代。上面的复制收集算法会浪费50%的空间,对于老年代这种大空间内存不合适。

分代收集算法
把Java堆分为新生代和老年代,新生代又按照8:1:1的比例分为Eden, From Survivor(S1), To Survivor(S2)。这样就可以根据各个年代的特点采用不同的回收算法。新生代发生的GC叫MinorGC,老年代发生的GC叫majorGC,老年代内存满时触发的majorGC叫fullGC

  • 大部分对象在Eden中生产,回收时先将Eden区的存活对象复制到S1,然后清空Eden区,当S1也装满了的时候,就将Eden区和S1区的存活对象复制到S2,然后清空Eden区和S1区,始终保持有一个Survivor区是空的,如此往复
  • 大对象直接进入老年代,这样可以避免占用空间太大提前触发垃圾收集
  • 长期存活的对象将进入老年代,虚拟机给每个对象定义一个年龄计数器,对象每次移动到Survivor中(即一次Minor GC)年龄就加1岁,默认加到15岁就会被晋升到老年代中,年龄阈值可以自行设定
  • 如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,大于等于该年龄的对象就可以直接进入老年代,无需到指定年龄阈值
  • 在发生minorGC之前,虚拟机会先检查老年代最大可用连续空间是否大于新生代所有对象空间,如果条件成立,那么minorGC是确保安全的。
    如果不成立,则判断虚拟机是否允许担保失败,
    如果允许,继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,
    如果大于,将尝试进行一次MinorGC,
    如果小于,或者虚拟机不允许担保失败,那这时将进行一次fullGC
垃圾收集器

收集器间有连线,表明两者可以搭配使用
因为CMS是用户线程和垃圾收集线程交替工作,当用户线程产生垃圾比回收垃圾快时,Serial Old作为CMS失败的后备方案
红色虚线:JDK8将 Serial+CMS 和 ParNew+Serial Old 这两种组合声明废弃,并在JDK9中完全移除
绿色虚线:JDK14中弃用 Parallel Scavenge+Serial Old 组合
黄色虚线:JDK9中将CMS标记为deprecated,JDK14中删除 CMS

参考文档

Serial收集器

单线程串行收集器,收集垃圾时必须暂停其他所有工作线程,直到收集结束。
采取复制收集算法。
适用于Cient环境下新生代收集器,没有线程交互开销,专心垃圾收集的效率很高。

Parnew收集器

serial收集器的多线程版本。
适用于Server模式下新生代收集器,多线程条件下垃圾收集。

Parallel Scavenge收集器

与Parnew收集器差不多,它的关注点与其他收集器不同,它的目的是达到一个可控制的吞吐量(吞吐量=运行用户代码时间/(运行用户代码时间 + 垃圾收集时间))。
parallel scavenge收集器提供两个参数精准控制吞吐量,控制最大垃圾收集停顿时间-XX:MaxGCPauseMillis 和 设置吞吐量大小的-XX:GCTimeRatio。还有一个开关参数-XX:+UserAdaptiveSizePolicy,打开后虚拟机会根据当前系统的运行情况自动调整 新生代大小-Xnm、Eden与Survivor区的比例-XX:SurvivorRatio、晋升老年代对象年龄-XX:PretenureSizeTreshold等细节参数,以提供最合适的停顿时间或者最大吞吐量。

Serial Old收集器

Serial收集器的老年代版本,采取标记整理算法。

Parallel Old收集器

Parallel scavenge收集器的老年代版本,采取标记整理算法。

CMS收集器

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间(低延迟)为目标的收集器。
让垃圾收集线程与用户线程同时工作,有良好的响应速度,适合与用户交互的程序。
为确保用户线程在垃圾回收过程中有足够的内存可用:当堆内存到达某一阈值时,便开始垃圾回收;

在这里插入图片描述

CMS收集器基于标记-清除算法实现,但过程相对前几种收集器复杂,步骤包括:

  • 初始标记:标记GC Roots能直接关联到的对象,STW时间极短
  • 并发标记:从GC Roots直接关联的对象开始,遍历整个对象图,标记可达对象,耗时较长,但与用户线程并发执行
  • 重新标记:修正并发标记阶段期间因用户程序继续运作导致的产生变动的对象的那部分标记记录,因为只是修正所以STW时间短
  • 并发清除
    清理掉标记阶段判断已死亡的对象。耗时最长,并发标记和并发清除可以和用户线程一起并行执行,产生的停顿更少。

不足:

  • 和用户线程一起并发执行回收时,会占用CPU资源,导致应用程序变慢,吞吐量降低;
  • 并发清除时还有用户线程在执行,这段时间产生的垃圾(浮动垃圾)CMS就无法在当次收集处理掉,只好留待下一次GC,所以并发清除时要给用户线程留出空间,如果留的空间不够用就会产生“ConcurrentModeFailure”,此时虚拟机将启用后备方案SerialOldGC,停顿时间就会变长了;
  • 用的标记清除算法会产生许多内存碎片,导致大对象分配时会提前触发FULL GC。
G1收集器

评价:

  • G1收集器追求于低停顿,主要针对配备多核CPU即大容量内存的机器,以极高概率满足GC停顿时间的同时,还兼具高吞吐量,是一款全功能的垃圾收集器。
  • 小内存应用上CMS的表现大概率会优于G1,而G1在大内存应用上较有优势。在6-8G的内存下,G1和CMS性能相差不大。

把Java堆分为多个Region,使用不同Region来表示Eden、幸存者区、老年代、Humongous(大对象)等。
新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合,通过Region的动态分配实现逻辑上的连续。
根据各个Region的价值大小(回收所获得的空间大小和回收所需时间的经验值),优先回收价值最大的Region(Garbage First 侧重回收垃圾量最大的Region)。

新加Humongous的原因:

  • 原来堆中的大对象默认直接分配到老年代,但如果它是个短期存活的大对象,就会对垃圾收集造成负面影响,因此G1划分Humongous区来专门存放大对象(超过0.5个Region)。
  • 如果一个H区装不下一个大对象,那么会寻找物理连续的H区来存储,没找到会触发FullGC。
  • G1大多数行为都把H区作为老年代的一部分来看待。

每个Region都有一个Remembered Set,存放Region之间的对象引用以及其他收集器中新生代与老年代之间的对象引用,使用Remembered Set来避免全堆扫描,每个Region都有一个与之对应的Remembered Set。

G1收集器的步骤与CMS很相似:

  • 初始标记:标记GC Roots能关联到的对象,而且让下一阶段用户程序能在合适的Region中创建新对象
  • 并发标记:将与收集并行的用户线程产生的对象变动记录在RememberdSetLogs里
  • 最终标记:将RememberdSetLogs的数据合并到RememberedSet中,多个Region可并行进行最终标记
  • 筛选回收:对各个Region的回收价值和成本进行排序,根据用户期望的GC停顿时间来指定回收计划
    只有并发标记能与用户线程并行执行。

相关:https://blog.csdn.net/YABAJ/article/details/89217526

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值