美文网首页
Java内存分配与垃圾回收

Java内存分配与垃圾回收

作者: 瑜小贤 | 来源:发表于2020-04-07 17:39 被阅读0次

垃圾回收

什么需要垃圾回收

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

堆内存分配策略

堆的进一步划分

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

可达--->就回收不了

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

  1. 方法区:类静态属性的对象
  2. 方法区:常量的对象
  3. 虚拟机栈(本地变量表)中引用的对象
  4. 本地方法栈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)
    因为:
  1. 全部采用复制回收算法(两个区)
  2. 90%的对象不需要主动回收 --> 10%的对象需要回收
  3. 空间担保:如果剩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]); //打印栈中的数据
    }
}

相关文章

  • JVM-java内存区域与内存溢出异常

    JVM-java内存区域与内存溢出异常 1 说明 java 与 c++之间有一堵由内存动态分配和垃圾回收技术所围成...

  • Java内存分配与回收机制

    这篇文章主要讲Java内存的分配与回收机制,主要包括Java运行时的数据区域、对象的创建、垃圾收集算法与回收策略。...

  • Java内存分配与垃圾回收

    垃圾收集算法 一、 标记-清除算法(Mark-Sweep) 算法分为“标记”和“清除”两个阶段,首先标记出所有需要...

  • Java内存分配与垃圾回收

    垃圾回收 什么需要垃圾回收 栈:不需要,会随着线程的结束而消亡。堆:重点关注,凡是共享的对象,理应需要回收方法区/...

  • Java垃圾回收

    垃圾回收 Java内存分配 Java程序运行时内存分配有三种策略,分别是静态分配、栈式分配和堆式分配。三种分配方式...

  • Java垃圾回收

    本文主要摘自《深入理解Java虚拟机》,内容较多,尽量全面概括了 Java 垃圾回收机制、垃圾回收器以及内存分配策...

  • Java虚拟机内存分配与回收策略

    Java虚拟机中的内存分配与回收策略就是 Java的自动内存管理,其最核心的部分就是堆内存中对象的分配与回收。所以...

  • Java 虚拟机垃圾回收策略简要介绍

    垃圾回收是什么? Java 虚拟机垃圾回收是指对不使用的内存区域进行释放,防止分配空间时因内存不足而出现内存溢出异...

  • java内存模型与垃圾回收

    java内存模型与垃圾回收-luoxn28

  • 要点提炼| 理解JVM之GC

    有内存分配就会有内存回收,上篇也了解到Java堆是垃圾收集器管理的主要区域,本篇将理解这部分内存的垃圾回收机制。 ...

网友评论

      本文标题:Java内存分配与垃圾回收

      本文链接:https://www.haomeiwen.com/subject/eognphtx.html