JVM内存划分与垃圾回收机制

JVM 内存区域主要分为线程私有区域的程序计数器、虚拟机栈、本地方法区和线程共享区域的 堆、方法区、直接内存。注:线程私有数据区域生命周期与线程相同,随着用户线程的启动/结束而创建/销毁;线程共享区域随虚拟机的启动/关闭而创建/销毁

程序计数器(线程私有):一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,是唯一一个在虚拟机中没有规定任何 OutOfMemoryError情况的区域。如果是执行java方法的话,计数器记录的是虚拟机字节码指令的地址(当前指令的地址)。如果是Native方法,则为空。

虚拟机栈(线程私有):是描述java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

本地方法栈(线程私有):说到本地方法栈就得说到本地方法(Native Method),简单来说Native Method就是一个java调用非java代码的接口。其实现体是由非java语言在外面实现的,通过标识符native可以与所有其它的java标识符连用,并且native修饰表示这些方法是有实现体的,只不过这些实现体是非java的。本地方法的作用就是java应用与java外的环境交互,例如java需要与一些底层系统如操作系统交换信息。而本地方法栈则是为执行本地方法服务(HotSpot把本地方法栈和虚拟机栈合二为一)。

堆(线程共享):堆区域是被线程共享的一块内存区域,创建的对象都保存在堆中,也是垃圾收集器进行垃圾收集的主要内存区域。

方法区(线程共享):方法区即永久代, 用于存储被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,永久代内存回收的主要目标是常量池回收和类型的卸载。

由于堆是垃圾收集器进行垃圾收集的主要内存区域,所以下面主要介绍一下堆:现代虚拟机采用垃圾分代回收算法,所以堆从GC角度还可以划分成新生代和老年代,而新生代又分为Eden、From Survivor和To Survivor。

新生代:是用来存放新生的对象。一般占据堆的1/3空间,使用的Minor GC(Minor GC 采用复制清除算法)。由于频繁创建对象,所以新生代会频繁触发Minor GC进行垃圾回收。

Eden:Java新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。

ServivorFrom:From区域是这次GC进行垃圾回收的扫描区域。

ServivorTo:To区域存储上次GC回收后的幸存者。

老年代:主要存放应用程序中生命周期长的内存对象,使用的Major GC(Major GC 采用标记清除算法)。老年代的对象比较稳定,所以Major GC不会频繁执行。在进行Major GC前一般都先进行了一次 Minor GC,因为有新生代的对象晋身入老年代,导致空间不够用时才触发。或者当无法找到足够大的连续空间分配给新创建的较大对象时也会提前触发一次Major GC 进行垃圾回收腾出空间。

垃圾回收算法的种类以及工作原理。

标记清除算法(Mark-Sweep):标记清除算法最基础的垃圾回收算法,是CMS收集器所采用的算法。分为标注和清除两个阶段。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。该算法最大的问题是内存碎片化严重,如果有大对象需要存储,可能发生找不到足够大的可利用空间。

复制清除算法(copying):复制清除算法是为了解决标记清除算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块区域。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清理。例如新生代的Minor GC采用的就是复制清除算法,当进行GC回收时,则把From中存活的对象移动到To,然后清理掉From,并把To变为From,而当前的From区域变为下一次GC的To区域。

标记整理算法(Mark-Compact):标记整理算法结合复制清除算法和标记清除算法,也是目前最前沿的G1收集器所采用的算法。标记阶段和Mark-Sweep算法相同,但标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象,这样就解决了标记清除算法所产生的内存碎片化问题。

分代收集算法:分代收集算法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法,由于新生代中每次垃圾回收都要回收大部分对象,要复制的操作比较少,所以对新生代使用复制清除算法。即将新生代划分为一块较大的Eden和两个较小的Survivor(From Space, To Space),每次使用Eden和其中一块Survivor,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor(即To)空间中;而老年代因为每次只回收少量对象,所以采用标记整理算法,当新生代GC次数达到一定次数(默认15)后仍然存活的对象,或者新生代的To无法足够存储某个对象,则将这些对象移动到老生代。

GC垃圾收集器

Serial垃圾收集器(单线程的复制清除算法):Serial是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾收集器。Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。Serial垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java虚拟机运行在Client模式下默认的新生代垃圾收集器。

ParNew 垃圾收集器(多线程的复制清除算法):ParNew垃圾收集器其实是Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和Serial收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。ParNew 收集器默认开启和CPU数目相同的线程数,可以通过-XX:ParallelGCThreads参数来限制垃圾收集器的线程数。ParNew虽然是除了多线程外和Serial收集器几乎完全一样,但是ParNew垃圾收集器是很多java虚拟机运行在Server模式下新生代的默认垃圾收集器。

Parallel Scavenge收集器(多线程的复制算法):Parallel Scavenge收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量(CPU运行用户代码的时间/CPU总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),高吞吐量可以最高效率地利用 CPU时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。自适应调节策略也是Parallel Scavenge收集器与ParNew收集器的一个重要区别。

Serial Old收集器(单线程标记整理算法):Serial Old是Serial垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,也主要是运行在Client默认的java虚拟机默认的年老代垃圾收集器。

Parallel Old 收集器(多线程标记整理算法):Parallel Old收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。在 JDK1.6 之前,新生代使用 Parallel Scavenge收集器只能搭配年老代的Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和年老代Parallel Old收集器的搭配策略。

CMS 收集器(多线程标记清除算法):Concurrent mark sweep(CMS)收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。其工作机制分为4个阶段。1:初始标记(只是标记GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程),2:并发标记(进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程),3:重新标记(为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程),4:并发清除(清除GC Roots不可达对象,和用户线程一起工作,不需要暂停工作线程。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS收集器的内存回收和用户线程是一起并发地执行)。

G1 收集器:Garbage first垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与CMS收集器,G1收集器两个最突出的改进是:1.基于标记-整理算法,不产生内存碎片。2.可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。G1收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1收集器可以在有限时间获得最高的垃圾收集效率。

JDK8中取消永久代改为元空间

JDK 1.8里永久代(Perm)内容中字符串常量移至堆内存,其他内容如类信息、字段、静态属性、方法、常量等都移动到元空间内。元空间(MetaSpace)不在堆内存上,而是直接占用的本地内存。因此元空间的大小仅受本地内存限制。可通过参数来设定元空间的大小。

-XX:MetaSpaceSize  初始元空间大小

-XX:MaxMetaSpaceSize   最大元空间大小

-XX:MinMetaspaceFreeRatio  在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集

-XX:MaxMetaspaceFreeRatio  在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

元空间的特点

1.每个加载器有专门的存储空间。

2.不会单独回收某个类。

3.元空间里的对象的位置是固定的。

4.如果发现某个加载器不再存活,会把相关的空间整个回收。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值