Java语言从出现到如今,一直占据编程语言前列,他很大的一个缘由就是因为java应用程序所运行的平台有关。咱们你们都知道java应用程序运行在java虚拟机上。这样就大大减小了java应用程序和底层操做系统打交道的频率。这也就为java程序的跨平台提供了良好的基础。在java虚拟机中为咱们提供了一个很重要的机制就是java虚拟机的自动的内存管理机制。也就是咱们平时所说的垃圾回收机制,这使得开发人员不用本身来管理应用中的内存。C/C++开发人员须要经过malloc/free 和new/delete等函数来显式的分配和释放内存。这对开发人员提出了比较高的要求,容易形成内存访问错误和内存泄露等问题。今天咱们就一块儿来看一下java虚拟机给咱们提供的这个强大的功能——自动垃圾回收机制。java
咱们在c/c++的程序中,他们没有java中的自动垃圾回收机制,这就须要开发人员手动的去分配和释放内存,这样就要求咱们的开发人员要有必定的细心和对内存管理的经验。若是内存管理很差,很容易产生最多见的两个问题。一是“悬挂引用”,二是内存溢出。所为的悬挂引用就是一个对象引用所指向的内存区块已经被错误的回收并从新分配给新的对象了,程序若是继续使用这个引用的话会形成不可预期的结果。第二个内存溢出就很好理解了,开发人员在作开发的过程当中,只显示的申请内存而忘记用完释放掉内存,这样长时间会致使内存溢出的状况。而像java这种具备自动管理内存机制的语言来讲,咱们开发人员只需考虑引用的运用就能够,把内存管理这块交给咱们的语言运行环境来管理。。开发人员并不须要关心内存的分配和回收的底层细节。Java平台经过垃圾回收器来进行自动的内存管理。这样就大大减小了开发人员的工做量c++
1、Java垃圾回收机制算法
Java 的垃圾回收器要负责完成3 件任务:编程
1.分配内存缓存
2.确保被引用的对象的内存不被错误回收服务器
3.回收再也不被引用的对象的内存空间。多线程
垃圾回收是一个复杂并且耗时的操做。若是JVM 花费过多的时间在垃圾回收上,则势必会影响应用的运行性能。通常状况下,当垃圾回收器在进行回收操做的时候,整个应用的执行是被暂时停止(stop-the-world)的。这是由于垃圾回收器须要更新应用中全部对象引用的实际内存地址。不一样的硬件平台所能支持的垃圾回收方式也不一样。好比在多CPU 的平台上,就能够经过并行的方式来回收垃圾。而单CPU 平台则只能串行进行。不一样的应用所指望的垃圾回收方式也会有所不一样。服务器端应用可能但愿在应用的整个运行时间中,花在垃圾回收上的时间总数越小越好。而对于与用户交互的应用来讲,则可能但愿所垃圾回收所带来的应用停顿的时间间隔越小越好。对于这种状况,JVM 中提供了多种垃圾回收方法以及对应的性能调优参数,应用能够根据须要来进行定制。并发
2、判断对象是否该被回收算法框架
1.引用计数算法编程语言
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1;任什么时候刻计数器值都为0时对象就表示它不可能被使用了。这个算法实现简单,但很难解决对象之间循环引用的问题,所以Java并无用这种算法!这是不少人都误解了的地方。
2.根搜索算法
经过一系列名为“GC ROOT”的对象做为起始点,从这些结点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOT没有任何引用链相连时,则证实这个对象是不可用的。若是对象在进行根搜索后发现没有与GC ROOT相链接的引用链,则会被第一次第标记,并看此对象是否须要执行finalize()方法(忘记finalize()这个方法吧,它能够被try-finally或其余方式代替的),当第二次被标记时,对象就会被回收。
3、Java虚拟机基本垃圾回收算法:
1.标记-清除(Mark-Sweep)
此算法执行分两阶段。第一阶段从引用根节点开始标记全部被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。它中止全部工做,收集器从根开始访问每个活跃的节点,标记它所访问的每个节点。走过全部引用后,收集就完成了,而后就对堆进行清除(即对堆中的每个对象进行检查),全部没有标记的对象都做为垃圾回收并返回空闲列表。下图 展现了垃圾收集以前的堆,阴影块是垃圾,由于用户程序不能到达它们:
可到达和不可到达的对象
标记-清除实现起来很简单,能够容易地回收循环的结构,而且不像引用计数那样增长编译器或者赋值函数的负担。可是它也有不足―― 收集暂停可能会很长,在清除阶段整个堆都是可访问的,这对于可能有页面交换的堆的虚拟内存系统有很是负面的性能影响。
标记-清除的最大问题是,每个活跃的(即已分配的)对象,无论是否是可到达的,在清除阶段都是能够访问的。由于不少对象均可能成为垃圾,这意思着收集器花费大量精力去检查并处理垃圾。标记-清除收集器还容易使堆产生碎片,这会产生区域性问题并能够形成分配失败,即便看来有足够的自由内存可用。此算法须要暂停整个应用,同时,会产生内存碎片。
2.复制(Copying)
此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另一个区域中。次算法每次只处理正在使用中的对象,所以复制成本比较小,同时复制过去之后还能进行相应的内存整理,不过出现“碎片”问题。固然,此算法的缺点也是很明显的,就是须要两倍内存空间。
3.标记-整理(Mark-Compact)
此算法结合了“标记-清除”和“复制”两个算法的优势。也是分两阶段,第一阶段从根节点开始标记全部被引用对象,第二阶段遍历整个堆,把清除未标记对象而且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题。
4.增量收集(Incremental Collecting)
实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么缘由JDK5.0中的收集器没有使用这种算法的。
5.分代(Generational Collecting)
将堆分红新生代(Eden, From Survivor, To Survivor)和老年代,在新生代中使用复制算法,即Minor-GC,当一些对象通过屡次的Minor-GC后还留在新生代,则会被搬移到老年代中。而老年代中使用标记-清理或标记-整理算法,即Major GC/Full GC。
-XX:PretenurseSizeThreshold=1024,则大于次参数的对象会直接分配到老年代(尽量不要写一些“短命大对象”!)
-XX:MaxTenuringThreshold=15,在survivor空间存活15次以后,则会搬移到老年代
若是是Survivor空间中相同年龄全部对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就能够直接进入老年代。
进行Minor GC时,虚拟机会检测以前每次晋升到老年代的平均大小是否大于老年代的剩余空间大小,若是大于,则直接进行一次Full GC。
在对垃圾收集算法进行评价时,咱们可能要考虑如下全部标准:
1)暂停时间。收集器是否中止全部工做来进行垃圾收集?要中止多长时间?暂停是否有时间限制?
2)暂停的可预测性。垃圾收集暂停是否规划为在用户程序方便而不是垃圾收集器方便的时间发生?
3)CPU 占用。总的可用 CPU 时间用在垃圾收集上的百分比是多少?
4)内存大小。许多垃圾收集算法须要将堆分割成独立的内存空间,其中一些空间在某些时刻对用户程序是不可访问的。这意味着堆的实际大小可能比用户程序的最大堆驻留空间要大几倍。
5)虚拟内存交互。在具备有限物理内存的系统上,一个完整的垃圾收集在垃圾收集过程当中可能会错误地将很是驻页面放到内存中来进行检查。由于页面错误的成本很高,因此垃圾收集器正确管理引用的区域性 (locality) 是很必要的。
6)缓存交互。即便在整个堆能够放到主内存中的系统上 ―― 实际上几乎全部 Java 应用程序均可以作到这一点,垃圾收集也经常会有将用户程序使用的数据冲出缓存的效果,从而影响用户程序的性能。
7)对程序区域性的影响。虽然一些人认为垃圾收集器的工做只是收回不可到达的内存,可是其余人认为垃圾收集器还应该尽可能改进用户程序的引用区域性。整理收集器和复制收集器在收集过程当中从新安排对象,这有可能改进区域性。
8)编译器和运行时影响。一些垃圾收集算法要求编译器或者运行时环境的重要配合,如当进行指针分配时更新引用计数。这增长了编译器的工做,由于它必须生成这些簿记指令,同时增长了运行时环境的开销,由于它必须执行这些额外的指令。这些要求对性能有什么影响呢?它是否会干扰编译时优化呢?
无论选择什么算法,硬件和软件的发展使垃圾收集更具备实用性。20 纪70 和80 年代的经验研究代表,对于大型Lisp程序,垃圾收集消耗25%到40%的运行时。垃圾收集还不能作到彻底不可见,这确定还有很长的路要走。
四、三种垃圾回收器
目前的收集器主要有三种:串行收集器、并行收集器、并发收集器。
1.串行收集器
使用单线程处理全部垃圾回收工做,由于无需多线程交互,因此效率比较高。可是,也没法使用多处理器的优点,因此此收集器适合单处理器机器。固然,此收集器也能够用在小数据量(100M左右)状况下的多处理器机器上。可使用-XX:+UseSerialGC打开。
2.并行收集器
1)对年轻代进行并行垃圾回收,所以能够减小垃圾回收时间。通常在多线程多处理器机器上使用。使用-XX:+UseParallelGC.打开。并行收集器在J2SE5.0第六6更新上引入,在Java SE6.0中进行了加强--能够堆年老代进行并行收集。若是年老代不使用并发收集的话,是使用单线程进行垃圾回收,所以会制约扩展能力。使用-XX:+UseParallelOldGC打开。
2)使用-XX:ParallelGCThreads=<N>设置并行垃圾回收的线程数。此值能够设置与机器处理器数量相等。
3)此收集器能够进行以下配置:
最大垃圾回收暂停:指定垃圾回收时的最长暂停时间,经过-XX:MaxGCPauseMillis=<N>指定。<N>为毫秒.若是指定了此值的话,堆大小和垃圾回收相关参数会进行调整以达到指定值。设定此值可能会减小应用的吞吐量。
吞吐量:吞吐量为垃圾回收时间与非垃圾回收时间的比值,经过-XX:GCTimeRatio=<N>来设定,公式为1/(1+N)。例如,-XX:GCTimeRatio=19时,表示5%的时间用于垃圾回收。默认状况为99,即1%的时间用于垃圾回收。
3.并发收集器
能够保证大部分工做都并发进行(应用不中止),垃圾回收只暂停不多的时间,此收集器适合对响应时间要求比较高的中、大规模应用。使用-XX:+UseConcMarkSweepGC打开。
1)并发收集器主要减小年老代的暂停时间,他在应用不中止的状况下使用独立的垃圾回收线程,跟踪可达对象。在每一个年老代垃圾回收周期中,在收集初期并发收集器会对整个应用进行简短的暂停,在收集中还会再暂停一次。第二次暂停会比第一次稍长,在此过程当中多个线程同时进行垃圾回收工做。
2)并发收集器使用处理器换来短暂的停顿时间。在一个N个处理器的系统上,并发收集部分使用K/N个可用处理器进行回收,通常状况下1<=K<=N/4。
3)在只有一个处理器的主机上使用并发收集器,设置为incremental mode模式也可得到较短的停顿时间。
4)浮动垃圾:因为在应用运行的同时进行垃圾回收,因此有些垃圾可能在垃圾回收进行完成时产生,这样就形成了“Floating Garbage”,这些垃圾须要在下次垃圾回收周期时才能回收掉。因此,并发收集器通常须要20%的预留空间用于这些浮动垃圾。
5)Concurrent Mode Failure:并发收集器在应用运行时进行收集,因此须要保证堆在垃圾回收的这段时间有足够的空间供程序使用,不然,垃圾回收还未完成,堆空间先满了。这种状况下将会发生“并发模式失败”,此时整个应用将会暂停,进行垃圾回收。
6)启动并发收集器:由于并发收集在应用运行时进行收集,因此必须保证收集完成以前有足够的内存空间供程序使用,不然会出现“Concurrent Mode Failure”。经过设置-XX:CMSInitiatingOccupancyFraction=<N>指定还有多少剩余堆时开始执行并发收集。
5、关于垃圾收集的几点补充
通过上述的说明,能够发现垃圾回收有如下的几个特色:
1)垃圾收集发生的不可预知性:因为实现了不一样的垃圾收集算法和采用了不一样的收集机制,因此它有多是定时发生,有多是当出现系统空闲CPU资源时发生,也有多是和原始的垃圾收集同样,等到内存消耗出现极限时发生,这与垃圾收集器的选择和具体的设置都有关系。
2)垃圾收集的精确性:主要包括2 个方面:
(a)垃圾收集器可以精确标记活着的对象;
(b)垃圾收集器可以精确地定位对象之间的引用关系。前者是彻底地回收全部废弃对象的前提,不然就可能形成内存泄漏。然后者则是实现归并和复制等算法的必要条件。全部不可达对象都可以可靠地获得回收,全部对象都可以从新分配,容许对象的复制和对象内存的缩并,这样就有效地防止内存的支离破碎。
3)如今有许多种不一样的垃圾收集器,每种有其算法且其表现各异,既有当垃圾收集开始时就中止应用程序的运行,又有当垃圾收集开始时也容许应用程序的线程运行,还有在同一时间垃圾收集多线程运行。
4)垃圾收集的实现和具体的JVM 以及JVM的内存模型有很是紧密的关系。不一样的JVM 可能采用不一样的垃圾收集,而JVM 的内存模型决定着该JVM能够采用哪些类型垃圾收集。如今,HotSpot 系列JVM中的内存系统都采用先进的面向对象的框架设计,这使得该系列JVM均可以采用最早进的垃圾收集。
5)随着技术的发展,现代垃圾收集技术提供许多可选的垃圾收集器,并且在配置每种收集器的时候又能够设置不一样的参数,这就使得根据不一样的应用环境得到最优的应用性能成为可能。