【黑马JVM(2)】垃圾回收

JVM垃圾回收

  • 如何判断对象可以回收
    • 引用计数法
    • 可达性分析算法
    • 四种引用
  • 垃圾回收算法
    • 标记-清除
    • 标记-整理
    • 标记-复制
    • 分代垃圾回收
    • 相关VM参数
  • 垃圾回收器
    • 串行
    • 吞吐量优先
    • 响应时间优先
    • G1
      • 垃圾回收阶段
        • Young Collection
          • Young Collection跨代引用
        • Young Collection+CM
          • Remark-SATB
        • Mixed Collection
      • Full GC
      • G1垃圾回收优化
        • JDK 8u20字符串去重
        • JDK 8u40并发类卸载
        • JDK 8u60 回收巨型对象
        • JDK 9 并发标记起始时间的调整
  • 垃圾回收调优
    • 确认目标
    • 最快的GC是不发生 GC
    • 内存调优
      • 新生代调优
      • 老年代调优
    • GC调优案例

如何判断对象可以回收

引用计数法

只要一个对象被其他变量所引用,就让该对象的计数+1,如果引用了两次,计数就+2,如果某一个变量不再引用他了,计数-1,当该对象引用计数为0时,表示该对象未被引用,就可以当作垃圾回收。
存在弊端:
在这里插入图片描述
A对象引用B对象,B对象的引用计数为1,B对象反过来也引用A对象,A对象的引用计数也为1,造成循环依赖,两者一直相互引用,内存无法得到释放,从而导致内存泄漏。

可达性分析算法

Java 虚拟机中的垃圾回收器采用可达性分析来探索所有存活的对象。

  • 首先要确定一系列跟对象:肯定不能当成垃圾被回收的对象(GC Root对象)
  • 在垃圾回收之前,要对堆内存中的所有对象进行一遍扫描,看哪些对象被跟对象直接或间接引用,这些对象不能被回收,反之没有被跟对象直接或间接引用,就可以作为垃圾,将来被回收。(扫描堆中的对象,看是否能够沿着 GC Root对象 为起点的引用链找到该对象,找不到,表示可以回收)

哪些对象可以作为 GC Root ?
在这里插入图片描述
上图四个分类为:
系统类:由启动类加载器加载的类,核心的类,在运行期间肯定会用到的类(Object,HashMap…)
本地方法栈:Java虚拟机在执行方法时,必须调用操作系统方法,操作系统方法所引用的Java方法
活动线程:运行线程的栈帧中所引用的对象
正在加锁的对象:synchronized关键字对一个对象加了锁,被加锁的对象不能被回收

在Java计数体系里,可以作为GC Roots对象的可以分为以下几种:

  • Java虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量等。
    每个方法执行时,jvm都会创建一个对应的栈帧,每次函数调用调用都是一次入栈。在栈中包括局部变量表和操作数栈,局部变量表中的变量可能为引用类型(reference),他们引用的对象即可作为GC Root。不过随着函数调用结束出栈,这些引用便会消失。
  • 方法区中类静态属性引用的对象
    在类中使用的static声明的引用类型字段,加载类的时候就加载到内存中
  • 方法区中常量引用的对象,譬如字符串常量池里的引用
    使用final声明的引用类型字段
  • 本地方法栈中JNI(native方法)引用的对象
    程序中native本地方法引用的对象
  • Java虚拟机内部的引用,如基本数据类型对应的class对象,一些常驻的异常对象(比如NullPointException,OutOfMemoryError),还要系统类加载器。
  • 所有被同步锁(synchronized关键字)持有的对象
  • 反应Java虚拟机内部情况的JMXBean,JVMTI中注册的问题,本地代码缓存等。
    局部变量存放在活动栈帧里,局部变量的引用对象(new出来)的放在堆里。

四种引用

强引用(FinalReference)>软引用(SoftReference)>弱引用(WeakReference)>虚引用(PhantomReference)
在这里插入图片描述

  1. 强引用:Java中默认就是强引用,new了一个Object对象,并将其赋值给obj,这个obj就是new Object()的强引用。
Object obj = new Object();

无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

  1. 软引用:软引用是用于描述一些有用但非必须的引用。
  • SoftReference的实例保存一个Java对象的软引用,只有在内存不足的情况下,并且没有其他强引用被引用的对象才会被回收,内存够用是不会被回收的。
  • 当软引用所引用对象被回收时,软引用会进入引用队列,对软引用自身所占内存进行释放。
  1. 弱引用:也是用来描述些非必须对象,但是强度比软引用更弱一些。
  • 弱引用引用的对象且没有其他强引用被引用,只要垃圾回收执行,就会被回收,不管是否内存不足。
  • 弱引用的回收也可以配合引用队列来释放弱引用自身。
    在这里插入图片描述
  1. 虚引用:最弱的一种引用关系
  • 虚引用是无法通过get方法来获取的。一个虚引用对象被回收时会被放在一个ReferenceQueue队列中,意思就是虚引用回收时会给出一个信号放在队列中。
  • 必须配合引用队列使用,主要配合 ByteBuffer 使用,被引用对象回收时,会将虚引用入队,由 Reference Handler 线程调用虚引用相关方法释放直接内存
    • 一个对象是否有虚引用的存在,完全不会对其生存时间时间构成影响,也无法通过虚引用来取得一个对象实例。
    • 为一个对象设置虚引用关联的唯一目的是为了能在这个对象被收集器回收时得到一个系统通知
    • 虚引用对象在创建时会关联一个引用队列,创建ByteBuffer实现类对象时,会创建一个Cleaner虚引用对象,ByteBuffer会分配一块直接内存,并且把直接内存地址传递给虚引用对象。
    • 当没有强引用引用ByteBuffer时,ByteBuffer被垃圾回收掉,直接内存并不能被Java所管理,所以在ByteBuffer被回收时,让虚引用对象进入引用队列(ReferenceQueue),虚引用所在的队列会由一个Reference Handler线程定时去引用队列查询有没有新入队列的cleaner,如果 有,会调用cleaner中的clean方法(会根据记录的直接内存中地址调用Unsafe.freeMemory释放直接内存)
      在这里插入图片描述
  1. 终结器引用(FinalReference)
  • 无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再有Finalizer线程通过终结器引用找到被引用对象并调用它的finalize方法,第二次GC时才能回收被引用对象。
    • 所有的类对象都会继承Object类,类中有finalize终结方法,当对象重写终结方法且没有强引用引用时,可以当作垃圾被回收。
    • 虚拟机会帮对象创建对应的终结器引用,在垃圾回收时,将终结器引用加入引用队列(被引用对象暂时没有被回收),再由一个优先级很低的线程(Finalizer Handler),定时去引用队列查询有没有新入队列的终结器引用,通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时回收掉被引用对象。
      在这里插入图片描述

-Xmx20m -XX:+PrintGCDetails -verbose:gc 设置堆内存最大值为20m,打印GC详细信息

在这里插入图片描述
强引用时,会造成堆内存溢出

    public static void main(String[] args) throws IOException {
        List<byte[]> list = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            list.add(new byte[_4MB]);
        }

        System.in.read();
    }

在这里插入图片描述
list先引用软引用对象,再间接引用byte[]

 public static void main(String[] args) throws IOException {
   // list --> SoftReference --> byte[]
  List<SoftReference<byte[]>> list = new ArrayList<>();
       for (int i = 0; i < 5; i++) {
           SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB]);		
           System.out.println(ref.get());
           list.add(ref);
           System.out.println(list.size());
        }   
     System.out.println("循环结束:" + list.size());
        for (SoftReference<byte[]> ref : list) {
            System.out.println(ref.get());
        }
    }

由GC信息可知,在第5次添加的时候,内存已经不够,在一次完全的垃圾回收后,内存空间任然不够,又触发了一次新的内存回收,将软引用的内存回收
在这里插入图片描述
配合引用队列,将软引用对象清理掉

public class Demo2_4 {
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        List<SoftReference<byte[]>> list = new ArrayList<>();

        // 配合引用队列,将软引用清理
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();

        for (int i = 0; i < 5; i++) {
            // 关联了引用队列, 当软引用所关联的 byte[]被回收时,软引用自己会加入到 queue 中去
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
            System.out.println(ref.get());
            list.add(ref);
            System.out.println(list.size());
        }

        // 从队列中获取无用的 软引用对象,并移除
        Reference<? extends byte[]> poll = queue.poll();
        while( poll != null) {
            list.remove(poll);
            poll = queue.poll();
        }

        System.out.println("===========================");
        for (SoftReference<byte[]> reference : list) {
            System.out.println(reference.get());
        }

    }
}

在这里插入图片描述
弱引用示例:

-Xmx20m -XX:+PrintGCDetails -verbose:gc

public class Demo2_5 {
    private static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        //  list --> WeakReference --> byte[]
        List<WeakReference<byte[]>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            WeakReference<byte[]> ref = new WeakReference<>(new byte[_4MB]);
            list.add(ref);
            for (WeakReference<byte[]> w : list) {
                System.out.print(w.get()+" ");
            }
            System.out.println();

        }
        System.out.println("循环结束:" + list.size());
    }
}

在第5次添加时,内存不够了,回收掉第4个,才能添加到第5个,…第10次是因为弱引用本身也占内存,放不下时,进行了Fll GC,将弱引用全部清空。
在这里插入图片描述

垃圾回收算法

标记-清除

标记-清除算法分为“标记”和“清除”两个阶段,首先通过可达性分析,标记出所有需要回收的对象,然后统一回收所有被标记的对象。
在这里插入图片描述

  • 优点: 不需要做额外处理,清除速度快
  • 缺点:会造成内存碎片,后续可能发生大对象不能找到可利用空间的问题。

标记-整理

标记-整理算法的“标记”过程与“标记-清除算法”的标记过程一致,但标记之后不会直接清理。而是将所有存活对象都移动到内存的一端。移动结束后直接清理掉剩余部分。
在这里插入图片描述

  • 优点:没有内存碎片
  • 缺点:效率低,速度慢

标记-复制

将内存分成两块,每次申请内存时都使用其中的一块,当内存不够时,将这一块内存中所有存活的复制到另一块上。然后将然后再把已使用的内存整个清理掉。

在这里插入图片描述
在这里插入图片描述

  • 优点:没有内存碎片
  • 缺点:可用空间减半

分代垃圾回收

长时间使用的对象放在老年代中,用完可以丢弃的对象放在新生代中。老年代的垃圾回收很久发生一次,新生代的垃圾回收发生的比较频繁。

  • 新的对象默认分配在伊甸园区域,不断添加新对象, 当伊甸园内存不足时,触发minor gc垃圾回收。
  • minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行
    在这里插入图片描述
  • 通过可达性分析沿着GC Root引用链,看是否可以作为垃圾,采用标记复制算法,将伊甸园和幸存区from存活的对象复制到幸存区To,存活的对象寿命加 1,回收掉伊甸园和from标记为垃圾的对象,并且交换幸存区from和to。
    在这里插入图片描述
    在这里插入图片描述
  • 当新的对象再次将伊甸园填满时,触发第二次垃圾回收,找到伊甸园中存活的对象放入幸存区To中,寿命+11,将幸存区From中存活的对象放入幸存区To中,寿命再+1,不要的对象回收掉,交换幸存区from和to。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 当寿命超过了阈值(默认15(4bit)),说明对象经常被使用,将垃圾晋升到老年代。
  • 当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc(整个清理),STW的时间更长

相关VM参数

含义参数
堆初始大小-Xms
堆最大大小-Xmx或 -XX:MaxHeapSize=size
新生代大小-Xmn或 -XX:NewSize=size + -XX:MaxNewSize=size(初始最大同时指定)
幸存区比例(动态)-XX:InitialSurvivorRatio=ratio和-XX:+UserAdaptiveSizePolicy(开启)
幸存区比例-XX:SurvivorRatio=ratio(默认8,如果新生代为10,伊甸园为8,to和from各自2)
晋升阈值-XX:MaxTenuringThreshold=threshold()
晋升详情-XX:+PrintTenuringDistribution打印晋升详情
GC详情-XX:+PrintGCDetails -verbose:gc打印GC详情
FullGC前MinorGC-XX:+ScavengeBeforeFullGC 默认打开

GC案例分析
设置参数:

-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc

public class Demo2_1 {
    private static final int _512KB = 512 * 1024;
    private static final int _1MB = 1024 * 1024;
    private static final int _6MB = 6 * 1024 * 1024;
    private static final int _7MB = 7 * 1024 * 1024;
    private static final int _8MB = 8 * 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
    }
}

在这里插入图片描述

public class Demo2_1 {
    private static final int _512KB = 512 * 1024;
    private static final int _1MB = 1024 * 1024;
    private static final int _6MB = 6 * 1024 * 1024;
    private static final int _7MB = 7 * 1024 * 1024;
    private static final int _8MB = 8 * 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_7MB]);
            list.add(new byte[_512KB]);
    }
}

在这里插入图片描述

public class Demo2_1 {
    private static final int _512KB = 512 * 1024;
    private static final int _1MB = 1024 * 1024;
    private static final int _6MB = 6 * 1024 * 1024;
    private static final int _7MB = 7 * 1024 * 1024;
    private static final int _8MB = 8 * 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_8MB]);
    }
}

在这里插入图片描述

public class Demo2_1 {
    private static final int _512KB = 512 * 1024;
    private static final int _1MB = 1024 * 1024;
    private static final int _6MB = 6 * 1024 * 1024;
    private static final int _7MB = 7 * 1024 * 1024;
    private static final int _8MB = 8 * 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_8MB]);
            list.add(new byte[_8MB]);
    }
}

在这里插入图片描述

public class Demo2_1 {
    private static final int _512KB = 512 * 1024;
    private static final int _1MB = 1024 * 1024;
    private static final int _6MB = 6 * 1024 * 1024;
    private static final int _7MB = 7 * 1024 * 1024;
    private static final int _8MB = 8 * 1024 * 1024;

    public static void main(String[] args) throws InterruptedException {
       new Thread(() -> {
            ArrayList<byte[]> list = new ArrayList<>();
            list.add(new byte[_8MB]);
            list.add(new byte[_8MB]);
        }).start();

        System.out.println("sleep....");
        Thread.sleep(1000L);
    }
}

一个线程内的OutOfMemory,不会导致Java进程结束
在这里插入图片描述

垃圾回收器

串行

单线程的收集器,说明它只会使用一个CPU或一条收集线程区完成垃圾收集工作,并且在它进行垃圾收集时,必须暂停其他所有所有的工作线程,直到它收集结束。
优势:简单而高效(与其他收集器的单线程比),对于限定单个CPU的环境来说,Serial收集器由于没有线程交互的开销,专心做垃圾收集,自然可以获得最高的单线程收集效率。
场景:适合堆内存较小,个人电脑

-XX:+UserSerialGC =Serial +SerialOld 指定年轻代和老年代都使用串行收集器
等价于新生代用Serial GC(复制算法),老年代用Serial Old GC(标记+整理算法)

用户工作的线程,在安全点停下来
在这里插入图片描述

吞吐量优先

多线程的收集器,主要让单位时间内,STW(垃圾收集器最大停顿时间) 的时间最短(0.2+0.2 = 0.4),可以高效地利用CPU时间,尽快完成程序的运算任务(垃圾回收时间占比最低,这样就称吞吐量高)。
场景:堆内存较大,多核 cpu来支持(单核,也是多个线程轮流争抢单核CPU的时间片,效率更低),适合在后台运算而不需要太多交互的任务。parallel并行,指多个垃圾回收器可以并行的运行,占用不同的cpu。但是在此期间,用户线程是被暂停的,只有垃圾回收线程在运行。

-XX:+UseParallelGC 手动指定年轻代使用Parallel并行收集器执行内存回收任务(复制算法)

-XX:+UseParallelOldGC 手动指定老年代使用并行回收收集器(标记+整理算法)
jdk8默认是开启的.上面两个参数,默认开启一个,另一个也会被开启(互相激活)

-XX:+UseAdaptiveSizePolicy:自适应调整新生代大小(新生代占比和晋升阈值大小)

-XX:ParallelGCThreads:设置年轻代并行收集器的线程数,
最好与CPU数量相等,以避免过多的线程数影响垃圾收集性能,在默认情况下,CPU数量小于8, ParallelGCThreads的值等于CPU数量,当CPU数量大于8,ParallelGCThreads的值等于3+(5*CPU_COUNT/8)

-XX:GCTimeRatio:垃圾收集时间占总时间的比例(=1/(N+1)),用于衡量吞吐量的大小
取值范围(0,100),默认99,也就是垃圾回收时间不超过1%,很难达到,一般设置为19,即100分钟只允许5分钟垃圾回收
与-XX:MaxGCPauseMillis参数有一定矛盾性,暂停时间越长,Radio参数就越容易超过设定的比例

-XX:MaxGCPauseMillis 设置垃圾收集器最大停顿时间(即STW的时间)单位是毫秒(该参数使用需谨慎)
为了尽可能地把停顿时间控制在MaxGCPauseMillis以内,收集器在工作时会调整Java堆大小或者其他一些参数
对于用户来讲,停顿时间越短体验越好,但是在服务器端,我们注重高并发,整体的吞吐量,所以服务器端适合Parallel,进行控制

在这里插入图片描述

响应时间优先

多线程
场景:堆内存较大,多核 cpu
尽可能让单次 STW 的时间最短 0.1+0.1+0.1+0.1+0.1 = 0.5

-XX:+UseConcMarkSweepGC(老年代,标记清除算法) ~ -XX:+UseParNewGC ~ SerialOld (新生代,复制算法)
concurrent 并发(垃圾回收器进行垃圾回收时,其他用户线程也可以并发进行,与垃圾回收线程抢占cpu)mark标记,sweep清除()

-XX:ParallelGCThreads=n 并行的垃圾回收线程数,一般跟cpu数目相等
-XX:ConcGCTreads=threads 并发的垃圾回收线程数目,
一般是ParallelGCThreads的 1/4,即一个cpu做垃圾回收,剩下3个cpu留给人家用户线程。

-XX:CMSInitiatingOccupancyFraction=percent,开始执行CMS垃圾回收时的内存占比,
早期默认65,即只要老年代内存占用率达到65%的时候就要开始清理,留下35%的空间给新产生的浮动垃圾。

-XX:+CMSScavengeBeforeRemark 在重新标记之前,对新生代做一次垃圾回收

在这里插入图片描述

CMS回收器发生并行失败时,CMS回收器会退化成SerialOld的单线程的基于标记整理的垃圾回收器。

CMS老年代回收过程

  • 当老年代空间不足时,所有进程运行到安全点暂停,垃圾回收的线程进行初始标记,初始标记比较快,只是标记根对象。此过程会Stop The World,阻塞其他用户线程。
  • 初始标记完成以后,达到下一个安全点,其他用户线程也可以继续运行了,此时垃圾回收线程进行并发标记,即可以跟其他用户线程并发工作,将其他垃圾标记出来。此过程不会STW,响应时间很短,几户不影响用户线程工作。
  • 达到下一个安全点后,进行重新标记,因为上一个并发标记时,其他用户线程也在并发执行,有可能会产生新对象新引用,对垃圾回收线程造成了干扰,需要重新标记。此过程会STW
  • 到下一个安全点后,其他用户进程恢复,垃圾回收线程开始并发地清理垃圾,恢复运行。

整个工作阶段只会在初始标记和重新标记的时候STW,其他阶段并发执行,响应时间特别短。

CMS垃圾回收器

  • CMS垃圾回收器对cpu的占用率并不高,但是用户工作线程也在运行,垃圾回收线程占用用户线程的工作线程,整个应用程序的吞吐量小了。
  • CMS在执行最后一步并发清理的时候,由于其他线程还在运行,就会产生新的垃圾,而新的垃圾只有等到下次垃圾回收才能清理了。这些垃圾被称为浮动垃圾,所以要预留一些空间来存放浮动垃圾。
  • 重新标记阶段,新生代的对象可能会引用老年代的对象,重新标记时需要扫描整个堆,做可达性分析时,只要新生代的引用存在,不管有没有必要,都会通过新生代引用找到老年代,对性能影响有些大。因为新生代对象很多,且很多要作为垃圾被回收。可达性分析又会通过新生代引用去找老年代,但是就算找到了老年代,这些新生代还是要被回收,也就是说没有必要查找老年代。所以需要在重新标记之前,先回收新生代(-XX:+CMSScavengeBeforeRemark参数设置),就不会存在新生代引用老年代,然后去查找老年代了。新生代的垃圾回收(通过-XX:+UseParNewGC)之后,新生代对象少了,重新标记的压力就轻了。
  • 因为CMS基于标记清除算法,有可能会产生比较多的内存碎片。这样会造成将来给对象分配空间时minorGC后内存空间不足,老年代的空间也不足,会造成并发失败。CMS就会退化成SerialOld串行地垃圾回收,通过标记整理碎片来得到空间。但是会导致垃圾回收的时间变得很长(要整理),给用户造成不好的体验。

G1

定义:Garbage First
2004 论文发布
2009 JDK 6u14 体验
2012 JDK 7u4 官方支持
2017 JDK 9 默认,取代CMS垃圾回收器
在这里插入图片描述
适用场景:

  • 同时注重吞吐量(Throughput)和低延迟(Low latency),默认的暂停目标是 200 ms,在用户线程工作的同时,垃圾回收线程也在并发的执行
  • 超大堆内存,会将堆划分为多个大小相等的 Region(区域,1248M)
  • 整体上是 标记+整理 算法,两个区域之间是 复制 算法

-XX:+UseG1GC 显示启动G1
-XX:G1HeapRegionSize=size 设置区域大小
-XX:MaxGCPauseMillis=time 设置暂停目标

在这里插入图片描述

垃圾回收阶段

在这里插入图片描述

新生代垃圾收集:同样叫Minor GC(Young GC)发生时机就是Eden区满的时候
新生代垃圾收集+并发标记:当老年代内存超过阈值时,在新生代垃圾回收的同时,并发标记
混合收集:不只清理年轻代,还会将老年代的一部分区域进行清理

Young Collection

Young GC主要是对Eden区进行GC,它在Eden空间耗尽时会被触发。在这种情况下,Eden空间的数据移动到Survivor空间中,如果Survivor空间不够,Eden空间的部分数据会直接晋升到年老代空间。Survivor区的数据移动到新的Survivor区中,也有部分数据晋升到老年代空间中。最终Eden空间的数据为空,GC停止工作,应用线程继续执行。
在这里插入图片描述

Young Collection跨代引用

新生代垃圾回收时,先找到GC Root对象,进行可达性分析算法,找到存活对象,存活对象复制到幸存区。
那如何找到所有的根对象呢? 根对象有一部分来自于老年代,老年代存活的对象特别多,如果遍历一遍老年代去寻找根对象,那这样扫描下来会耗费大量的时间。G1引进了RSet的概念。它的全称是Remembered Set,作用是跟踪指向某个heap区内的对象引用。
在这里插入图片描述
上图中region2的RSet记录了两个引用到本region内对象的关系

  • 每个region都有自身对应的一个记忆集RSet

  • 每次引用类型数据写操作的时候,都会产生一个写屏障(post-write barrier)暂时的中断操作

  • 检查将要写入的引用指向的对象是否和该引用类型数据在不同的region(其他收集器将会检查老年代对象是否引用了新生代对象,是,标记为dirty card)

  • 如果不同,通过CardTable把相关引用信息记录到引用指向对象所在的region对象的RSet中
    在这里插入图片描述
    老年代维护采用card table技术,将老年代区域再细分为card(上图右侧橙色区域),每个card大约为512k,如果老年代对象引用了新生代,对应的card标记为dirty card(粉色区域),在做GC Root遍历时,不需要找整个老年代,只需关注dirty card区域,减少扫描范围,提高搜索效率。

  • 当堆新生代进行回收时,通过Remembered Set记录找到对应的dirty card,然后在dirty card区域遍历Region的GC Root

Young Collection+CM

  • 当堆空间的内存占用达到阈值(-XX:InitiatingHeapOccupancyPercent,默认45%)就开始老年代的并发标记过程。
  • 初始标记阶段:标记GC Roots直接可达的对象,也就是直接引用关系对象,会发生STW(由于是直接可达的对象的标记,所以暂停时间很短),并且会触发一次Young GC。
  • 根区域的扫描(Root Region Scanning):G1扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一个过程必须在Young GC之前完成(因为Young GC会操作Survivor区中的对象)。
  • 并发标记(Concurrent Marking):在整个堆中进行并发标记(与程序线程并发执行),此过程可能会被Young GC打断,在并发标记阶段中,若发现某些region中所有对象都是垃圾,那这个region就会被立即回收,同时并发标记过程中,会计算每个region的对象活性(该region存活对象的比例,G1垃圾回收的时候并不是所有region都会参与回收的,根据回收的价值高低来优先回收价值较高的region)。
  • 再次标记:由于并发标记阶段是收集器的标记线程和程序线程并发执行的,需要进行再次标记,修正上一次的标记结果,可以理解为增量补偿标记。会出现STW(暂停时间较短)。G1采用的是比CMS跟快的初始快照算法:snapshot-at-the-beginning(SATB)。
  • 独占清理:计算各个region的存活对象和GC回收比例,并进行排序(回收价值高低排序),识别可以混合回收的区域。为下阶段做铺垫,会发生STW。需要注意的是这个阶段实际上并不会做垃圾的回收。
  • 并发清理阶段:识别并清理完成空闲的区域。
    在这里插入图片描述
Remark-SATB

SATB全称是Snapshot-At-The-Beginning,由字面理解,是GC开始时活着的对象的一个快照。它是通过Root Tracing得到的,作用是维持并发GC的正确性。那么它是怎么维持并发GC的正确性的呢?根据三色标记算法,我们知道对象存在三种状态:白:对象没有被标记到,标记阶段结束后,会被当做垃圾回收掉。灰:对象被标记了,但是它的field还没有被标记或标记完。黑:对象被标记了,且它的所有field也被标记完了。

在这里插入图片描述

SATB 利用 write barrier(写屏障) 将所有即将被删除的引用关系的旧引用记录下来(加入到队列中),标记为灰色,最后以这些旧引用为根 Stop The World 地重新扫描一遍即可避免漏标问题。

在这里插入图片描述
因此G1 Remark阶段 Stop The World 与 CMS了的remark有一个本质上的区别,那就是这个暂停只需要扫描有 write barrier 所追中对象为根的对象, 而 CMS 的remark 需要重新扫描整个根集合,因而CMS remark有可能会非常慢。

Mixed Collection

混合式垃圾回收,每次- 收集既可能只收集年轻代分区(年轻代收集),也可能在收集年轻代的同时,包含部分老年代分区(混合- 收集),这样即使堆内存很大时,也可以限制收集范围,从而降低停顿。

G1有一个参数:“-XX:InitiatingHeapOccupancyPercent”,默认值是45%,当老年代的大小占据了堆内存的45%的Region时,此时就会触发一个新生代和老年代的混合回收阶段,对E S 0 H进行全面回收。

该阶段一旦触发会导致系统进入STW,同时进行最后一个标记:

  • 最终标记阶段:会根据并发标记阶段记录的对象修改,最终标记哪些对象是存活,哪些对象是垃圾。

此时老年代也是根据标记-复制算法来进行回收的,会将标记存活的对象拷贝到新的Region中作为老年代区域:

  • 标记完成后马上开始垃圾的回收。对于一个混合的回收过程,G1从老年代移动存活的对象到空闲区域,这些空闲的区域变成了老年代region。当越来越多的对象晋升到老年代region的时候,为了避免堆内存被耗尽,就会触发混合垃圾收集Mixed GC,该算法并不是一个Old GC也不是Full GC,除了回收整个Young region之外,还会回收一部分Old region,部分的region垃圾回收设计可以对垃圾回收的耗时进行控制。
  • 在并发标记结束之后,老年代中能够完全确认为垃圾的region中的内存分段被回收了,部分为垃圾的region中内存分段也被计算出来了,默认情况下,这些老年代的内存分段会被分为8次回收(可以通过-XX:G1MixedGCCountTarget设置)。
  • 混合回收的回收集包括1/8的老年代的内存分段,Eden区内存分段,Survivor内存分段,混合回收的算法和年轻代回收的算法完全一致。
  • 混合回收并不一定要进行8次,有一个阈值设置:-XX:G1HeapWastePercent,默认值10%,代表允许整个堆内存中有10%的内存可以被浪费,意味着如果发现可以回收的垃圾占对内存的比例低于10%,则不进行混合回收,因为GC花费的时间相对于较少的垃圾回收来说得不偿失。
  • 由于老年代的内存分段默认分为8次回收,G1会优先回收垃圾多的内存分段,垃圾占内存分段比例越高的会优先被回收。并且有一个阈值决定内存分段是否被回收:-XX:G1MixedGCLiveThresholdPercent,默认为65%,代表垃圾占内存分段比例要达到65%来回被回收,如果垃圾占比太低,意味着存活的对象多,复制算法就会花费更多的时间区复制存活的对象。
  • 必要的情况下(对象分配速度远大于回收速度),Full GC仍然会触发(Full GC的成本较高,单线程,性能差,STW时间长)
  • 堆内存太小、对象分配速度远大于回收速度等原因都可以导致G1在复制存活对象的时候没有空闲的内存分段可用,最终造成Full GC的触发。
    在这里插入图片描述

Full GC

SerialGC
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
ParallelGC
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足发生的垃圾收集 - full gc
CMS
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足时,垃圾回收速度低于产生速度时候,并发失败,退化为单线程SerialGC串行执行,为full GC,否则不是。
G1
新生代内存不足发生的垃圾收集 - minor gc
老年代内存不足:超过阈值时先并发标记再混合收集,当回收速度高于新的用户线程产生垃圾的速度,处于并发垃圾收集。当垃圾回收速度低于新产生的垃圾速度,退化为full GC,响应时间较长。

G1垃圾回收优化

JDK 8u20字符串去重

-XX:+UseStringDeduplication 开启字符串去重功能,默认打开

在这里插入图片描述
会将所有新分配的字符串放入一个队列,当新生代回收时,G1并发检查是否有字符串重复,如果它们值一样,让它们引用同一个 char[]。s1和s2引用的是堆中的两个不同的对象,只不过那两个对象都指向同一个字符串而已,所以s1!= s2。

优点:节省大量内存
缺点:略微多占用了 cpu 时间,新生代回收时间略微增加

注意: 与 String.intern() 不一样
String.intern() 关注的是字符串对象,而字符串去重关注的是 char[],在 JVM 内部,使用了不同的字符串表。

JDK 8u40并发类卸载

之前版本jdk的类,一般是不卸载的,类加载之后,会一直占用内存。
在所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类都不再使用,则卸
载它所加载的所有类。

-XX:+ClassUnloadingWithConcurrentMark 默认启用

卸载条件:
类的实例都被回收掉
类所在的类加载器其中的所有类都不再使用了

JDK 8u60 回收巨型对象

当一个对象大于 region 的一半时,称之为巨型对象。
G1 不会对巨型对象进行拷贝,回收时被优先考虑。
G1 会跟踪老年代所有 incoming 引用,这样老年代 incoming 引用为0 的巨型对象就可以在新生代垃圾回收时处理掉。
在这里插入图片描述

JDK 9 并发标记起始时间的调整

为了减少Full GC,可以提前让并发标记,混合收集提前开始。
JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent 老年代在整个堆内存的占比的阈值,超过时,并发垃圾回收开始,默认45%

JDK 9 可以动态调整 -XX:InitiatingHeapOccupancyPercent 用来设置初始值,在垃圾回收过程中,进行数据采样并动态调整阈值,会添加一个安全的空档空间,减少Full GC产生机率。

垃圾回收调优

参考:GC如何调优
查看虚拟机运行参数:java -XX:+PrintFlagsFinal -version | findstr “GC”(查看本地虚拟机与GC相关的参数)

PS D:\java\idea\IdeaProject2\jvm\out> java -XX:+PrintFlagsFinal -version | findstr "GC"
java version "1.8.0_66"
Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)
    uintx AdaptiveSizeMajorGCDecayTimeScale         = 10                                  {product}
    uintx AutoGCSelectPauseMillis                   = 5000                                {product}
     bool BindGCTaskThreadsToCPUs                   = false                               {product}
    uintx CMSFullGCsBeforeCompaction                = 0                                   {product}
    uintx ConcGCThreads                             = 0  CMS并发线程数,默认0                                 {product}
     bool DisableExplicitGC                         = false                               {product}
     bool ExplicitGCInvokesConcurrent               = false                               {product}
     bool ExplicitGCInvokesConcurrentAndUnloadsClasses  = false                               {product}
    uintx G1MixedGCCountTarget                      = 8                                   {product}
    uintx GCDrainStackTargetSize                    = 64                                  {product}
    uintx GCHeapFreeLimit                           = 2                                   {product}
    uintx GCLockerEdenExpansionPercent              = 5                                   {product}
     bool GCLockerInvokesConcurrent                 = false                               {product}
    uintx GCLogFileSize                             = 8192                                {product}
    uintx GCPauseIntervalMillis                     = 0                                   {product}
    uintx GCTaskTimeStampEntries                    = 200                                 {product}
    uintx GCTimeLimit                               = 98                                  {product}
    uintx GCTimeRatio                               = 99       GC时间占比                           {product}
     bool HeapDumpAfterFullGC                       = false                               {manageable}
     bool HeapDumpBeforeFullGC                      = false                               {manageable}
    uintx HeapSizePerGCThread                       = 87241520                            {product}
    uintx MaxGCMinorPauseMillis                     = 4294967295                          {product}
    uintx MaxGCPauseMillis     最大GC停止时间目标      = 4294967295                          {product}
    uintx NumberOfGCLogFiles                        = 0                                   {product}
     intx ParGCArrayScanChunk                       = 50                                  {product}
    uintx ParGCDesiredObjsFromOverflowList          = 20                                  {product}
     bool ParGCTrimOverflow                         = true                                {product}
     bool ParGCUseLocalOverflow                     = false                               {product}
    uintx ParallelGCBufferWastePct                  = 10                                  {product}
    uintx ParallelGCThreads                         = 13                                  {product}
     bool ParallelGCVerbose                         = false                               {product}
     bool PrintClassHistogramAfterFullGC            = false                               {manageable}
     bool PrintClassHistogramBeforeFullGC           = false                               {manageable}
     bool PrintGC                                   = false                               {manageable}
     bool PrintGCApplicationConcurrentTime          = false                               {product}
     bool PrintGCApplicationStoppedTime             = false                               {product}
     bool PrintGCCause                              = true                                {product}
     bool PrintGCDateStamps                         = false                               {manageable}
     bool PrintGCDetails                            = false                               {manageable}
     bool PrintGCID                                 = false                               {manageable}
     bool PrintGCTaskTimeStamps                     = false                               {product}
     bool PrintGCTimeStamps                         = false                               {manageable}
     bool PrintHeapAtGC                             = false                               {product rw}
     bool PrintHeapAtGCExtended                     = false                               {product rw}
     bool PrintJNIGCStalls                          = false                               {product}
     bool PrintParallelOldGCPhaseTimes              = false                               {product}
     bool UseGCOverheadLimit                        = true                                {product}
     bool UseGCTaskAffinity                         = false                               {product}
     bool UseMaximumCompactionOnSystemGC            = true                                {product}
     bool UseParNewGC                               = false                               {product}
     bool UseParallelGC                            := true                                {product}
     bool UseParallelOldGC                          = true                                {product}
     bool UseSerialGC                               = false                               {product}

掌握相关工具:jmap,jconsole,jstat查看GC相关状态

调优不仅仅从内存GC,还应该考虑线程堆锁的竞争,CPU的占用,以及IO的调用,网络延迟,软硬件的考虑。

确认目标

对于 GC 调优来说,首先就需要清楚调优的目标是什么?要清楚自己的应用程序是做什么的,如果是做科学运算,就要关注高吞吐量,如果是互联网项目,就要追求低延迟,提高用户体验。

  • 高吞吐量:ParallelGC
  • 低延迟:CMS(不推荐),G1,ZGC(java12)

GC调优从性能的角度看,通常关注三个方面,内存占用(footprint)、延时(latency)和吞吐量(throughput)。大多数情况下调优会侧重于其中一个或者两个方面的目标,很少有情况可以兼顾三个不同的角度。也可能需要考虑其他 GC 相关的场景,例如,OOM 也可能与不合理的 GC 相关参数有关;或者,应用启动速度方面的需求,GC 也会是个考虑的方面。

最快的GC是不发生 GC

查看 FullGC 前后的内存占用,考虑下面几个问题

  • 数据是不是太多?
    • "select * from 大表 " 会将所有的数据从mysql中查出来读入到java内存
    • "select * from 大表 limit n " 限制数量
  • 数据表示是否太臃肿?
    • 对象图 一次性取出对象相关联不用的数据
    • 对象大小对内存占用 Java中最小的Object占用16byte (Integer 16 int 4)
  • 是否存在内存泄漏?
    • static Map map = …不断的往静态的map对象中存放对象,会造成内存溢出
    • 长时间存活的对象推荐使用,软/弱引用
    • 不推荐Java实现缓存,建议第三方缓存实现redis/memorycacehe

内存调优

新生代调优

新生代的特点:

  • new对象首先在伊甸园中分配,分配速度特别快
    • 每个线程都会在内存中分配一块私有区域(TLAB thread-local allocation buffer线程局部分配缓冲区),当new一个对象时,首先会检查TLAB中有没有可用内存,有的话,优先在TLAB中进行对象分配,可以避免多个线程同时创建对象时对内存占用的干扰。
  • 死亡对象的回收代价是零
    • 新生代发生垃圾回收时,采用复制算法(伊甸园+幸存区from–>幸存区to),复制完后,伊甸园和幸存区from的内存都被释放掉。
  • 大部分对象用过即死
  • Minor GC 的时间远远低于 Full GC

新生代内存越大越好吗?
-Xmn
Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). GC is
performed in this region more often than in other regions. If the size for the young generation is
too small, then a lot of minor garbage collections are performed. If the size is too large, then only
full garbage collections are performed, which can take a long time to complete. Oracle
recommends that you keep the size for the young generation greater than 25% and less than
50% of the overall heap size.
【设置新生代的初始大小和最大大小(以字节为单位)。GC是在该区域比在其他区域更频繁地执行。如果年轻一代太小,则会执行触发多次minor GC。如果尺寸太大,仅执行新生代的垃圾收集,可能需要很长时间。建议新生代保持占堆的25%~50%。】

新生区大小建议:

  • 新生代能容纳所有【并发量 * (请求+响应)】的数据
  • 幸存区大到能保留【当前活跃对象+需要晋升对象】
    • 晋升阈值配置得当,让长时间存活对象尽快晋升(不然会耗费幸存区的内存,而且不断的复制)

-XX:MaxTenuringThreshold=threshold 调整最大晋升阈值
-XX:+PrintTenuringDistribution 打印晋升区的存活对象

Desired survivor size 48286924 bytes, new threshold 10 (max 10)
- age 1: 28992024 bytes, 28992024 total
- age 2: 1366864 bytes, 30358888 total
- age 3: 1425912 bytes, 31784800 total
...

老年代调优

以 CMS 为例

  • CMS 的老年代内存越大越好
  • 先尝试不做老年代调优,如果没有 Full GC 证明不是因为老年代内存不足引起的垃圾回收,即使发生了Full GC,也先尝试调优新生代。
  • 观察发生 Full GC 时老年代内存占用,将老年代内存预设调大 1/4 ~ 1/3

-XX:CMSInitiatingOccupancyFraction=percent

GC调优案例

案例1:Full GC 和 Minor GC频繁
业务高峰来了,创建大量对象将新生代空间塞满,幸存区的晋升阈值就会降低,导致很多生存周期很短的对象,也会被晋升到老年代,进一步触发老年代Full GC的发生。
先试着增大新生代内存大小,内存充裕了,垃圾回收就不会那么频繁,同时增大了幸存区和幸存区阈值,让生命周期较短的对象,尽可能的留在新生代,进一步减少触发老年代的GC。

案例2:请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)
查看GC日志,判断CMS的哪一阶段耗费时间较长,(一般重新标记耗时较长)。

所以需要在重新标记之前,先回收新生代(-XX:+CMSScavengeBeforeRemark参数设置),就不会存在新生代引用老年代,然后去查找老年代了。新生代的垃圾回收(通过-XX:+UseParNewGC)之后,新生代对象少了,重新标记的压力就轻了。
在这里插入图片描述
案例3:老年代充裕情况下,发生 Full GC (CMS jdk1.7)
1.8采用元空间作为方法区的实现,1.7采用永久代作为方法区的实现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/883.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第三代api自动化测试框架使用教程(pytest+allure+sql+yaml)

使用教程一、配置1、环境配置2、框架配置3、启动入口二、用例编写1、用例模板2、参数依赖写法2、函数&#xff08;方法插件&#xff09;写法3、接口上传文件和表单参数4、接口上传json参数5、接口无数据填写6、code断言7、body断言7、json断言8、sql断言9、完整断言写法&#x…

TCP UDP详解

文章目录TCP UDP协议1. 概述2. 端口号 复用 分用3. TCP3.1 TCP首部格式3.2 建立连接-三次握手3.3 释放连接-四次挥手3.4 TCP流量控制3.5 TCP拥塞控制3.6 TCP可靠传输的实现3.7 TCP超时重传4. UDP5.TCP与UDP的区别TCP UDP协议 1. 概述 TCP、UDP协议是TCP/IP体系结构传输层中的…

手把手的教你安装PyCharm --Pycharm安装详细教程(一)(非常详细,非常实用)

简介 Jetbrains家族和Pycharm版本划分&#xff1a; pycharm是Jetbrains家族中的一个明星产品&#xff0c;Jetbrains开发了许多好用的编辑器&#xff0c;包括Java编辑器&#xff08;IntelliJ IDEA&#xff09;、JavaScript编辑器&#xff08;WebStorm&#xff09;、PHP编辑器&…

C/C++考试必考题目(含答案*仅供参考)

今天继续来分享几个C经常考试的几道题目&#xff0c;大家快快拿去&#xff0c;赶紧做一下 目录 &#xff08;小事一桩&#xff09;约瑟夫问题 discreb input output 效果展示&#xff1a; 1、 猜价格游戏 2、 计算 N 以内的所有素数 3、 袋中取球 4、 乘法口诀表 …

尚医通-(三十三)就诊人管理功能实现

目录&#xff1a; &#xff08;1&#xff09;前台用户系统-就诊人管理-需求说明 &#xff08;2&#xff09;就诊人管理-接口开发-列表接口 &#xff08;3&#xff09;就诊人管理-接口开发-其他接口 &#xff08;4&#xff09;前台用户系统-就诊人管理-前端整合 &#xff0…

react的基础使用

react中为什么使用jsxReact 认为渲染逻辑本质上与其他 UI 逻辑内在耦合&#xff0c;比如&#xff0c;在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI&#xff0c;以及需要在 UI 中展示准备好的数据。react认为将业务代码和数据以及事件等等 需要和UI高度耦合…

竞赛无人机搭积木式编程——以2022年TI电赛送货无人机一等奖复现为例学习(7月B题)

在学习本教程前&#xff0c;请确保已经学习了前4讲中无人机相关坐标系知识、基础飞行控制函数、激光雷达SLAM定位条件下的室内定点控制、自动飞行支持函数、导航控制函数等入门阶段的先导教程。 同时用户在做二次开发自定义的飞行任务时&#xff0c;可以参照第5讲中2021年国赛植…

【uniapp小程序实战】—— 使用腾讯地图获取定位

文章目录&#x1f34d;前言&#x1f34b;正文1、首先看官网uni.getLocation(OBJECT)#注意2、腾讯位置服务平台申请密钥和下载SDK2.1 申请开发者秘钥2.2 开通webserviceAPI服务2.3 下载微信小程序JavaScriptSDK2.4 安全域名设置3、配置manifest.json文件4、示例代码展示4.1 引用…

面试重难点问题(C++)

持续更新&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 网络部分 1.问&#xff0c;四次挥手的过程&#xff0c;和双方状态变化&#xff1f; 挥手这前&#xff0c;两边都是established状态&#xff0c;客户端发起断开请求&#xff0c;向服务器发送fin请求&…

Docker6种网络配置详解,网络模式应该这么选

文章目录一、Bridge网络模式二、Host网络模式三、Overlay网络模式四、None网络模式五、Macvlan网络模式六、Ipvlan网络模式七、网络模式选择在Docker中&#xff0c;网络配置是一个重要的主题&#xff0c;因为容器需要与其他容器或外部网络进行通信。Docker提供了多种网络模式和…

注意下C语言整形提升

C语言整形提升 C语言整形提升是指在表达式中使用多种类型的数据时&#xff0c;编译器会自动将较小的类型转换为较大的类型&#xff0c;以便进行运算。在C语言中&#xff0c;整型提升规则如下&#xff1a; 如果表达式中存在short类型&#xff0c;则将其自动转换为int类型。 如…

【JavaEE】初识线程

一、简述进程认识线程之前我们应该去学习一下“进程" 的概念&#xff0c;我们可以把一个运行起来的程序称之为进程&#xff0c;进程的调度&#xff0c;进程的管理是由我们的操作系统来管理的&#xff0c;创建一个进程&#xff0c;操作系统会为每一个进程创建一个 PCB&…

C++之深浅拷贝

一、浅拷贝 我们看下以下代码 Test.h 文件 #pragma once #include<iostream> using namespace std; class Student { public:Student(){}~Student(){if (m_Id ! nullptr){delete m_Id;m_Id nullptr;}}Student(int id, string strName){m_Id new int[id];m_strName s…

字符函数和字符串函数(上)-C语言详解

CSDN的各位友友们你们好,今天千泽为大家带来的是C语言中字符函数和字符串函数的详解,掌握了这些内容能够让我们更加灵活的运用字符串,接下来让我们一起走进今天的内容吧!写这篇文章需要在cplusplus.com上大量截图,十分不易!如果对您有帮助的话希望能够得到您的支持和帮助,我会持…

信号处理-小波变换4-DWT离散小波变换概念及离散小波变换实现滤波

连续小波变换的适用场景&#xff1a;能够获取某一段信号的瞬时信息、时频信息 缺点&#xff1a;计算量大&#xff0c;无法进行数据压缩- 针对连续小波存在的缺点提出离散小波变换 离散小波变换 离散小波变换 分解过程&#xff1a;&#xff08;离散2进正交&#xff09; cD1: …

数据结构与算法——栈和队列<也不过如此>

&#x1f3c6;作者主页&#xff1a;king&南星 &#x1f384;专栏链接&#xff1a;数据结构 &#x1f3c5;文章目录一、&#x1f947;栈1、&#x1f948;概念理解2、&#x1f948;链表头插头删实现栈1、&#x1f949;预备准备2、&#x1f949;创建结点函数3、&#x1f949;遍…

SPI读写SD卡速度有多快?

SD卡是一个嵌入式中非常常用的外设&#xff0c;可以用于存储一些大容量的数据。但用单片机读写SD卡速度一般都有限&#xff08;对于高速SD卡&#xff0c;主要是受限于单片机本身的接口速度&#xff09;&#xff0c;在高速、实时数据存储时可能会有影响。但具体速度可以达到多少…

vue2+高德地图web端开发使用

创建vue2项目我们创建一个vue2项目&#xff0c;创建vue2项目就不用再多说了吧&#xff0c;使用“vue create 项目名 ”创建即可注册高德地图高德地图官网地址&#xff1a;https://lbs.amap.com/如果是第一次使用&#xff0c;点击注册然后进入我们的控制台注册完之后进入控制台&…

<Linux>计算机体系结构和操作系统

计算机体系结构(冯 • 诺依曼体系)和操作系统&#xff08;Operator System&#xff09; 文章目录计算机体系结构(冯 • 诺依曼体系)和操作系统&#xff08;Operator System&#xff09;一、冯 • 诺依曼体系结构1.存储器&#xff08;内存&#xff09;2.运算器和控制器&#xff…

系统重装漏洞

zzcms系统重装漏洞 一、配置zzcms环境 1. 使用小皮搭建zzcms框架 2. 安装zzcms 按照下面的操作进行,傻瓜式操作即可 3. 打开网站 二、漏洞利用 在访问install目录的默认文件后,会出现zzcms安装向导 http://www.zzcms.com/install/index.php 但是会显示 “安装向导…
最新文章