垃圾收集器(GC)的做用相信你们都知道,它将咱们的不用的内存空间给回收,Java的垃圾收集器是"动态分配内存和垃圾收集"的。正由于它是动态的,因此不少人都忽略了它,但当出现一些内存泄漏、内存溢出的问题时,咱们必须掌握JVM才能去解决问题java
上文中,咱们说到程序计数器、虚拟机栈、本地方法栈这3个区域随线程生,随线程死。算法
栈中的栈帧随着方法的进入和退出而出栈、入栈,每一个栈帧分配多少内存基本在类结构肯定时就是已知的(不包括JIT的优化)数组
而Java堆和方法区只在程序运行期间才会知道开辟的空间(),这部份内存分配和回收是动态的,因此垃圾收集器关注的这部份内存安全
当一段内存再也不使用(不处于存活状态)时就回收,下文会谈到哪些内存将再也不使用bash
这就是咱们下文要讲到的各类回收机制服务器
首先,来看堆,堆中存放了几乎全部的实例对象,在对堆进行回收内存时,要先判断哪些对象能被回收(存活)。多线程
每当有一个地方引用它,计数器+1,每当一个引用失效,计数器-1;任什么时候刻计数器为0的对象是不能被使用的。并发
这种分析虽然简单,但有一个问题,如循环引用:布局
public class A {
Object obj;
public void testGC(){
A a1 = new A();
A a2 = new A();
a1.obj = a2;
a2.obj = a1;
a1 = null;
a2 = null;
System.gc();//若是采用引用记数法则不回收
}
}
复制代码
因此,咱们须要一种更全面的回收机制性能
经过一系列被称为“GC Roots”的对象做为起点,从这些节点开始往下搜索,搜所走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连,则证实此对象是不可用的
可做为GC Roots的对象:
垃圾收集器判断对象存活都和引用有关,下面来看引用有哪些类型
一个对象要被宣告死亡,要经历两部:
一个对象的finalize()方法只会被执行一次
在Java9中,finalize()方法已被弃用,缘由以下:
永久代的方法区分为两部分:
知道了要回收哪些东西,咱们还要知道如何回收,下面来看一下典型的垃圾回收算法
分为标记和清除两个阶段:首先标记出全部须要回收的对象,在标记完成后赞成收回被标记的对象
他将内存分为两块,每次只使用其中一块。当一块内存用完后,就将还存活的对象复制到另外一块上面,再将已使用的内存一次清理掉。
可是,咱们通常不将它对半分,而是分为一块较大的Eden和两块较小的Survivor区域,HotSpot默认Eden:Survivor比例大小为8:1,即Eden为收集前的空间,一块Survivor为收集后的大小,只浪费了10%的空间。
注:当每次回收有大于10%的对象存活时,经过分配担保机制让Survivor中剩余存不下的进入老年代
标记过程和标记-清除算法同样,清理以前,让全部存活的对象都向一端移动,而后直接清理掉边界之外的内存。
根据对象存活周期的不一样划分为几块,通常为新生代和老年代
以上为理论的垃圾收集算法,实际如HotSpot虚拟机会对算法有严格的考量。。。
OopMap:虚拟机用它来得知哪些地方存放着对象引用,在类加载完成后,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程当中,也会在特定位置记录栈和寄存器中哪些位置是引用
程序在安全点才能暂停执行GC,因此安全点通常选定为“是否具备让程序长时间执行的特征”(如方法调用,循环跳转,异常跳转),前文“特定位置”就被称为安全点。
如何让全部线程都跑到最近安全点上停下来:
安全点保证了程序执行时在不太长的时间内就会遇到可进入的GC的安全点,例如线程处于SLeep或Blocked状态,这时线程没法响应JVM中断请求,这种状况,就须要安全区域来解决。
安全区域是指在一段代码中,引用关系不会发生变化。
当线程执行到了安全区域中的代码,标识本身进入了Safe Region,挡在这段时间里JVM要发起GC时,就不用管标识本身为Safe Region状态的线程了。在线程要离开Safe Region时,去检查是否完成根节点枚举,若是完成,线程继续执行;不然它就必须等待直到收到能够安全离开Safe Region的信号为止。
能够理解为收集算法是内存回收的方法论,垃圾收集器就是内存回收的具体实现
特色:
特色:
特色:
Serial Old是Serial收集器的老年代版本,它一样是一个单线程收集器,使用标记整理算法。这个收集器的主要意义也是在于给Client模式下的虚拟机使用。
若是在Server模式下,主要两大用途:
Parallel Old 是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器在1.6中才开始提供。
这类应用尤为重视服务器的响应速度,但愿系统停顿时间最短,以给用户带来较好的体验。CMS收集器就很是符合这类应用的需求
CMS收集器是基于“标记-清除”算法实现的。它的运做过程相对前面几种收集器来讲更复杂一些,整个过程分为4个步骤:
其中,初始标记、从新标记这两个步骤仍然须要“Stop The World”.
CMS收集器主要优势:
CMS三个明显的缺点:
使用G1收集器时,Java堆的内存布局是整个规划为多个大小相等的独立区域(称为Region,大小为2的幂次方,如1M,2M,4M),虽然还保留有新生代和老年代的概念,但新生代和老年代再也不是物理隔离的了,它们都是一部分Region的集合。
G1收集器之因此能创建可预测的停顿时间模型,是由于它能够有计划地避免在真个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的价值大小(回收所获取的空间大小以及回收所须要的时间的经验值),在后台维护一个优先列表,每次根据容许的收集时间,优先回收价值最大的Region(这也就是Garbage-First名称的又来)。这种使用Region划份内存空间以及有优先级的区域回收方式,保证了G1收集器在有限的时间内能够获取尽可能可能高的收集效率
问:若一个对象在Region中,但他若是有其余Region中、甚至整个堆任意对象有引用关系,作可达性断定对象存活时,要扫描整个对空间吗?
答:
G1中提供了三种模式垃圾回收模式,young gc、mixed gc 和 full gc,在不一样的条件下被触发。
自动内存管理最终能够归结为自动化地解决了两个问题:
简单来讲,对象内存分配主要是在堆中分配。可是分配的规则并非固定的,取决于使用的收集器组合以及JVM内存相关参数的设定
大多数状况下,对象在Eden区分配内存
Minor GC和Full GC的区别:
大对象是指,须要连续内存空间的Java对象,例如很长的字符串或数组
虚拟机给每一个对象定义了一个对象年龄(Age)计数器,若是对象在Eden出生并通过第一次Minor GC后仍然存活,而且能被Survivor容纳的话,会被移动到Survivor空间中,而且对象年龄为1.每在Survivor区中渡过一次Minor GC,年龄增长1,当它的年龄增长到必定程度(默认15),就被晋升到老年代。
若是在Survivor空间中相同年龄全部对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就能够直接进入老年代
伪代码解释:
//准备Minor GC:
if (老年代中最大连续可用空间>新生代全部对象总空间){
//开始Minor GC
} else {
if (容许担保失败){
if (老年代最大连续可用空间>历次晋升老年代对象平均大小){
//开始Minor GC
} else {
//开始Full GC
}
} else {
//开始Full GC
}
}
复制代码
通常来讲,新生代只使用一个survivor空间来进行轮换时的备份,因此当出现极端状况(即新生代空间在一次minor GC后所有存活)时survivor空间有可能爆满,因此此时须要老年代进行分配担保,即survivor区没法容纳的对象都进入老年代。
在JDK 6 Updale 24 以后,Handle PromotionFailure 不会再影响到虚拟机的空间分配担保策略,只要老年代的连续空间大于新生代对象总大小或者历次晋升的平均大小就会进行Minor GC,不然将进行Full GC。
最后,内存回收和垃圾收集器不少时候都是影响系统性能,并发能力的缘由,虚拟机也提供了多种收集器和大量的调节参数,由于不少时候咱们要选择本身的业务来设置相应的收集方式