Java中的内存分配与垃圾回收

一、内存分配    

Java程序运行时的内存分配,按照JVM规范,包括以下几个区域:程序计数器、虚拟机栈、本地方法栈、方法区、堆。其中,前三个是线程私有的,与线程生命周期相同,线程退出内存自动回收;后两者是所有线程共享内存的,只在垃圾回收机制被触发时,被动回收。

* 程序计数器,内存区域极小,是当前线程的字节码执行行号指示器;

* 虚拟机栈、本地方法栈,即平时所说的“栈”,是虚拟机用来执行方法(包括Java、非Java方法)时,使用的临时内存空间,用来存储当前方法、局部变量等,全部基本类型变量,以及类对象的引用都存储在栈中;

* 方法区,全局共享区域,用来存储已经被虚拟机加载的Class信息、常量(如字符串字面常量)、静态变量,以及编译器编译后的代码等;

* 堆,是Java虚拟机管理中最大的一块内存,为所有线程所共享,用来存储所有Java类实例。需要注意的是,实例数据在堆中开辟内存,而对象的引用相当于指针,存储在各线程的栈中。

虚拟机中的堆,按照分代的概念,分为新生代和老生代,hotspot中将新生代又分为eden、survivor1、survivor2三块区域。在默认情况下,虚拟机为一个新诞生的对象分配内存,是在eden区域,对于特别大的对象,例如通常是超过3MB(由虚拟机参数调控)的大对象,则直接分配在老生代,以避免在young gc时的大量内存复制。当经过一次young gc,eden区和survivor区中仍然存活的对象,会复制到另一个作为担保的survivor区中,当survivor区中的存活对象的年龄,超过某个由虚拟机参数调控的上限时,survivor中的存活对象会升级,并迁往老年区。如果此时老年区的空间不足,则会触发一次老年区的full gc。

二、垃圾回收算法

垃圾回收需要解决的三个问题:

1)哪些内存可回收。上面各内存区域中,只有方法区、堆是全局共享的内存块,需要垃圾回收来处理,回收内存。确定某个对象是否可回收,需要通过“可达性分析算法”来判断,哪些内存块是孤立无源、不可达的对象或对象集合。 

2)什么时间能执行回收。执行垃圾回收时,需要完全地“冻结”现场,达到所谓“stop the world”,以避免在GC的过程中,引用关系发生变化,而引发问题。虚拟机的做法并非立刻强制中断线程,而是让所有用户线程主动中断。GC线程设置一个标识,让其它所有线程在到达其下一个“安全点”时,根据此标识而主动挂起线程,等待GC线程的操作;

3)如何执行垃圾回收。垃圾回收算法有很多,但总体策略是分代回收,针对新生代、老生代特点,采取的GC算法是不同的。

* young gc/minor gc。对于新生代内存块,对象存活率低,需要频繁清理,为了避免“标记-清除”算法的碎片化问题,也为了提高效率,采取“复制”算法,即将新生代(young区)分为eden+survivor1+survivor2几块空间,在进行GC时,将eden/survivor1中的存活对象,复制到survivor2空间中暂存,然后整块清理掉前面的eden+survivor1区域,回收这块通常占比90%的内存区域。

* major gc/full gc。对于老生代内存块,对象存活率高,不需要进行频繁的存活性判断的扫瞄,当进行GC时,通常使用“标记-整理”算法:标记可回收区域、移动集中存活对象内存、最终对剩余的垃圾区域进行回收。

三、垃圾收集器实现

GC线程在工作时,会完全或部分中断用户线程,影响系统吞吐率,使用户任务出现停顿甚至卡死。新生代的收集器有Serial/ParNew/Parallel Scavenge,老生代的收集器有CMS/Serial Old/Parallel Old。

* Serial/Serial Old是单线程收集器,在进行GC时,会中断所有用户线程,在新、老生代分别采用复制算法和标记-整理算法进行垃圾清理;

* ParNew是多个GC线程并行,但同样会中断所有用户线程,算法层面和Serial系列一致;

* Parallel收集器,同样也是多线程并行的回收器,不同的是其关注的是系统的吞吐率的指标,即用户任务的停顿时间比例,而不是停顿的绝对时间。

* CMS(Concurrent Mark Sweep)收集器,目的是追求最短停顿时间,目前流行最为广泛的收集器实现,用来回收老生代内存。其回收过程,可分为initial mark/concurrent mark/remark/concurrent sweep四个步骤。其中,initial mark仅标记一下GC Roots能直接关联到的对象,concurrent mark是多GC线程同时与用户线程进行并发,进行GC Roots Traceing,实质是存活对象的联通域生长与标记,remark阶段对concurrent mark阶段用户线程并发执行时的对象引用变动,进行修正,concurrent sweep是并发地对垃圾进行清理。第1、3步,所有用户线程依然会中断,但因为其操作十分简单,所以速度很快,第2、4步,耗时较长,但是与用户线程并发进行的,因此整体来看,CMS GC的用户任务停顿时间极短。

原文地址:https://www.cnblogs.com/jacksonshi/p/5840952.html