垃圾回收:java
将已经分配出去的内存,取再也不使用的回收回来,已便于再次分配算法
如何区分对象是否变成垃圾?安全
引用计数法与可达性分析多线程
引用计数法:并发
使用计数器统计对象引用个数jvm
使用语言:性能
Python线程
问题:指针
须要额外的空间存储计数器,频繁更新操做对象
没法处理循环引用问题
可达性分析:
GCRoot做为初始存活对象合集
GCRoot:
堆外指向堆内的引用
GCRoot范围:
Java方法栈帧中的局部变量
已加载类的静态变量
JNI handles
已启动且未中止的Java线程
好处:
解决了循环引用问题
问题:
多线程状况下可能出现漏报和误报
Stop-the-world以及安全点
解决方案:
STW(Stop the World),中止其余非垃圾回收的工做,直到垃圾回收完成,这一段时间会形成GC暂停。
经过安全点机制来实现STW,当全部线程都到达安全点,才准许STW独占线程工做。
安全点的做用,找到一个稳定状态,jvm不在产生堆栈。
垃圾回收的三种方式
标记-清除:
原理及其简单,形成内存碎片,分配效率低
标记-压缩:
解决内存碎片化问题,代价压缩算法性能开销
标记-复制:
一样能解决内存碎片化问题,缺点堆空间使用效率低下
JVM虚拟机的堆划分
JVM经过分代思想进行垃圾回收,JVM堆分为新生代和老年代。
新生代:
Eden区以及两个大小相同的Survivor区
Eden存储对象的内存,对空间是线程共享,所以划分空间是须要同步。
经过TLAB避免两个对象共用一段内存。
TLAB能够向JVM申请一段连续的内存为线程私有。
申请操做须要加锁Eden区内存耗尽怎么办?
JVM便会触发Minor GC,存活对象放置到Survivor区。
from指针的survivor区中的对象会复制到to指向的Survivor区。
交换from和to指针,下次Minor GC时,to指向的survivor区仍是空的。
若是JVM对象在survivor区复制默认超过15次晋升到老年代,或者单个survivor区已占用50%,较高复制次数的对象也会晋升到老年代。
MinorGC好处不会对整个堆进行垃圾回收。
可是有一个问题,老年代的对象可能引用新生代的对象。
在标记存活对象时,咱们须要扫描老年代的对象。
若是该对象拥有对新生代对象的引用,这个也会被做为GCRoots。
可能会致使从新作一次全堆扫描,引入了卡表技术
卡表
卡表(Card Table):
将整个堆分为一个个大小为512字节的卡,而且维护一个卡表,用来存储每张卡的一个标识位。
这个标识位表明对象的卡是否可能存在指向新生代对象的引用。若是存在,咱们认为这张卡是脏卡。
如何设置卡的标识位?
JVM获取每一个引用型实例的写操做,并记录写标识位的操做
解释执行实现简单。JIT则须要插入额外逻辑,写屏障(write barrier)。
写屏障:
能加大Minor GC的效率,可是会带来虚共享问题,能够经过-XX:UseCondCardMark,来尽可能减小写卡表的操做
垃圾回收器类型
Serial:
最古老、最稳定的垃圾回收器
可同时做用于新生代和老年代
新生代采用复制算法
老年代采用标记-压缩算法
ParNew:
Serial收集器新生代的并行版本
复制算法
多线程,需多核支持
-XX:ParallelGCThreads限制线程数量
Parallel收集器:
相似ParNew,
新生代复制算法,老年代标记压缩
更加关注吞吐量
-XX:+UseParallelGC使用Parallel收集器+老年代串行
-XX:+UseParallelOldGC使用Parallel收集器+老年代并行
-XX:+MaxGCPauseMills最大停顿时间,单位毫秒,GC尽可能保证回收不超过该时间
-XX:+GCTimeRatio 0-100的取值范围,垃圾收集时间占总时间的比,默认99,即最大容许1%时间作GC
TIPS:停顿时间和吞吐量不可能同时调优
CMS收集器:
Concurrent Mark Sweep并发标记清除
标记清除算法
并发阶段下降吞吐量
老年代收集器(新生代使用ParNew)
-XX:+UseConcMarkSweepGC
G1收集器:
横跨新生代和老年代
打乱堆结构,堆分红不少个区域,每一个区域均可以充当Eden区、Survivor区或者老年代其中一个
标记压缩算法
G1在Java7中发布取代CMS, java9之后CMS收集器废弃
优先回收死亡对象较多的区域
ZGC收集器:
暂停时间不超过10ms
java11将提供