一.Java堆从GC的角度去看,可以分为三个区域,分为是新生代,老年代,永久代(是方法区的实现,但是物理逻辑是和堆在一起的)
其中新生代占1/3的堆空间,老年代占2/3的堆空间,永久代占得很少,就不进行划分了,他也占了,但是很少很少,新生代占的1/3又分为Eden区(占比8/10),ServivorFrom区(占比1/10),ServivorTo区(占比1/10),大概的占比图如下
1.gc
1)如果进行了一次gc后还存活,age+1,然后进入新生代中的s0区
2)GC会根据区域的剩余内存进行判断是否进行清理
3)如果age等于15时,进入老年代,长期存活的对象进入老年代
4)动态年龄判断:如果s0中有一部份的对象的内存大于它的50%,则将年龄大的放进老年代
2.使用gcroot判断对象是否可达,来判断对象是否可被回收(是否为垃圾),哪些可以被作为gcroot呢:
1、栈(栈帧中的本地变量表)中对象的引用。
2、方法区中的静态成员。
3、方法区中的常量引用的对象(全局变量)。
4、本地方法栈中JNI(一般说的Native方法)引用的对象。
1.新生代
我们新创建的对象(除了大对象,大对象是直接放进了老年代)都是放在新生代中的,由于JVM会频繁的创建对象,所以就会频繁的触发GC,新生代时的GC叫做MinorGC
新生代中分为了三个区域,分别时Eden、ServivorTo、ServivorFrom(这两空间交替变成ServivorTo和ServivorFrom的)
Eden:我们新创建的对象首先就会放入到Eden区,当Eden区的内存不足时会触发MinorGC
ServivorTo:存放上一次MinorGC的幸存者,也是下一次的ServivorFrom区
ServivorFrom:存放的是上一次MinorGC的幸存者,在上一次中他是ServivorTo区,他在这一次中充当被扫描者的角色
其实从上面的几个介绍来看,可能就只能理解Ened是干嘛的,如果了解了MinorGC的流程就会很清楚了
MinorGC(使用的复制算法):将空间划分为两份,一个预留,一个实际,在进行GC时,将预留的开启,将没被删除的放进预留的内存块,将实际的转换为预留的
MinorGCC的具体过程是采用复制算法实现的,具体步骤如下:
1、首先会把Eden区与ServivorFrom区中存活的对象复制到ServivorTo区中去。这里会将达到了老年代的标准的对象复制到老年代区去,然后整体对象的标准年龄+1(15为老年代的标准),如果ServivorTo的内存不足的情况,存活的对象就直接全部复制到老年代
2、清空Eden区与ServivorFrom区
3、将ServivorTo区与ServivorFrom区互换,原本的ServivorTo区成为了下一次ServivorFrom区,然后下一次他里面的对象又要被复制到下一次的ServivorTo区中区
2.老年代
老年代主要存放的就是长生命周期的对象和大对象,不会频繁出发GC了,老年代的GC叫做MajorGC,在进行老年代GC之前,他会调用新生代的GC一次,然后将那些要存放到老年代的对象都复制过来,如果在新生代GC后,老年代的空间不足,那么就会触发MajorGC
老年的GC采用的是标记清除法,标记清除算法:在GC的过程中,将要删除的标记删除,但会产生内存碎片
他会去扫描所有的对象并且标记存活的对象,然后回收掉所有未标记的对象。MajGC因为要扫描所有的对象,所以耗时会较长,而且标记清除法也容易产生内存碎片,老年代内存空间不足时,会触发OOM
3.永久代
永久代主要保存的是Class和元数据信息,Class在类加载时被放入永久代,永久代不会触发GC,因为他不会GC,所以他的内存会随着加载Class文件的增加而增加,加载的Class文件过多时就会OOM,比如Tomcat引用的jar文件过多时就会导致JVM内存不足而无法启动
在1.8之前,永久代是用的JVM虚拟机的内存,所以他的实际可用内存就受到了JVM内存的限制,1.8之后用的是操作系统的内存