垃圾收集及JVM内存分配

          程序计数器,虚拟机栈,本地方法栈(栈只是众多数据结构中的一种而已,并不是计算机内存物理上存在的一个地方,是应程序要求建立的一种有数据结构性质的内存)三个区域随线程而生,随线程而灭。因此这几个区域的内存分配和回收都具备确定性,在这几个区域内就不需要过多考虑回收的问题。因为方法结束或线程结束时,内存自然就跟随着回收了。java堆和方法区则不一样,这部分内存的分配和回收都是动态的,垃圾收集器关注的是这部分内存。

          回收堆区:

          堆里面存放着所有对象实例,垃圾收集器对堆进行回收时,第一件事就是确定这些对象哪些还活着,哪些已经死去。

          很多资料说判断对象是否已死是用引用计数算法来判断的,这么推算两个对象互相引用时不会被回收,其实不是。两个互相循环引用的对象会被回收。判断对象是否已死的方法是可达性分析算法

          通过一系列称为GC Roots的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有引用链相连时,此对象不可用。

          可作为GC Roots的对象:虚拟机栈中引用的对象,方法区类静态属性引用的对象,方法区常量引用的对象,本地方法栈中引用的对象。

          可达性分析算法中不可达的对象最终会被调用finalize()方法来释放内存资源。

          判断对象是否存活与引用(存放着另一块内存地址的内存)有关因此即使一个对象到GC Root有引用链也可能被回收,具体要看引用强度。

          我们希望能描述这样一类对象:当内存足够时,这些对象能保存在内存中。当垃圾回收过后内存还是紧张,则可以抛弃这些对象由此把引用的概念进行了扩充,引用分为四种:强引用,软引用,弱引用,虚引用,引用强度依次减弱。垃圾回收器不会回收强引用引用的对象。对于软引用关联的对象,系统将要发生内存溢出异常前,虚拟机才会把这些软引用关联的对象列进回收范围进行回收,如果此时回收后还没有足够内存,则抛出内存溢出异常。

          被弱引用关联的对象,无论当前内存是否充足,都只能生存到下一次垃圾收集前。

          给对象设置虚引用关联的唯一目的是此对象被收集时可收到系统通知。无法通过虚引用来获取一个实例。虚引用对对象的生存时间也没影响。

          也就是说,强引用一定不会回收,虚引用的设置只是为了让对象在被收集时可收到系统通知,软引用在垃圾回收前不回收,垃圾回收后内存不够要发生内存溢出时才回收,弱引用只能生存到下一次垃圾收集前。

          

          垃圾收集算法:标记清除算法,复制算法,标记整理算法,分代收集算法(其实只是各个算法的综合运用)。

          标记清除算法是最基础的收集算法,简单直接:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。标记清除算法的不足是(零散性问题):1标记和清除的对象由于在内存分布上的不连续性使得两个过程效率低  2标记清除后产生大量不连续的内存碎片,分配较大的对象时可能找不到足够的连续内存再一次触发垃圾收集。

标记整理算法是标记清除算法的改良版:先标记出存活对象,让所有存活对象向内存的一端移动,移动后对另一端进行清理。

  

      复制算法:标记出内存中存活的对象(此内存使用完时),把这些对象复制到另一块内存上,然后把之前使用过的内存一次清理掉。现在的商业虚拟机都采用复制算法来回收新生代。

        

          

      

现在商业虚拟机的垃圾收集都采用分代收集策略,即把java堆分为新生代和老年代。把新生对象存在新生代(的eden区和一个from的survivor区中),新生对象中占内存大的对象直接存在老年代。垃圾回收时对新生代使用复制算法,将新生代中eden区和from的survivor区存活的对象存到另一个to的survivor区中,再对eden区和from的survivor区进行清理。如果复制对象to的survivor空间不足以支持eden和from的survivor中存活的新生代的对象,则用老年代进行分配担保将存活对象直接存入老年代。HotSopt虚拟机中新生代eden和survivor的大小比例默认为8:1。

(新生代老年代指的是空间)对新生代进行垃圾收集时有大批对象死去,只有少量对象存活,因此对新生代收集采用复制算法,只需要付出少量对象的复制成本就可以完成收集。老年代中对象存活率高,且没有额外空间对它进行分配担保,因此对老年代的回收采用标记清除或标记整理来收集。虚拟机给每个对象设置了一个年龄计算器,年龄增加到一定临界值会晋升到老年代,该临界值由-XX:MaxTenuringThreshoid设置。新生的对象第一次垃圾回收后被复制到to的survivor区中年龄为1,以后每经历一次垃圾回收年龄加1.

          新生代GC/minorGC/GC:指发生在新生代的垃圾收集。因为新生代对象朝生夕灭,所以minorGC发生频繁,回收速度也快。

         老年代GC/majorGC/fullGC:指发生在堆区(年轻代,老年代)和非堆区(永久代/方法区)的垃圾收集,majorGC的速度比minorGC的速度慢十倍以上。

         -Xms500m(JVM初始分配的堆内存)         

         -Xmx500m(JVM允许分配的最大堆内存)  

         -Xmn150m(JVM堆中分给新生代的空间大小)    最大堆内存减去新生代的大小便是老年代的大小   

         -XX:PermSize=500m(JVM初始分配的非堆内存)         

         -XX:MaxPermSize=500m(JVM允许分配的最大的非堆内存)

         -XX:+PrintGCDetails(打印垃圾回收信息)

         -Xloggc:D:(打印垃圾回收信息到D盘下。。)

        这些jvm启动参数可以通过eclipse设置,如果服务器上没有eclipse,可以直接去应用服务器的配置文件中设置(先将应用部署后再查看fullGC的情况),如果是非web应用,可以直接在应用的启动脚本中设置。

 [GC [PSYoungGen: 14825K->64K(124672K)] 90325K->78313K(397760K), 0.0109606 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

 [Full GC (System) [PSYoungGen: 64K->0K(124672K)] [PSOldGen: 78249K->51187K(273088K)] 78313K->51187K(397760K) [PSPermGen: 130343K->129024K(223232K)], 0.3097390 secs] [Times: user=0.31 sys=0.00, real=0.31 secs]

          JVM内存分配设置:

         堆空间:3-4倍fullGC后老年代空间占用量(新生代1-1.5,老年代2-3)

         非堆区(永久代):1.2-1.5倍fullGC后永久代空间占用量

        

          GC对程序的影响:

          GC时需要对不同状态的对象进行标记,GC时程序会停止运行。所谓的并行GC器只是感官上并行,实际还是需要将程序停止。

【中括号后面紧跟的内容表示垃圾回收前后堆空间的变化】

       

          回收方法区:

          对于永久代(方法区)的垃圾收集主要回收两部分内容:废弃常量和无用的类。

          常量:例如字符串‘adc’存在于常量池,但当前系统没有一个Sting对象叫“abc”或没有一个String对象引用“adc”,则'adc'将被清理,常量池中其他类,方法,字段的符号引用也是如此。

          无用的类:判断一个类是否是无用的类有三个条件:1.该类所有实例已被回收,java堆中不存在该类的任何实例。

                                                                          2.加载该类的ClassLoader已被回收。

                                                                          3.该类对应的Class对象没有被引用,无法通过反射访问该类的方法。

Minor GC ,Full GC 触发条件

Minor GC触发条件:当Eden区满时,触发Minor GC。

Full GC触发条件:

(1)调用System.gc时,系统建议执行Full GC,但是不必然执行

(2)老年代空间不足

(3)方法去空间不足

(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存

(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

https://www.zhihu.com/question/41922036/answer/93079526

作者:RednaxelaFX
链接:https://www.zhihu.com/question/41922036/answer/93079526
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

针对HotSpot VM的实现,它里面的GC其实准确分类只有两大种:
  • Partial GC:并不收集整个GC堆的模式
    • Young GC:只收集young gen的GC
    • Old GC:只收集old gen的GC。只有CMS的concurrent collection是这个模式
    • Mixed GC:收集整个young gen以及部分old gen的GC。只有G1有这个模式
  • Full GC:收集整个堆,包括young gen、old gen、perm gen(如果存在的话)等所有部分的模式。

Major GC通常是跟full GC是等价的,收集整个GC堆。但因为HotSpot VM发展了这么多年,外界对各种名词的解读已经完全混乱了,当有人说“major GC”的时候一定要问清楚他想要指的是上面的full GC还是old GC。

最简单的分代式GC策略,按HotSpot VM的serial GC的实现来看,触发条件是:
  • young GC:当young gen中的eden区分配满的时候触发。注意young GC中有部分存活对象会晋升到old gen,所以young GC后old gen的占用量通常会有所升高。
  • full GC:当准备要触发一次young GC时,如果发现统计数据说之前young GC的平均晋升大小比目前old gen剩余的空间大,则不会触发young GC而是转为触发full GC(因为HotSpot VM的GC里,除了CMS的concurrent collection之外,其它能收集old gen的GC都会同时收集整个GC堆,包括young gen,所以不需要事先触发一次单独的young GC);或者,如果有perm gen的话,要在perm gen分配空间但已经没有足够空间时,也要触发一次full GC;或者System.gc()、heap dump带GC,默认也是触发full GC。

HotSpot VM里其它非并发GC的触发条件复杂一些,不过大致的原理与上面说的其实一样。
当然也总有例外。Parallel Scavenge(-XX:+UseParallelGC)框架下,默认是在要触发full GC前先执行一次young GC,并且两次GC之间能让应用程序稍微运行一小下,以期降低full GC的暂停时间(因为young GC会尽量清理了young gen的死对象,减少了full GC的工作量)。控制这个行为的VM参数是-XX:+ScavengeBeforeFullGC。这是HotSpot VM里的奇葩嗯。可跳传送门围观:JVM full GC的奇怪现象,求解惑? - RednaxelaFX 的回答

并发GC的触发条件就不太一样。以CMS GC为例,它主要是定时去检查old gen的使用量,当使用量超过了触发比例就会启动一次CMS GC,对old gen做并发收集。
新生的小心情
原文地址:https://www.cnblogs.com/jianmianruxin/p/6942235.html