一:新生代
新生代就是用来存放那个那些生命周期很短(创建和使用完立马被回收)的对象,
例如:
什么情况下会触发新生代的垃圾回收
比较常见的情形就是创建的N多对象后,导致Java堆内存中囤积了大量的对象,这是对象都是之前有人引用,但是在栈针出栈后就没人引用,这时候,新生代预先分配的空间被大量的垃圾对象占用,此时,需要在新生代给新的对象分配空间时,就会触发一下新生代的垃圾回收。叫做Minor Gc 也叫Young Gc
哪些对象是不能被回收的
Jvm使用可达性分析法来判断一个对象是否可以被回收,这个算法采用的是分析每个对象都有在引用他,然后向上去判断。看是否有一个GC Roots
示例1:局部变量作为一个GCRoots
示例2:静态变量作为一个GCRoots
java对象不同的引用类型:
强引用
软引用
弱引用
新生代的垃圾回收算法
复制算法
所谓的复制算法,就是将内存区域分为两部分,然后只使用其中的一块内存,待那块内存快满时,就将内存中的对象专业到另外一块区域,保证没有内存碎片,接着一次性回收原来那块内存中的垃圾对象,再腾出一块新的内存区域,两块内存重复着循环使用
例如:
在“loadReplicasFromDisk()”方法中创建了对象,此时对象就会分配在新生代的一块内存区域,而且是有main线程的占内存中的“loadReplicasFromDisk()”方法的栈针内的局部变量引用,如图:
假设与此同时,代码在不同的的运行,大量的对象都会分配在新生代的一块内存区域,而且分配过后,很快就是去了局部静态变量或者静态变量的引用,成为垃圾对象,如图:
接着这个时候,新生代内存那块被分配对象的区域快满了,再次要分配的对象的时候,发现内存不足了,就会触发一次MinorGC去回收新生代那块被使用的内存空间里的垃圾对象。
如果是直接标记处哪些对象可以被删除,然后直接对那块内存区域中的对象进行垃圾回收,这样会导致大量的内存碎片。进而导致内存浪费
如下图(红色标记的就为内存碎片)
合理的是,并不是直接按照上述思路直接对已使用的那块内存对象把垃圾回收掉,然后只保留存活的对象,而是先对那块在使用的内存里标记出哪些对象是不能被回收的,然后把哪些存活的对象移动另外一个区域,如图:
然后由被转移的内存空间继续承接新对象的空间分配
最后在一次性把原来使用的内存区域里的垃圾对象一扫而空,全部回收掉新的内存区域:
复制算法的缺点:只有一半内存可以正常使用,这样对内存的使用效率太低。
真正的复制算法
把新生代内存分为三块。1个Eden区,两个Survivor区 ,其中Eden区占80%的内存空间,每一个Survivor区占10%的内存空间,比如如Eden去有800M,每一个Survivor区就100M内存,如图:
平时使用的就一个Eden区和一个Survivor区,相当于有900M内存可以使用,去下图:
刚开始所有对象都分配在eden区,当Eden区快满了就是触发一次Minor GC,此时就把Eden区的对象一次性转移到另外一块空这个Survivor区,接着Eden区就会被清空,然后在次分配到Eden区,就会出现如上图所示,Eden区和一块Survivor区里有对象,其中Survivor区里放着上次MinorGC后的存活对象。
如果下次Eden区再满了,那么就会再次触发一次Minor GC,接着就会把Eden区和上次Minor GC后存活的对象的Survivor区内的对象转移到另外一块空着的Survivor区,,如果空着的Survivor没办法存放Minor GC后剩余的对象,这时候就必须把这些对象直接转移到老年代中去。
如图:
接着新对象继续分配在Eden区和另外一块被使用的Survivor区,然后使用有一块Survivor区是空着的,就这样循环使用着三块内存区域。
新生代向老年代转移
1:15次GC之后进入老年代
2:动态对象年龄判断
大致的规则就是加入当前对象放在Survivor区中,一批对象的总大小总是大于这个Survivor区域的内存大小的50%,那么此时大于等于这批对象年龄的对象,就可以直接进入老年代;
如图:
3: 大对象直接进入老年代
4:Minor GC后的对象无法放入到Survivor区中,这批对象直接进入老年代。
比如上面这张图。假如发生GC的时候,发现Eden区的超过150M的存活对象,此时Survivor区没办法放入,就会直接进入老年代:
老年代空间分配担保原则
首先在执行任何一次Minor GC的时候,JVM都会检查一下老年代可用的内存空间,是否大于新生代的内存总和,因为在最极端的情况系,新生代在Minor GC后所有对象都存活下来,
如果说发现老年代的内存大小是大于新生代的所有对象,那么此时就可以大胆的发生Minor GC,因为即使在Minor GC后,即使所有的对象都存活,Survivor区放不下,老年代也可以放下。
如果发现老年代的可用内存大小小于新生代的全部对象大小,就会看一个“-XX:HandlePromotionFailure”的参数是否设置了,加入有这个参数设置,就会进行下一步判断,看看老年代的内存大小是否大于每一次Minor gc进入老年代的平均大小,
比如:之前每次MinorGC后平均都有10M左右的对象进入老年代,那么此时老年代的可用内存大于10M,这就说明,很可能这次也是差不多10M左右的对象进入老年代,此时老年代的空间是够得
如图:
如果上面那两个步骤都判断成功了,就会再次冒险触发一下Minor GC,此时Minor GC可能有几种可能:
第一种:Minor GC过后,剩余存活对象的内存大小小于Survivor区的内存大小,那么就会直接进入Survivor区
第二种:Minor GC过后,剩余存活对象的内存大小大于Survivor区的内存大小,但是小于老年代的内存大小,那么就会直接进入老年代
第三种:Minor GC过后,剩余存活的对象的大小大于老年代的大小,也大于老年代的内存大小,此时老年代也放不下剩余的存活对象,就会发生“Handle Promotion Failure”的情况,这时就会触发一次“Full GC”。
FullGC就是对老年代进行垃圾回收,同时也会对新生代进行垃圾回收。因为这个时候必须吧老年代里没人引用的对象给回收掉,然后才可能让Minor GC过后存活的对象进入老年代里面,
如果还是Full GC过后,老年代还是没有足够的空间存放Minor GC过后的剩余存活对象,那么此时就会报所谓的“OOM” 内存溢出。
如果上面那次判断失败了,或者是没有设置”-XX:HandlePromotionFailure“参数,就会触发一次Full GC,对老年代进行垃圾回收,进来腾出一部分空间,在进行Minor GC
二:老年代
老年代就是用来存放那个生命周期长(创建后长期存在)的对象
例如:
老年代触发垃圾回收的时机
1:老年代可用内存小于新生代全部对象的大小,如果没开启空间担保参数会直接触发FullGC,
2:老年代可用内存大小小于历次新生代GC后进入老年代的平均对象大小,此时会提前FullGC,
3:在Minor GC之后,存活对象大于Survivor,那么会直接进入老年代,此时老年代空间不足
4:“-XX:CMSInitiatingOccupancyFaction”参数
老年代垃圾回收算法
标记整理算法
在老年代里标记处一些存活对象。接着会让这些存活对象在内存里移动,把存活的对象进来都挪到一边去,让存活对象紧靠在一起,避免垃圾回收过后出现内存碎片。然后再一次性的把垃圾对象回收掉
注意:老年代的垃圾回收算法的速度,只要要比新生代的垃圾回收算法速度慢10倍,如果老年代频繁的出现full GC,会导致系统性能被严重的影响。出现频繁卡顿的情况。
三:永久代
永久代就是方法区,用来存放一些类信息的
四:jvm核心参数
-Xms: java堆内存的大小
-Xmx: java堆内存允许扩展的最大大小
对于上述这对参数来说,通常都设置为大小完全一样,
-Xmn: java堆内存新生代代大小,扣除新生代的大小,就是老年代的大小
-XX:PermSize: 永久代大小(java8之前)
-XX:MaxPermSize: 永久代最大大小(java8之前)
-XX:MetaspaceSize:永久代大小(java8之后,方法区改为元空间)
-XX:MaxMetaspaceSize:永久代最大大小(java8之后,方法区改为元空间)
-Xss: 每个线程的栈内存大小
每个线程都有自己的java虚拟机栈,然后每次每执行一个方法,就会将对应方法的栈针压入线程的栈里,方法执行栈针就从线程栈里出栈,