垃圾回收主要针对的是JVM的堆内存,我讲一下我了解的hotspot实现的jvm, 分为新生代和老年代,按照以前的说法,还有一个永久代,永久代在方法区里(物理上跟老年代相连),保存了class信息,静态变量,常量池等, jdk1.8之后,方法区实现发生了变化,取消了永久代的概念(hotspot特有的概念),使用了元空间的概念, 跟老年代分开,直接使用了jvm的内存(会使得内存溢出的可能性减小,空间也更容易扩展),同时方法区也转移一部分数据出去,例如类的静态变量和字符串常量池都放到了堆内存当中.在堆内存中,根据垃圾回收的范围,一般有两种,一种是minor GC(Young GC)针对新生代,一种是majorGC针对老年代,一般majorGC动作伴随着minorGC所以也可叫做fullGC.然后我还知道垃圾回收常见的搜集算法和回收算法.搜集算法包括:引用计数法(缺点是两个垃圾循环引用就没法标记),解决方法:根可达分析算法(GC root包括:栈中引用的对象(局部变量表中引用的对象), 类静态属性引用的对象, 常量引用的对象, Native方法引用的对象),标记之后就是涉及一个回收算法:包括标记清除法(会产生内存碎片,所以有些公司维护就重启服务器),复制算法(拷贝同时也清空,既做到垃圾回收,还做到了碎片整理,但是缺点内存空间浪费一倍,年轻代中S1和S0中就是使用这个算法),标记整理算法(就是在清理垃圾基础上多了一步碎片整理,一般在老年代内存不足时,触发fullGC,做碎片整理工作).jvm具体的垃圾回收器包括最早期的(Serial和Serial Old),中期的(Parallel Scanvenge和Parallel Old)和过渡期的(ParNew和CMS)和G1以及将来的ZGC,我简单说一下CMS(老年代回收器),CMS,初始标记的时候只标记根对象的第一层,然后在用户线程执行期间并发标记,然后再并发期间产生的错标和漏标会在下一阶段重新标记(并发标记,也触发stw)
G1(1.9以后才是默认垃圾回收器),G1允许用户手动设置一个期望的STW时间,G1并不会准确符合要求,只会尽可能缩短时间,G1将整个堆内存划分成若干相等大小的区域,默认2048,依然保留了伊甸区,幸存者区和老年代概念,但不是物理上连续,一块区域可能是年轻代也可能是老年代,空间大小不绝对固定.GC扫描内存时,无需扫描整块区域,扫描特定区域即可,增大支持的堆内存的大小,它回收时,会根据设定的STW调整策略,将要进行扫描的区域进行价值排序,如果设置的stw时间短,就优先回收一部分区域(如果设置时间过低会导致回收不完全,经常GC,导致吞吐量下降),他也使用复制算法,减少了碎片.大对象也单独存放了(humongous)
逃逸: 方法中对象的引用被外面使用或者没有被返回则会在栈中开辟空间),对象会在栈中开辟空间
可达状态:当创建一个对象后,如果有一个以上的引用变量引用它,则这个对象处于可达状态,程序可以通过对象的实例变量和方法进行调用。
可恢复状态:如果程序中的某个对象不再有任何引用变量引用它,它就进入到了可恢复状态,在这个状态下,系统的垃圾回收机制准备回收这该对象所占的内存。,在回收该对象之前会调用该对象的finalize方法,看是否还有其他变量引用,如果有可以由可恢复状态进入到可达状态。
不可达状态: 当对象与所有引用变量的关联都被切断,并且系统已经调用该对象的finalize方法也无法进入到可达状态, 从而导致进入不可达状态, 系统才会进行垃圾回收。
程序员无法精准的控制系统何时进行垃圾回收,也无法明确在什么时间进行垃圾回收,程序只能控制一个对象不再被任何变量引用,但不能控制它何时被垃圾回收。
虽然程序无法精准的控制垃圾回收,但是依然可以通过代码指定系统进行强制垃圾回收。但是系统执行垃圾回收仍然是不确定的,大部分时候程序员只能指定强制垃圾回收,但是在何时进行还是由jvm自身决定。
两种方式进行强制垃圾回收:
System和Runtime类的gc两者区别:
相同点: 都会显式触发Full GC,同时对老年代和新生代进行回收,尝试释放被丢弃对象占用的内存。
System.gc()调用附带一个免责声明, 无法保证立即触发gc
不同点: Runtime.gc() 是 native method而System.gc() 是非 native method,它依次调用 Runtime.gc();
额外补充: 如果每次调用gc方法后想让gc必须执行,可以追加调用system. runFinalization方法
在垃圾回收机制回收某个对象所占用的内存,会调用这个对象的finalize方法。
Finalize方法有4个特点:
Serial收集器:
Serial收集器还提供用于执行老年代垃圾收集的Serial old收集器。Serial old收集器同样也采用了串行回收和"stop the World"机制,只不过内存回收算法使用的是标记-压缩算法
优点: 简单高效
缺点: 如果垃圾回收时间过长, 就会造成应用卡顿的情况
ParNew收集器
Parrallel Scavenge收集器
CMS 并发标记清除
目的最短停顿时间, 为了注重用户体验
采用标记清除算法实现
老年代收集器
步骤:
初始标记和重新标记还是需要stop the world
优点:并发收集、低延迟。
缺点:
G1收集器
面向服务器的垃圾收集器,主要针对多颗处理器即大容量内存的机器。以极高概率满足GC停顿时间要求的同时还具备高吞吐量性能。它同时兼顾年轻代和老年代。
G1将整个Java堆(新生代、老年代)划分为多个大小固定的独立区域,并且跟踪这些区域里面的垃圾堆积程度,在后台维护一个优先列表,每次根据允许的收集时间,优先回收垃圾最多的区域。
**优点:**与其他GC收集器相比,G1使用了全新的分区算法,还在并发的基础上并行, 大内存应用上则发挥其优势。平衡点在6-8GB之间
**缺点: **G1无论是为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS要高。经验上讲在小内存应用上CMS的表现大概率会优于G1
jdk1.8 默认垃圾收集器: 新生代(Parallel Scavenge) + 老年代(Parallel Old)
jdk1.9 默认G1
jdk11 使用的是实验性质的ZGC, 也是低停顿, 几乎所有地方并发执行, 除了初始标记是STW, 几乎所有停顿时间都在初始标记上.
ZGC技术: 着色指针Colored Pointer和 读屏障Load Barrier.
包括: 标记-清除算法、标记-复制算法、标记-整理算法、分代收集算法。
分代收集算法: 新生代(标记-复制算法), 老年代(标记-整理算法)
标记整理比标记清除强在了不会产生内存碎片.