堆空间的基本结构
Java堆是垃圾收集器管理的主要地方,所以又称为GC堆
主要是分代垃圾回收算法,所以Java对被划分成多个部分:新生代、老年代、永久代
内存分配和回收原则
对象优先在Eden区分配
当Ende区空间满的时候,虚拟机会发起一次Minor GC。在垃圾回收过程红无法存入Survivor时,治好通过分配担保机制直接存入老年代中
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
大对象直接进入老年代主要是为了避免为大对象分配内存时由于分配担保机制带来的复制而降低效率。
长期存活的对象将进入老年代
每个对象会有个年龄计数器(age),当进行一次Minor GC后,对象没有被回收的话,就进入Survivor中,进入的初始化age是,当age到15的时候就会进入老年代
部分收集
新生代收集:只收集新生代
老年代收集:只收集老年代
混合收集:对整个新生代和部分老年代进行收集
整堆收集:收集整个Java堆和方法区
空间分配担保机制
是Minor GC之前老年代中海油容纳新生代对象的剩余空间
死亡对象判断机制
垃圾回收机制需要判断哪些对象已经死亡
引用计数法:给每个对象加上一个应用计数器,每被引用一次,计数器+1,引用失效则-1,解决不了对象之间相互循环引用的问题(除了对象 objA
和 objB
相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,导致它们的引用计数器都不为 0,于是引用计数算法无法通知 GC 回收器回收他们)
可达性分析算法:通过一系列的GT Roots对象作为七点,从这些节点向下搜索,走过的路径称为引用链,当一个对象连接不到GT Roots时,就需要被回收
哪些对象可以作为 GC Roots 呢?
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈(Native 方法)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有被同步锁持有的对象
引用类型总结
强引用:如果一个对象具有强引用,即时内存不足的时候,也不会被垃圾回收器回收。
软引用:在内存够的时候就不会被回收,内存不够的时候才回收。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用:具有短暂的生命周期,垃圾回收器扫描到的时候,不管内存够不够都会回收。但是垃圾回收期是一个优先级很低的线程,并不是每一次都会回收。弱引用可以和一个引用队列联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列重量
虚引用:任何时候都有可能被回收
虚引用和软引用、弱引用的区别:虚引用必须和一个引用队列一起使用。在垃圾回收之前,会将这个对象加入引用队列中。程序通过判断引用队列来了解这个对象是否将要被垃圾回收,在引用这个对象垃圾回收之前做出相应的操作。
基本上不会使用到虚引用和弱引用。软引用常常用于JVM加速回收、防止溢出、维护系统安全
如何判断一个常量是废弃常量?
假如在字符串常量池中存在字符串 "abc",如果当前没有任何 String 对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池了。
如何判断一个类是无用的类?
满足以下三个条件的才是无用的类:1.该类的所有实例对象都被回收 2.该类的类加载器已被回收 3.不可以被反射获取该类,也就是java.lang.class对象没有在任何地方被引用
垃圾收集算法
标记-清除
分为两个阶段。第一步将需要回收的对象标记出来,第二步清除被标记的对象。后续的算法是在这个基础上的改进。存在着空间问题(产生大量的碎片)和效率问题。
标记-复制
将内存分为两个同样的大小,将不被回收的对象复制到另一半去,然后将这一半的所有都回收
标记-整理
根据老年代的特点提出一种标记算法,标记过程和标记-清除算法一样,但是标记完,将不是回收的对象向一端移动,再清楚端边界以外的内存
分代收集算法
在新生代中,每次都有着大量的对象死去,所以用标记复制算法,在老年代中,常用的是标记复制和标记整理算法
垃圾收集器:内存回收的具体实现
Serial(串行)收集器
是一个单线程的垃圾收集器,只会用一条线程去进行垃圾回收,在回收的时候会暂停其他的线程。没有与其他线程的交互,简单高效
PerNew收集器
多线程版本的Serial收集器
Parallel Scavenge收集器
标记复制算法的多线程收集器。
Serial Old收集器
Serial老年代的收集器
Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及 CPU 资源的场合,都可以优先考虑 Parallel Scavenge 收集器和 Parallel Old 收集器。
CMS收集器
CMS注重的是用户体验。以极短的回收停顿时间为目的。是真正意义上的并发收集器,事项了垃圾回收线程与用户线程同时工作。
标记-清除算法实现的。运行分为四个步骤:
1.初始标记:暂停其他所有的线程,记录下直接与root相连的对象,速度快的很
2.并发标记:同时开启GC和用户线程,标记可达对象。这个过程并不能标记所有的可达对象,因为用户线程不断地变化会导致不好的实时性
3.重新标记:将并发标记期间用户线程产生变动的标记记录。这一步时间稍长
4.并发清除:GC线程清除没有被标记的对象
G1收集器
面向服务器的收集器,保证停顿时间也要很高的吞吐量。有以下的特点
并行与并发:多核CPU保证了其效率,缩短了停顿时间,也保持了之前的并发特点
分代收集:保持了分代的概念
空间整合:整体上看是标记整理算法,局部上看是标记复制
停顿可预测:是 G1 相对于 CMS 的另一个大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为 M 毫秒的时间片段内
G1 收集器的运作大致分为以下几个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
ZGC收集器
与 CMS 中的 ParNew 和 G1 类似,ZGC 也采用标记-复制算法,不过 ZGC 对该算法做了重大改进。
在 ZGC 中出现 Stop The World 的情况会更少!