垃圾回收
什么需要垃圾回收
栈:不需要,会随着线程的结束而消亡。
堆:重点关注,凡是共享的对象,理应需要回收
方法区/元空间:不用过多关注
堆内存分配策略
堆的进一步划分
VM参数配置:
- -Xms 堆区内存出事内存分配的大小
- -Xmx 堆区内存可被分配的最大上限
- -XX:+PrintGCDetails 打印GC详情
- -XX:+HeapDumpOnOutOfMemoryError 当堆内存空间溢出时,输出堆的内存快照
- 新生代大小:-Xmn20m 表示新生代大小20m(初始和最大)
- -XX:SurvivorRatio=8 表示Eden和Survivior的比值,缺省值为8,表示 Eden:From:To = 8:1:1
- 新生代(PSYoungGen)
Eden空间
From Survivor空间
To Survivor空间 - 老年代(ParOldGen)
GC如何判断对象的存活
-
引用计数法:JVM早期使用,PHP还在用
A ==> B
C ==> B
缺点:相互引用时,很难判断是否该回收
A ==> B 同时 B ==> A
-
可达性分析(面试点)
public class GCRoots {
Object o = new Object();
static Object GCRoot1 = new Object(); //GC Roots
final static Object GCRoot2 = new Object();
//
public static void main(String[] args) {
//可达
Object object1 = GCRoot1; //=不是赋值,在对象中是引用,传递的是右边对象的地址
Object object2 = object1;
Object object3 = object1;
Object object4 = object3;
}
public void king(){
//不可达(方法运行完后可回收),疑问:回收时机?回收后别的方法引用到了呢?
Object object5 = o; //o不是GCRoots
Object object6 = object5;
Object object7 = object5;
}
//本地变量表中引用的对象
public void stack(){
Object ostack = new Object(); //本地变量表的对象
Object object9 = ostack;
//以上object9 在方法没有(运行完)出栈前都是可达的
}
}

可达--->就回收不了
在Java,可作为GC Roots的对象包括(面试点)
- 方法区:类静态属性的对象
- 方法区:常量的对象
- 虚拟机栈(本地变量表)中引用的对象
- 本地方法栈JNI(Native方法)中引用的对象
各种引用(面试点)
-
强引用 //new、 =
-
软引用 SoftReference
public class TestSoftRef {
public static class User{
public int id = 0;
public String name = "";
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
User u = new User(1,"King"); //new是强引用
SoftReference<User> userSoft = new SoftReference<User>(u);
u = null;//干掉强引用,确保这个实例只有userSoft的软引用
System.out.println(userSoft.get());
System.gc();//进行一次GC垃圾回收
System.out.println("After gc");
System.out.println(userSoft.get());
//往堆中填充数据,导致OOM
List<byte[]> list = new LinkedList<>();
try {
for(int i=0;i<100;i++) {
System.out.println("*************"+userSoft.get());
list.add(new byte[1024*1024*1]); //1M的对象
}
} catch (Throwable e) {
//抛出了OOM异常时打印软引用对象
System.out.println("Exception*************"+userSoft.get());
}
}
}
将要发生内存溢出异常之前,会回收软引用。如果这次回收之后还没有足够的内存,才会抛出内存溢出异常。
例子:使用内存展示图片
-
弱引用 WeakReference
public class TestWeakRef {
public static class User{
public int id = 0;
public String name = "";
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static void main(String[] args) {
User u = new User(1,"King");
WeakReference<User> userWeak = new WeakReference<User>(u);
u = null;//干掉强引用,确保这个实例只有userWeak的弱引用
System.out.println(userWeak.get());
System.gc();//进行一次GC垃圾回收
System.out.println("After gc");
System.out.println(userWeak.get());
}
}
在垃圾回收(发生GC)时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收,不论内存是否充足。
例子:THreadLocal
-
虚引用 PhantomReference
一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用或者弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。
垃圾回收算法
复制算法(Copying)

特点
- 实现简单、运行高效
- 内存复制、没有内存碎片
- 利用率只有一半
(面试点)
- 新生代使用
- 新生代中3个区的比例 8:1:1(为什么默认8:1:1)
因为:
- 全部采用复制回收算法(两个区)
- 90%的对象不需要主动回收 --> 10%的对象需要回收
- 空间担保:如果剩2个对象,From区、To区都放不下,是由老年代来担保的,放入老年代即可
标记-清除算法(Mark-Sweep)

特点
- 利用率100%
- 不需要内存复制
- 有内存碎片
标记-整理算法(Mark-Compact)

特点
- 利用率100%
- 没有内存碎片
- 需要内存复制
堆内存分配策略
-
对象优先在Eden区分配
/**
* 堆中内存分配和回收
* -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails
*/
public class EdenAllocation {
private static final int _1MB =1024*1024; //1M的大小
// * 对象优先在Eden分配
public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3,allocation4;
allocation1 = new byte[1*_1MB]; //根据信息可以知道 1M的数组大约占据 1.5M的空间(对象头,对象数据、填充)
allocation2 = new byte[1*_1MB];
allocation3 = new byte[1*_1MB];
allocation4 = new byte[1*_1MB];
}
}
-
大对象直接进入老年代
/**
* 堆中内存分配和回收-大对象直接进入老年代
* -Xms20m -Xmx20m -Xmn10m -XX:+PrintGCDetails
*/
public class BigAllocation {
private static final int _1MB =1024*1024; //1M的大小
// * 大对象直接进入老年代
public static void main(String[] args) {
byte[] allocation1,allocation2,allocation3;
allocation1 = new byte[2*_1MB]; //根据信息可以知道 2M的数组大约占据 3M的空间
allocation2 = new byte[3*_1MB];//大对象直接进入老年代(3M的数组大约占据 3M的空间)
}
}
-
长期存活的对象将进入老年代
-
动态对象年龄判定
并不是死板的判定age==15
动态对象年龄判定.png
- 空间分配担保
由老年区
什么时候垃圾回收
即GC的触发条件,控件不够了
- 新生代 Minor GC
- 老年代 Full GC
如何垃圾回收

简单的垃圾回收器工作示意图

CMS垃圾回收器工作示意图

G1收集器
G1收集器 JDK1.7才正式引入,采用分区回收的思维
基本补习生吞吐量的前提下完成低停顿的内存回收
可预测的停顿是起最大的优势
吞吐量 = CPU的时间100% GC10% 业务线程90% --> 吞吐量高
GC90% 业务线程10% --> 吞吐量低


Stop the World现象
就是由GC,尤其是Full GC引起的时间消耗,所谓暂停所有线程的过程,虽然时间很短,但仍然是“stop the world”
内存泄露与内存溢出的辨析
内存溢出OOM:需要分配的内存空间不够了、GC处理不了
内存泄漏:
/**
* 手写一个栈
*/
public class Stack {
public Object[] elements;
private int size =0;
private static final int Cap = 16;
public Stack() {
elements = new Object[Cap];
}
public void push(Object e){ //入栈
elements[size] = e;
size++;
}
public Object pop(){ //出栈
size = size -1;
Object o = elements[size];
//elements[size] = null; //让GC 回收掉,如果不置空就会发生内存泄漏!!!
return o;
}
}
/**
* 内存泄漏
*/
public class UseStack {
public static void main(String[] args) {
Stack stack = new Stack(); //new一个栈
Object o = new Object(); //new一个对象
System.out.println("o="+o);
stack.push(o); //入栈
Object o1 = stack.pop(); //出栈
System.out.println("o1="+o1);
System.out.println(stack.elements[0]); //打印栈中的数据
}
}
网友评论