JAVA语言最大的特点在于具备良好的垃圾收集特点,也就是GC是整个java之中最重要的安全保证,帮助开发者写出合理的代码。整个JVM中的GC处理机制:对不需要的对象进行标记,而后进行清除。
图一:java堆内存的划分
1.8之后永久代改成了元空间 永久代只有HotSpot中有 Oracle 试图将HotSpot和JRockit的标准合并为一个
JVM中堆内存之中实际内存分三块
1、 新生代:新对象和没有达到一定年龄的对象都在新生代
2、 老年代:被长时间使用的对象,内存空间比
3、 元空间:一些方法中临时操作的对象等,直接使用物理内存(外部内存);
最初的永久代需要在JVM堆内存中进行划分(内部内存);
分区的目的是方便内存的分类管理
GC处理的主要区域是新生代和老年代,元空间(永久代)都不在GC处理范围内。
在开发中会有临时对象和常驻对象为了保证GC的性能问题对于GC对象的处理流程如图
图二:GC的策略
新对象产生,对象需要申请空间
1、 为新对象申请空间 Eden区是否足够,足够 分配;不足,Minor GC回收Eden中的不活跃对象 回收后再次判断Eden区空间是否充足,充足 分配;不足 转2;
2、 判断 Survivor空间是否充足,充足 把Eden区的部分活跃对象保存到Survivor区,Eden区有了空闲空间,分配给新对象;否则 转3
3、 判断 Tenured空间是否充足,充足:把Survivor区的部分活跃对象保存到Tenured区,把Eden区的部分活跃对象保存到Survivor区,Eden区有了空闲空间,分配给新对象;否则进行Full GC,JVM进行完全垃圾回收,接着判断Tenured区空间充足,如果充足:重复上述的对象移动过程;否则:抛出“OutOfMemoryError”异常。
图三:伸缩区
在堆内存的分区中都存在一部分可变伸缩区,其基本使用流程是:如果内存不足了,则在可变的范围之内扩大内存空间,当内存不在紧张的时候再将可变内存空间释放,但是扩充和收缩需要消耗资源
内存调优就是减少收缩和扩充过程,干掉伸缩区,最简单的调优只会调整两个参数“-Xmx”(最大内存)和“-Xms”(初始化内存),通过调整着两个参数,让:最大内存=初始化内存,这样就没有伸缩区了,从而提高内存使用效率。
首先介绍根的概念:
1、栈中引用的对象
2、方法区中静态成员或者常量引用的对象(全局对象)
3、JNI方法中引用的对象
所有对象都产生在年轻代的Eden区 如果空间不足则会引发Minor GC 和Major GC(Full GC)所有的关键字new新实例化的对象一定会保存在Eden区,而对于存活区保存的一定是在Eden中长时间存在并且经过了好几次的Minor GC还保存下来的活跃的对象,这个对象会晋升到Survivor区中;
Survivor区一定会有两块空间且空间大小相对:一块是为了晋升,一块为了对象回收,这两块内存空间中一定有一块是空的。年轻代GC算法的Survivor区实现是复制算法:从根集合扫描出存活对象,并找到存活对象复制到一块新的完全未使用的空间中,然后进行空间合并(主要是存活区),腾出新的完全未使用的空间。
图四:年轻代GC策略
从算法上可以看出,在Survivor区分为S1和S0,两者大小相等且总有一个空的,所以这个是浪费的空间。
另一个问题就是,在我们写代码时间,会有很多临时对象,那么Eden区中可能会保存大量的临时对象就会导致频发的Minor GC操作,在HotSpot虚拟机中为了加快此空间的内存分配采用了两种技术“Bump-The-Pointer”和“TLAB(Thread-Local-Allocation Buffers)”
Bump-The-Pointer:这个算法把Eden看成一个栈空间,所有的线程每个申请的堆对象都直接压栈,也可以出栈。
图五:Bump-The-Pointer
TLAB(Thread-Local-AllocationBuffers):TLAB算法还是采用的压栈和出栈,只是把Eden区分为多个,每个线程都有一个独立的私有栈区,用于存储对应线程所申请的堆对象。
图六:TLAB(Thread-Local-AllocationBuffers)
老年代主要接收由年轻代发送过来的对象,一般情况下是经过好几次的Minor GC之后还会保存下来的对象才会进入老年代,如果保存的对象超过了Eden区的大小,那么此对象也会直接保存到老年代中,当老年代内存不足时会引发“Full GC”(Major GC)内存最大
老年代的GC算法是两种方式的结合:整理和压缩
标记-清除:在回收清除的过程中,所有在老年代中被回收的对象并没有对空间进行整理,老年代最头疼的问题是碎片化问题类似操作系统中的连续动态内存分配。
图七:标记-清除
标记-压缩:把各个碎片化的空闲空间移动到一起,形成一个大的连续内存空间,但是合并碎片化内存需要消耗大量资源
图八:标记-压缩
所有在进行老年代存储的时候尽可能保存长期会被使用,不会被轻易回收的大对象
永久代保存在堆内存之中,但是不会被回收,例如intern()方法产生的对象不会被回收。如果操作不当导致永久代中的数据量过大,一样会抛出“OutOfMemoryError”
元空间是在JDK1.8之后才有,和永久代没什么区别,唯一区别是永久代是堆内存的,而元空间是物理内存,直接受到操作系统的控制。