JAVA堆的特点
JAVA堆是JAVA虚拟机所管理的内存中最大的一块
JAVA堆是被所有线程共享的一块内存区域
虚拟机启动时创建JAVA堆
JAVA堆的唯一目的是存放对象实例
JAVA堆是垃圾收集器管理的主要区域
从内存回收角度看,由于现在收集器基本都采用分代收集算法,因此JAVA堆可细分为:新生代和老年代,新生代又被划分为三个区域Eden, From Survivor, To Survivor. 进一步划分的目的是为了更好地回收内存或更快地分配内存。
JAVA堆的大小是可扩展的,通过 -Xmx 和 -Xms 控制
如果堆内存不够分配实例对象,并且堆也无法再扩展时,将抛出 OutOfMemoryError 异常
Young 新生代(占堆空间的1/3) | Old 老年代(占堆空间的2/3) | ||
---|---|---|---|
Eden | From Survivor | To Survivor | |
占整个新生代空间的8/10 | 占整个新生代空间的1/10 | 占整个新生代空间的1/10 | |
堆内存划分
堆大小 = 新生代 + 老年代。 堆的大小可通过参数 -Xms (堆的初始容量)、 -Xmx (堆的最大容量) 来指定
新生代默认划分比例是 Eden: From Survivor: To Survivor = 8: 1: 1。(可通过参数 -XX:SurvivorRatio设定)
JVM 每次只会使用 Eden 和其中一块 Survivor 区域来为对象服务。因此,任意时刻下总有一块 Survivor 区域空闲
新生代实际可用的内存空间是 9/10 的新生代空间
堆的垃圾回收方式
JAVA堆是GC垃圾回收的主要区域。GC分为两种:Minor GC 和 Full GC(亦称 Major GC)
Minor GC
Minor GC 是发生在新生代中的垃圾收集动作,所采用的算法是 复制算法
GC一般为堆空间某个区域发生了垃圾回收
几乎所有JAVA对象都“出生”于新生代区域,即JAVA对象都在新生代区域进行内存的申请与JAVA对象的存放。JAVA中的大部分对象通常不会长久地“存活”,具有朝生夕死的特点
当一个对象被判定为“死亡”时,GC就负责来回收掉这部分对象的内存空间
新生代是收集垃圾的频繁区域
Full GC
Full GC 是发生在老年代的垃圾收集动作,采用 标记-清除算法(可能产生大量内存碎片)或 标记整理算法(解决内存碎片问题)
现实的生活中,老年代的人通常会比新生代的人 “早死”。堆内存中的 老年代(Old) 不同,老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 “死掉”。因此,Full GC 发生的次数不会像 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间更长,一般是Minor GC的 10倍以上
标记-清除算法 收集垃圾时会产生许多的内存碎片(不连续的内存空间),此后需要为较大的对象分配内存空间时,如果无法找到足够的连续内存空间,就会提前触发一次 GC 的收集动作
垃圾对象的判定标准
JVM 的 GC 工作主要针对的对象是堆内存。在做GC工作之前,首先要判定堆内存中的对象实例是否为垃圾,通常使用以下两种算法来定义
1. 引用计数算法
JAVA 在运行时,当有一个地方引用该对象实例,会将这个对象引用计数值 +1,引用失效时就 -1. JVM 在扫描内存时,发现引用计数值 = 0 即垃圾对象,计数值 > 0 即活跃对象。
目前垃圾回收算法,没有采用引用计数算法,原因是在对象互相引用的情况下,无法判定两者是否为垃圾对象
2. 根搜索算法
根搜索算法是以 “GC ROOTS” 为起始点往下搜索,所有经过的对象合并起来称为引用链,在这引用链里,没有的对象称为垃圾对象(JVM 还做了一个筛选动作,判定当前对象是否执行finalize()方法,如果不需要执行才判定为垃圾对象,这里不做介绍),在引用链里的是活跃对象
GC ROOT:
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中的类静态属性引用的对象
方法区中的常量引用的对象
本地方法栈中 JNI(Native 方法)的引用对象
垃圾回收算法
1. 标记-清除(Mark-Sweep)
JVM 会扫描所有的对象实例,通过根搜索算法,将活跃对象进行标记,JVM 再一次扫描所有对象,将未标记的对象进行清除,只有清除动作,不作任何的处理,这样导致的结果会存在很多的内存碎片
2. 复制(copying)
JVM 扫描所有对象,通过根搜索算法标记被引用的对象,之后会申请新的内存空间,将标记的对象复制到新的内存空间里,存活的对象复制完,会清空原来的内存空间,将新的内存作为 JVM 的对象存储空间。这样虽然解决了内存内存碎片问题,但是如果对象很多,重新申请新的内存空间会很大,在内存不足的场景下,会对 JVM 运行造成很大的影响
3. 标记-整理(Mark-compact)
标记整理实际上是在标记清除算法上的优化,执行完标记清除全过程之后,再一次对内存进行整理,将所有存活对象统一向一端移动,这样解决了内存碎片问题
4. 分代回收
目前 JVM 常用回收算法就是分代回收,年轻代以复制算法为主,老年代以标记整理算法为主。原因是年轻代对象比较多,每次垃圾回收都有很多的垃圾对象回收,而且要尽可能快的减少生命周期短的对象,存活的对象较少,这时候复制算法比较适合,只要将有标记的对象复制到另一个内存区域,其余全部清除,并且复制的数量较少,效率较高;而老年代是年轻代筛选出来的对象,被标记比较高,需要删除的对象比较少,显然采用标记整理效率较高