System.gc()
- 显式触发
Full GC
,同时对老年代和新生代进行回收,附带一个免责声明,无法保证对垃圾收集器的调用
- 性能基准测试时,会在运行之间调用
- 底层调用本地方法
public static void gc() {
//效果一致
Runtime.getRuntime().gc();
}
public native void gc();
复制代码
System.runFinalization();
复制代码
public class GCTest {
public static void main(String[] args) {
//当GC1执行完成,GC1的栈变量的引用都会失效,进行回收
GC1();
System.gc();
}
public static void GC1() {
{
byte[] buffer = new byte[1024 * 1024];
}
//执行完上面的作用域后,buffer引用并不会直接失效,而是是一个可重用的状态
//如果没有新的变量产生,这个引用还是存在的
//定义了value,就会替换buffer变量,引用失效,gc后就被回收
int value = 10;
System.gc();
}
}
复制代码
内存溢出与内存泄漏
- 内存指的并不是物理内存,而是
虚拟机内存
,取决于磁盘交换区设定的大小
- 内存溢出 OOM
- 应用程序占用的内存增长速度非常快,造成垃圾回收跟不上内存消耗速度
- 在抛出OOM异常之前,会进行
独占式的Full GC
,回收大量内存;JVM会尝试回收软引用指向的对象
- 当分配超大对象,超过堆的最大值,JVM判断垃圾收集并不能解决这个问题,直接抛出OOM
- 解释,没有空闲内存,且垃圾回收器也无法提供更多内存
- 堆内存设置不够,存在内存泄漏问题
- 代码中创建了大量大对象,长时间不能被垃圾回收器收集,如在运行时存在大量动态类型生成的场合、
intern字符串
缓存占用太多空间(永久代时期)
- 对象不再被程序用到了,但是GC又不能进行回收
- 某些操作导致对象的生命周期很长,导致OOM,如,一个
可以定义在方法内的变量
定义成了成员变量,并用static修饰;web程序,对象数据存储到上下文对象或者会话对象中
- 一旦发生内存泄漏,内存会被逐步蚕食,直到耗尽所有内存,有可能导致OOM
- 对于java来说,这个对象是不再被使用的,但是对这个对象的的引用是忘记断开的,根据
可达性分析
仍能找到这个对象,因此不能被GC回收,会导致内存泄漏
- 对于引用计数器的情况下,循环引用会导致内存泄漏
- 单例模式,单例的生命周期和应用程序一样长,如果持有对外部对象的引用,这个外部对象是不能被回收的,导致内存泄漏
- 一些提供
close
的资源未关闭,导致内存泄漏,连接一直存在,如数据库连接、网络连接和io连接等必须手动close,对于访问外部资源的对象,必须进行即时关闭,方便进行垃圾回收
STW
- Stop The World,GC事件发生过程中,产生应用程序的停顿,导致整个用户线程被暂停,没有任何响应
- 可达性分析枚举根节点会导致执行线程挺短
- 分析工作必须在一个确保一致性的快照中进行,分析过程中对象引用关系不能发生变化,冻结在某个时间点
- 需要减少STW的出现
- 与采用哪种GC无关,所有的GC都有STW;G1也不能完全避免,只能提高回收效率,尽可能缩短暂停时间
- 在后台自动和自动完成的,在用户不可见的情况下,把用户称唱的工作线程停掉
- 开发正不要调用
System.gc()
会导致STW发生
并行与并发
- 并发,在一个时间段中,有多个程序处于运行期间,并且在同一个处理器上运行
- 多个事情,在
同一个时间段
内同时发生,互相抢占资源
- CPU切换执行
- 并不是真正意义上的同时进行,只是CPU把一个时间段划分成几个时间片段,然后在这几个时间片段来回切换,每个时间片段执行某个程序上的任务
- 并行,当有一个以上CPU时,一个CPU执行一个进程,另一个CPU执行另一个进程,两个进程互不抢占CPU资源
- 多个事情,在
同一个时间点
上同时发生,不抢占资源
- 决定并行的不是CPU数量,而是CPU核心数量,一个CPU多核也可以实现
- 只有在多CPU或者一个CPU多核才会发生并行
- 垃圾回收中,针对的是垃圾收集线程是否在同一个CPU上
- 并行,多条垃圾收集线程并行运行,在多个CPU上同时执行,
用户线程处于等待状态
,ParNew/Parallel/Scavenge/Parallel Old
- 串行,单线程执行,如果内存不够,程序暂停,启动垃圾回收器进行垃圾回收
- 并发,用户线程和垃圾回收线程同时执行,垃圾回收器与用户线程在两个CPU上运行,垃圾回收线程在执行时不会停顿用户程序的运行,但是会有STW,需要保证可达性的准确性,可能会交替执行,
CMS/G1
安全点与安全区域
- 安全点,Safe Point,程序执行时并非在所有地方都能停顿下来,开始GC,只有在特定的位置才能停顿下来
- 如果安全点太少,可能导致GC等待时间太长
- 如果太频繁,导致运行的性能问题
- 安全点的选择,通常会根据
是否具有让程序长时间执行的特征
为标准,选择一些执行时间较长的指令作为安全点,如方法调用、循环跳转和异常跳转
- 运行到这几个地方,线程的一些状态可以被确认,便于保证对象的引用不发生改变
- 循环的末尾
- 方法临返回前 / 调用方法的call指令后
- 可能抛异常的位置
- 主要的目的就是避免程序长时间无法进入
Safe Point
。比如 JVM 在做 GC 之前要等所有的应用线程进入安全点,如果有一个线程一直没有进入安全点,就会导致 GC 时 JVM 停顿时间延长。比如这里,超大的循环导致执行 GC 等待时间过长。
- 抢先式中断,中断所有线程,如果还有线程在不安全点,恢复线程,让线程跑到安全点
- 主动式终端(基本采用),设置一个终端标志,各个线程运行到安全点时,主动轮询这个标志,如果中断标志为真,线程主动暂停
- 安全区域,Safe Region,线程处于不执行的状态,
sleep/blocked状态
,设置一段区域,这段区域内对象的引用关系不发生变化
,在这个区域内任何位置开始的GC都是安全的,被扩展的安全点
- 当程序运行到安全区域时,标识线程已经进入
Safe Region
,如果这段时间发生GC,JVM会忽略标识为Safa Region
状态的线程,不对该线程进行STW,该线程等待被唤醒
- 当线程被唤醒,即将离开安全区域时,会检查JVM是否已经完成GC,如果完成则继续运行,否则必须等待直到收到可以安全离开安全区域的信号
引用
- 能够描述一类对象,当内存空间还足够,则保留在内存;如果内存空间在进行垃圾回收后,还是很紧张,抛弃这些对象
- 强引用
Strong Reference
、软引用Soft Reference
、弱引用Weak Reference
、虚引用Phantom Reference
,引用强度依次减弱
- 强引用,
可触及的
,只要强引用关系还在,垃圾回收器永远不会回收掉引用对象
- 软引用,
软可触及的
,如果系统将要发生内存溢出之前,将会把这些对象列入二次回收范围内
;如果报OOM之前的回收,仍然没有足够内存,就会对软引用对象进行回收
,如果回收后,还没有足够的内存,抛出OOM异常
- 弱引用,
弱可触及的
,只要垃圾收集器工作,无论内存是否足够,都会被回收
- 虚引用,
虚可触及的
,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,无法通过虚引用获得一个对象实例,为一个对象设置虚引用关联的唯一目的,就是能在这个对象被收集器回收时,收到一个系统通知
,对象回收跟踪
强引用
- new的形式都是强引用,默认引用类型,可以直接访问目标对象
- 强引用的对象都是
可触及的
,即使抛出OOM,垃圾收集器也不会回收
- 是造成Java泄漏的主要原因之一
软引用
- 内存不足及回收
- 描述还有用,但非必需的对象,只要被软引用关联着的对象,在系统发生内存溢出异常前,会把这些对象列为回收范围进行二次回收,如果这次回收之后还没有足够的内存,抛出OOM
- 通常用来实现内部敏感的缓存,如
高速缓存
、MyBatis内部类
,保证使用缓存的同时,不会耗尽内存
- 垃圾回收器在某个时刻决定回收
软可触及的对象
时,清理软引用,并可选地把引用存放在一个引用队列
- 类似弱引用,JVM会尽量让软引用存活时间长一点
SoftReference softReference = new SoftReference<Person>(new Person("zhangsan",123));
//获取引用实例
System.out.println(softReference.get());
System.gc();
//堆空间内存足够,不会回收软引用可达对象
System.out.println(softReference.get());
try {
//内存不够
byte[] b = new byte[1024 * 1024 * 8];
//内存紧张,需要回收掉软引用对象,才能放得下这个数组
//byte[] b = new byte[1024 * (7168 - 483)];
} catch (Exception e) {
e.printStackTrace();
} finally {
//内存不足,回收软引用可达对象,null
System.out.println(softReference.get());
}
复制代码
弱引用
- 进行GC时,发现即回收
- 由于垃圾回收线程优先级较低,不能及时发先,可以存在较长的时间
- 在构造软引用时,指定一个引用队列
- 适合保存可有可无的缓存数据
- 三级缓存,内存(软引用、弱引用来保存)、本地、网络
WeakReference<Person> test = new WeakReference<>(new Person("test", 13));
System.out.println(test.get());
System.gc();
System.out.println(test.get());
复制代码
- 面试题 weakHashMap,在内存不足会被及时回收
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
....
}
复制代码
虚引用
- 对象回收跟踪,最弱的一个,一旦被回收,就会将这个虚引用添加到引用队列中
- 一个对象是否有虚引用的存在,完全不会决定对象的生命周期
- 一个对象只有虚引用,就跟完全没有被引用是一样的,随时会被回收
- 通过虚引用
get()
也无法获取被引用的对象
- 唯一目的在于跟踪垃圾回收过程,能在这个对象被收集器回收的时候收到一个系统通知
- 可以将一些资源释放操作,放在虚引用中执行操作
People people = new People("zhangsan", 13);
ReferenceQueue<People> phantomQueue = new ReferenceQueue<>();
PhantomReference<People> pf = new PhantomReference<>(people, phantomQueue);
people = null;
复制代码
- java中只有两种线程 用户线程、守护线程
- 垃圾回收线程就是一种守护线程
- 当没有用户线程时,虚拟机退出
public class PhantomReferenceTest {
static PhantomReferenceTest obj; //当前对象声明
static ReferenceQueue<PhantomReferenceTest> phantomQueue = null; //引用队列
public static class CheckRefQueue extends Thread {
@Override
public void run() {
while (true) {
if (phantomQueue != null) {
PhantomReference<PhantomReferenceTest> objt = null;
try {
objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();
} catch (Exception e) {
e.printStackTrace();
}
if (objt != null) {
System.out.println("虚引用被加入引用队列");
}
}
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize()...");
//复活
obj = this;
}
public static void main(String[] args) {
//启动线程检查引用队列
Thread t = new CheckRefQueue();
t.setDaemon(true); //设置为守护线程
t.start();
//初始化
phantomQueue = new ReferenceQueue<>();
obj = new PhantomReferenceTest();
PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<>(obj, phantomQueue);
try {
//null 获取不到虚引用的对象实例
System.out.println(phantomRef.get());
//去除强引用
obj = null;
//第一次gc,将虚引用标记为不可达,但是调用finalize()方法,导致其复活
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj is dead.");
} else {
System.out.println("obj is live.");
}
//第二次gc
obj = null;
System.gc();
Thread.sleep(1000);
if (obj == null) {
System.out.println("obj is dead.");
} else {
System.out.println("obj is live.");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
复制代码
终结器引用
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
复制代码
- 实现对象的
finalize()
方法
- 无需手动编码,内部配合引用队列使用
- 在GC时,终结器引用入队,由
Finalizer
线程,通过终结器找到被引用对象
并调用finalize()
方法,第二次GC时才能回收对象