Java内存分配与垃圾回收

2020-04-07  本文已影响0人  瑜小贤

垃圾回收

什么需要垃圾回收

栈:不需要,会随着线程的结束而消亡。
堆:重点关注,凡是共享的对象,理应需要回收
方法区/元空间:不用过多关注

堆内存分配策略

堆的进一步划分

VM参数配置:

  • -Xms 堆区内存出事内存分配的大小
  • -Xmx 堆区内存可被分配的最大上限
  • -XX:+PrintGCDetails 打印GC详情
  • -XX:+HeapDumpOnOutOfMemoryError 当堆内存空间溢出时,输出堆的内存快照
  • 新生代大小:-Xmn20m 表示新生代大小20m(初始和最大)
  • -XX:SurvivorRatio=8 表示Eden和Survivior的比值,缺省值为8,表示 Eden:From:To = 8:1:1

GC如何判断对象的存活

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 在方法没有(运行完)出栈前都是可达的
    }
}
可达性分析中的GCRoots

可达--->就回收不了

在Java,可作为GC Roots的对象包括(面试点)

  1. 方法区:类静态属性的对象
  2. 方法区:常量的对象
  3. 虚拟机栈(本地变量表)中引用的对象
  4. 本地方法栈JNI(Native方法)中引用的对象

各种引用(面试点)

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());
        }
    }
}

将要发生内存溢出异常之前,会回收软引用。如果这次回收之后还没有足够的内存,才会抛出内存溢出异常。
例子:使用内存展示图片

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

一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来获取一个对象的实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。虚引用和弱引用对关联对象的回收都不会产生影响,如果只有虚引用或者弱引用关联着对象,那么这个对象就会被回收。它们的不同之处在于弱引用的get方法,虚引用的get方法始终返回null,弱引用可以使用ReferenceQueue,虚引用必须配合ReferenceQueue使用。

垃圾回收算法

复制算法(Copying)

特点

(面试点)

  1. 全部采用复制回收算法(两个区)
  2. 90%的对象不需要主动回收 --> 10%的对象需要回收
  3. 空间担保:如果剩2个对象,From区、To区都放不下,是由老年代来担保的,放入老年代即可
标记-清除算法(Mark-Sweep)

特点

标记-整理算法(Mark-Compact)

特点

堆内存分配策略

/**
 * 堆中内存分配和回收
 * -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的空间)
    }

}

什么时候垃圾回收

即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]); //打印栈中的数据
    }
}
上一篇 下一篇

猜你喜欢

热点阅读