JVM(四)

在上一篇中,介绍了JVM组件中的运行时数据区域,这一篇主要介绍垃圾回收器

JVM架构图:

1、垃圾回收概述

        在第一篇中介绍JVM特点时,有提到过内存管理,即Java语言相对于C,C++进行的优化,可以在适当的时机自动进行垃圾回收,而不需要手动的编写回收逻辑。

        而JVM中的垃圾回收,主要体现在堆和方法区上。栈不存在垃圾回收的问题,因为栈帧之间的线程独立,并且方法在执行完后会弹出栈并且自动释放其中的内存。

        1.1、方法区的垃圾回收

        一般来说,方法区的垃圾回收体现在以下几点:

  • 废弃常量的回收: 方法区中存储的常量包括字符串常量、类的静态常量等。当这些常量没有被引用时,它们就成为了废弃常量。垃圾回收器可以通过检查废弃常量并回收它们占用的内存空间。

    • 无效的类的回收: 在JVM中加载的类信息存储在方法区中。当一个类不再被使用,例如类的实例已经全部被回收,那么该类就成为无效的类。垃圾回收器可以通过检查无效的类并回收相关的内存空间。

        而判定一个类是否能被卸载,需要满足以下条件:

  • 实例是否可达: 如果该类的所有实例都不再被任何活跃的引用链所持有,即没有任何途径可以从根对象(如静态变量、方法区中的常量等)到达该类的实例,那么该类的实例就成为了孤岛对象,可以被回收。

  • 静态变量和静态方法是否引用: 如果该类的静态变量或静态方法仍然被其他地方引用,那么该类本身的类信息也会保持可达状态,无法被回收。因此,如果某个类的静态资源仍然被引用,即使该类的实例全部不可达,该类也无法被回收。

public class GcDemo3 {
    public static void main(String[] args) {
        Student.study();
    }
}


class Student{
    public static void study(){
        System.out.println("我是一个学习java时长达到两年半的练习生");
    }
}

        没有unload:

  • 类加载器的生命周期: 在Java中,类的卸载与类加载器密切相关。当一个类加载器不再需要加载某个类时,可以触发该类的卸载。只有当某个类的所有实例都不可达,并且对应的类加载器也不再存活时,才能够导致该类的卸载和回收。

案例:

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

        try {
            ArrayList<Class<?>> classes = new ArrayList<>();
            ArrayList<URLClassLoader> loaders = new ArrayList<>();
            ArrayList<Object> objs = new ArrayList<>();
            for (int i = 0; i < 5; i++) {

                URLClassLoader loader = new URLClassLoader(
                        new URL[]{new URL("file:D:\\Idea_workspace\\2024\\")});
                Class<?> clazz = loader.loadClass("com.itheima.my.A");
                Object o = clazz.newInstance();
//                objs.add(o);
//                 loader = null;
//                classes.add(clazz);
//                 loaders.add(loader);
                System.gc();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

        加入了两个JVM参数:

        -XX:+TraceClassLoading: 打印加载信息

         -XX:+TraceClassUnloading:  打印卸载信息

        每次循环都会触发回收:

loader,class,a对应的实例都是强引用,按道理强引用是宁愿OOM也不会垃圾回收,案例中为什么每次循环都会触发回收?

        虽然每次循环中新创建了URLClassLoader对象、加载了类、实例化了对象,但是在循环结束后,这些对象都会丢失引用。因为这些对象都是在循环内部被创建的局部变量,一旦循环结束,它们就会超出作用域而无法再被访问到。

        把案例中的代码注释打开,则每次循环都将产生的对象放在对应的集合中,产生了引用,所以不会导致回收:


        1.2、堆区的垃圾回收

        堆的垃圾回收,取决于对象是否被引用:

        例如我现在有两个类,A中引用了B,B中引用了A

public class A {
    B b;
}
public class B {
    A a;
}
public class Demo1 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        a.b = b;
        b.a = a;
        a = null;
        b = null;
    }
}

         在JVM中是这样的结构:

         那么将a和b指向堆的地址设置成为null,能否触发垃圾回收呢?取决于不同的分析方法:

        1.2.1、引用计数法

        每个对象都有一个与之关联的引用计数器,当有新的引用指向该对象时,计数器加1;当引用不再指向该对象时,计数器减1。当一个对象的引用计数器为0时,即没有任何引用指向它,可以判定该对象为垃圾,可以被回收。

        在上面的案例中,a指向A的实例时,引用计数器就+1,A的实例又指向B实例中的a时,计数再次+1,b同理。

        如果将a和b指向堆的地址设置成为null,仍然保留了A的实例指向B中的a,B的实例指向A中的b的引用,即这种循环引用的情况下引用计数器无法归零,这也是引用计数法的缺陷之一。

引用计数法的缺陷:

  1. 无法处理循环引用: 如果存在循环引用的情况,即一组对象相互引用形成了一个环,那么这些对象的引用计数器就永远不会为0,导致它们无法被回收,从而引发内存泄漏。

  2. 计数器更新开销较大: 每次引用发生变化时,需要对相关对象的引用计数器进行更新,这会增加额外的开销,并且会在多线程环境下引入并发访问的问题。

  3. 无法处理跨代引用: 在分代垃圾回收算法中,对象通常被分为不同的代,而引用计数法无法处理跨代引用的情况,可能导致一些对象被错误地回收或长时间无法回收。

        1.2.2、可达性分析

        该算法通过从一组根对象(如线程栈、静态变量等)出发,递归地遍历所有可访问的对象,标记它们为可达对象,而未被标记的对象则被认为是不可达的,可以被回收。

        可达性分析主要包括以下几个步骤:

  1. 确定根对象: 首先确定一组根对象,通常包括活跃线程的栈帧、静态变量以及常量池中的对象等。这些根对象是程序中已知的起始点,是可以直接或间接访问到的对象。

  2. 遍历可达对象: 从根对象开始,对每一个对象进行遍历,找出其引用的对象,并标记这些对象为可达对象。然后对这些被标记的对象再次进行遍历,重复这个过程,直到无法找到新的可达对象为止。

  3. 标记未被访问到的对象: 在完成可达对象的遍历后,所有未被标记的对象都可以被认为是不可达的,即无法从根对象出发访问到的对象。

  4. 回收不可达对象: 将所有不可达对象进行回收,释放它们占用的内存空间。

        图中栈中的a,b即是根对象,当将a和b指向堆的地址设置成为null,A和B的实例就已经被标记为不可达,尽管内部依旧存在循环引用,但是依旧会被回收。

2、四种引用

        2.1、强引用

        强引用是最常见的引用类型,如果一个对象有强引用存在,那么它就不会被垃圾回收器回收。即使出现内存不足的情况,JVM也不会回收被强引用指向的对象。

        什么时候一个对象会存在强引用?

  1. 对象被赋值给一个变量:例如,通过Object obj = new Object();将一个对象赋值给一个变量 obj ,此时obj持有该对象的强引用。
  2. 对象作为参数传递给方法:例如,调用方法时,将对象作为参数传递给方法,方法内部持有该对象的强引用。
  3. 对象作为实例变量存储在其他对象中:例如,一个类中的实例变量引用了一个对象,那么该对象就具有强引用,只要该类的实例还存在。
  4. 对象被设置为数组元素:例如,通过byte[] bytes1 = new byte[1024 * 1024 * 100];将一个对象存储在数组中的某个元素位置,该对象就具有强引用。

        案例:

public class Demo1 {
    public static void main(String[] args) {
        byte[] bytes1 = new byte[1024 * 1024 * 100];
        byte[] bytes2 = new byte[1024 * 1024 * 100];
    }
}

        将最大堆内存设置成200M,上面的代码执行,如果过程中没有发生GC,就会内存溢出。

        可以看到,强引用即使是内存不足的情况下,也不会进行回收:

       2.2、软引用

        软引用是一种相对强引用弱化了一些的引用类型。当系统内存不足时,JVM会尝试回收被软引用指向的对象,但并不是必然回收,在这样的情况下,只有当对象已经没有强引用指向它时才会被回收。使用软引用可以有效地实现缓存功能。

        同样将最大堆大小设置为200M。

        byte数组对象被包装成了软引用(这里为什么要在包装成软引用后把bytes设置为null呢?可以思考一下)。

public class SoftReferenceDemo2 {
    public static void main(String[] args) throws IOException {

        byte[] bytes = new byte[1024 * 1024 * 100];
        SoftReference<byte[]> softReference = new SoftReference<byte[]>(bytes);
        bytes = null;
        System.out.println(softReference.get());

        byte[] bytes2 = new byte[1024 * 1024 * 100];
        System.out.println(softReference.get());
    }
}

        当创建第二个byte数组时,由于内存空间不足,对被包装成软引用的byte数组对象进行了回收。

        如果不将bytes设置为null,则不会对被包装成软引用的byte数组对象进行了回收,因为bytes有强引用指向它。

         当被软引用SoftReference包装的实例对象被回收后,其容器SoftReference对象也要被回收,在SoftReference的构造方法中,提供了一个ReferenceQueue队列。

        当被软引用SoftReference包装的实例对象被回收后,SoftReference对象会放入ReferenceQueue队列中

         2.3、弱引用

        弱引用比软引用的生命周期更短暂,一旦GC运行无论内存是否充足,被弱引用指向的对象就会被回收(如被包装成弱引用的原有对象具有强引用,依旧无法回收)。弱引用通常用于实现规范映射或者监听器模式。

public class Demo2 {
    public static void main(String[] args) {
        WeakReference<Object> weakReference = new WeakReference<>(new Object());
        System.out.println(weakReference.get());
        System.gc();
        System.out.println(weakReference.get());
    }
}

        2.4、虚引用

        虚引用是最弱的一种引用类型,它几乎没有实际作用,主要用于在对象被垃圾回收时收到一个系统通知。虚引用必须和引用队列(ReferenceQueue)一起使用。

PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), referenceQueue);

        此外还有一个finalize,它代表的是终结器引用。当对象被回收时,会将对象放入Finalizer类中的引用队列,并且稍后由FinalizerThread线程从队列中获取对象,执行finalize()方法,用于对象在被垃圾回收之前进行清理和释放资源操作。但是,具体的调用时间是不确定的,而且垃圾回收器也不保证一定会调用每个对象的finalize()方法。

        finalize()方法是Object类中的,如果需要自定义逻辑只需要重写该方法。

案例:

        可以在重写的finalize()方法中,再次将对象关联成强引用。

public class FinalizeReferenceDemo {
    public static FinalizeReferenceDemo reference = null;

    public void alive() {
        System.out.println("当前对象还存活");
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            System.out.println("finalize()执行了...");
            //设置强引用自救
            reference = this;
        } finally {
            super.finalize();
        }
    }

    public static void main(String[] args) throws Throwable {
        reference = new FinalizeReferenceDemo();
        test();
        test();
    }

    private static void test() throws InterruptedException {
        reference = null;
        //回收对象
        System.gc();
        //执行finalize方法的优先级比较低,休眠500ms等待一下
        Thread.sleep(500);
        if (reference != null) {
            reference.alive();
        } else {
            System.out.println("对象已被回收");
        }
    }
}

        由FinalizerThread线程从队列中获取对象:


小结:

        日常程序开发中,创建出的对象绝大部分都是强引用,即使出现内存溢出,也不会对被强引用的对象进行垃圾回收。弱引用软引用,区别在于前者是垃圾回收器只要发现了就会回收,后者是内存不足时才会被优先回收,但是共同点是,如果被包装成软/弱引用的原目标对象被强引用,则不会被回收。

3、垃圾回收算法

        JVM常见的垃圾回收算法有:标记-清除算法,标记-整理算法,复制算法,分代回收算法。

        无论哪一种算法,其核心思想是,先找到内存中存活的对象,然后清除其他的对象并释放内存,使得内存空间得以复用。

        Java的垃圾回收是由单独负责GC的线程去执行的,但是在执行的过程中,都需要暂时停止用户线程(STW):

        而评价一个垃圾回收算法的优劣,通常会关注以下几点:

  • 内存利用效率:垃圾回收算法应该尽可能地高效利用内存,避免内存碎片化和内存泄漏问题。

  • 垃圾回收的延迟:垃圾回收算法执行时,会导致程序停顿或延迟,这会影响系统的响应速度。因此,垃圾回收算法的评价中会考虑其对程序执行的影响,包括停顿时间的长短、频率等。

  • 垃圾回收的吞吐量:垃圾回收算法应该尽可能地减少对程序的干扰,以提高程序的整体吞吐量。(垃圾回收的吞吐量,指用户执行代码的时间/GC时间)

  • 碎片化处理能力:垃圾回收算法应该有效地处理内存碎片化问题,避免内存分配失败或者性能下降。

        3.1、标记-清除算法

        其核心思想在于,利用可达性分析,标记所有仍在使用中的对象,然后清除所有不可达的孤岛对象。(标记使用中的,清除未使用的)。

        如图所示,C对象为不可达,会在下一次GC中被清除。

        它的弊端在于,在清理过程中可能会存在内存碎片

        首先明白一点,内存是连续的,而未被标记即将被清理的对象,不会恰好都在一片连续的内存上,如图所示,红色部分代表被清理的对象。

        由于内存碎片的存在,JVM同时会维护一个空余内存的链表,如果我要添加一个3字节的对象,可能会遍历到链表的尾部才能实现,如图所示,这样的效率就会较低。

        3.2、标记-整理算法

        为了解决上述内存碎片导致的相关问题,引入了标记-整理算法,相当于对标记-清除算法的一种改进。

        其核心思想在于:通过可达性分析,将所有使用中的对象进行标记,然后对其进行整理,使其存放在一块连续的内存上,然后对所有未标记的对象进行清理:

        标记阶段:

        整理阶段:

        但是依旧存在一些缺陷:

  1. 执行时间较长:标记-整理算法在整理阶段需要移动存活对象,这可能导致执行时间较长,尤其在内存使用较大时,整理操作对程序的停顿时间会更加明显。

  2. 需要额外空间:标记-整理算法通常需要额外的空间来进行对象的移动操作,这可能会增加算法的空间复杂度,特别是当内存中存在大量存活对象时。

  3. 不适用于并发环境:标记-整理算法通常需要在整理阶段移动对象,这可能导致并发环境下的一些问题,比如需要暂停所有线程的执行,以保证整理操作的正确性。

        3.3、复制算法

        复制算法是另一种针对标记-清除算法的增强,其核心思想在于,将堆分为两个区域,分别是From和To,新创建的对象都在From中:

        当需要GC时,将所有可达的对象放入To区域:

        然后清理From区域:

        最后将名称互换:

        可达的对象在GC时都是在To空间。

        复制算法的主要弊端在于:空间开销大,复制算法需要额外的一块同等大小的内存空间来进行存活对象的复制,这可能导致整体的内存利用率下降,特别是在内存空间受限的情况下。

        3.4、分代回收算法

        其核心思想是,将内存按代划分成新生代和老年代,其中新生代又由伊甸园区和From,To组成(复制算法):

        新创建的对象会被分配在伊甸园区,当伊甸园区空间充满后,会触发一次Minor GC回收掉不可达的对象,然后将剩下的对象放入To区,并且将From和To的名称互换。

        我们可以通过arthas工具查看一下这个过程:(JDK1.8版本需要添加JVM参数-XX:+UseSerialGC 表示使用Serial 和 SerialOld垃圾回收器,后面会说明)

public class GcDemo0 {

    public static void main(String[] args) throws IOException {
        List<Object> list = new ArrayList<>();
        int count = 0;
        while (true){
            System.in.read();
            System.out.println(++count);
            //每次添加1m的数据
            list.add(new byte[1024 * 1024 * 1]);
        }
    }
}

        然后通过arthas的memory命令查看(从上到下分别是伊甸园区,幸存者区,老年区):

        同时我们也可以通过JVM命令自主设置这些区的大小:

        我们进行如下设置:

-XX:+UseSerialGC -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio=3 -XX:+PrintGCDetail

        初始状态下,伊甸园区使用了8M

        当我们添加了三次数据后触发了Minor GC:

[GC (Allocation Failure) [DefNew: 15819K->4096K(16384K), 0.0084006 secs] 26354K->20135K(57344K), 0.0084792 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]:

  • [GC (Allocation Failure) 表示这次垃圾回收是由于内存分配失败而触发的。
  • [DefNew: 15819K->4096K(16384K)表示新生代(Young Generation)的垃圾回收情况。15819K是垃圾回收前新生代已使用的内存大小,4096K是垃圾回收后新生代已使用的内存大小,16384K是新生代总内存大小。
  • 26354K->20135K(57344K)表示整个堆内存的垃圾回收情况。26354K是垃圾回收前堆内存已使用的大小,20135K是垃圾回收后堆内存已使用的大小,57344K是堆内存总大小。
  • 0.0084006 secs:表示这次垃圾回收的实际耗时。
  • [Times: user=0.00 sys=0.00, real=0.01 secs]:表示垃圾回收过程中消耗的CPU时间和实际时间。

        垃圾回收前后的内存情况:

        当某个对象在新生代存活过了最多15次垃圾回收(存在一种特殊情况:如果新生代的内存已满,即使年龄没有到达15,依旧会被放入老年代),JVM会认为它很难被回收,就会放入老年区中。 当老年代空间不足时,会先触发一次Minor GC,如果内存依旧不足,则会触发一次Full GC对新生代和老年代所有的垃圾进行回收,若Full GC后依旧无法回收老年代的垃圾,就会触发OOM错误。

        老年代空间不足,并且触发了Minor GC依旧内存不足:

        触发Full GC:

        由于案例中的对象都是强引用,所以无法回收,再次向老年代放入则会触发OOM:

4、垃圾回收器

        垃圾回收器是负责管理堆内存中对象的回收和内存释放的组件。JVM的垃圾回收器有多种不同的实现,通常新生代的垃圾回收器需要和老年代的垃圾回收器配合使用:

        4.1、Serial&Serial Old

        第一种组合方式是Serial(新生代)和Serial Old(老年代):

        Serial、Serial Old是一个单线程的收集器,有一个专门用于GC的线程进行垃圾回收,过程中会暂停所有用户线程来进行垃圾回收。

        区别在于,Serial使用的是复制算法,而Serial Old使用的是标记-整理算法

        如果需要使用该垃圾回收器,需要配合JVM参数:-XX:+UseSerialGC

        可以通过arthas的dashboard命令查看:

        4.2、ParNew&CMS(Serial Old)

        第二种组合方式是ParNew(新生代)配合CMS(老年代)或Serial Old(老年代)

        ParNew&Serial Old的组合,实际上是针对Serial&Serial Old的一种优化,使用多线程进行垃圾回收,ParNew使用的依旧是复制算法

        可通过JVM参数-XX:+UseParNewGC 使用:

        CMS垃圾回收器在尽量减少停顿时间的同时进行垃圾回收。适用于对响应时间要求较高的应用。允许用户线程和垃圾回收线程在某些时刻同时运行。 使用的是标记-清除算法

        执行主要分为四个阶段:

  1. 初始标记阶段:CMS会暂停所有应用线程来标记根对象。
  2. 并发标记阶段:CMS收集器会和应用程序线程并发地标记整个堆内存中的对象。
  3. 重新标记阶段:由于并发标记阶段没有暂停应用线程,某些对象可能发生改变,所以需要二次标记。(暂停所有应用线程
  4. 并发清理阶段:与应用程序并发的执行,清理所有未被标记的对象。

        它存在的缺陷:

  • 因为使用的是标记-清除算法,所以会产生内存碎片。
  • 在并发清理阶段,因为应用程序没有暂停,所以在清理的过程中也可能会存在新的垃圾。
  • 如果老年代内存不足无法分配对象,CMS会退化为Serial Old单线程回收老年代。
        4.3、Paraller Scavenge&Paraller Old

        最后一种组合是Paraller Scavenge(新生代)配合Paraller Old(老年代)

        Paraller Scavenge&Paraller Old组合,通过多线程并行地进行垃圾收集,将应用程序的暂停时间降到最低,以此来提高系统的吞吐量。Paraller Scavenge使用的是复制算法,Paraller Old使用的是标记-整理算法 ,在GC期间会暂停所有其他线程的执行

        在JDK1.8中,默认就是使用该组合进行垃圾回收,无需额外设置JVM参数。

        同时这个组合允许自定义最大暂停时间、吞吐量以及自动调整内存大小:

  • -XX:MaxGCPauseMillis= 设置最大暂停时间
  • -XX:GCTimeRatio = 设置吞吐量
  • -XX:+UseAdaptiveSizePolic 自动调整内存大小
        4.4、G1垃圾回收

        G1垃圾回收器是在JDK 7中引入的一种全新的垃圾回收器,并在JDK9中成为默认的垃圾回收器。

        它具有以下的特点:

  • 区域化内存管理:G1将堆内存划分为多个大小相等的区域,每个区域既可以用作新生代也可以用作老年代,这样就消除了新生代和老年代之间的严格划分。
  • 并发和并行:G1垃圾回收器在标记、整理和清理阶段都可以利用并发和并行的方式进行垃圾回收,以减少暂停时间并提高吞吐量。
  • 持续性垃圾回收:G1会根据应用程序的动态情况来选择哪些区域进行垃圾回收,以尽可能地减少垃圾回收的暂停时间。
  • 可预测的暂停:G1通过一种叫做“垃圾优先(Garbage-First)”的算法来选择优先回收哪些区域,从而实现可预测的垃圾回收暂停时间。

        在分代回收算法中,划分的新生代和老年代的内存空间是连续的:

        而在G1垃圾回收器中,新生代与新生代,老年代与老年代之间并非一定是一块完整的区域:

        而是将堆划分为大小相同的区域,每个区域的大小可以通过堆大小/2048算得,也可以自己通过JVM参数进行设置:-XX:G1HeapRegionSize= ,但是参数的值必须是2的指数幂,并且最大为32。

        在垃圾回收时,分为新生代回收和混合回收:

        4.4.1、新生代回收

        当新生代的空间达到60%时,会触发一次回收,使用的是复制算法 (标记伊甸园区和幸存者区,放入另一个幸存者区),但是为了兼顾最大暂停时间,通常不会对所有的垃圾进行回收。当某个对象的年龄到达15时会被放入老年代。

        如果某个对象的大小大于该区域的1/2,例如某个区域分配的大小为4M,某个对象的大小为3M,这些对象会被集中存放入Homongous区中。

        4.4.2、混合回收

        当多次新生代回收后,总堆占有率达到阈值时,会触发混合回收,使用的是复制算法

        混合回收的阶段:

  • 初始标记阶段:会暂停用户线程,标记所有GC Root引用的对象(不包含该对象的引用链)。
  • 并发标记阶段:会和用户线程并发执行,将第一步中对象的引用链一并标记为存活。
  • 最终标记阶段:会暂停用户线程,主要是标记第二步中发生引用改变漏标的对象。
  • 并发清理阶段:会和用户线程并发执行,将存活的对象通过复制算法 复制到其他区域。

        和CMS四个阶段的区别:

CMS混合回收
初始标记阶段标记所有GC Root引用的对象标记所有GC Root引用的对象
并发标记阶段标记所有的对象标记第一步中对象的引用链
最终(重新)标记阶段标记第二步中错标,漏标的对象标记第二步中发生引用改变漏标的对象,不管新创建和不再关联的对象
并发清理阶段使用的是标记-清除算法将存活的对象通过复制算法 复制到其他区域。

        G1进行老年代回收时,也会优先清理存活度最低的区域,但是在清理的过程中,如果没有足够的区域去转移对象,就会触发Full GC,这时使用的是标记-整理算法


总结

JVM基础篇完结

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

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

相关文章

写Python时不用import,你会遭遇什么

from *** import *** 想必你已经再熟悉不过这样的python语法。 当你的 python 代码需要获取外部的一些功能&#xff08;一些已经造好的轮子&#xff09;&#xff0c;你就需要使用到 import 这个声明关键字。import可以协助导入其他 module 。&#xff08;类似 C 预约的 inclu…

php反序列化初步了解

一、定义 序列化&#xff08;串行化&#xff09;&#xff1a;将变量转换为可保存或传输的字符串的过程&#xff08;通常是字节流、JSON、XML格式&#xff09; 反序列比&#xff08;反串行化&#xff09;&#xff1a;把这个字符串再转化成原始数据结构或对象&#xff08;原来的…

J.搬砖【蓝桥杯】/01背包+贪心

搬砖 01背包贪心 思路&#xff1a;要让重量更小的在更前面&#xff0c;价值更大的在更后面&#xff0c;vi−wj>vj−wi viwi>vjwj 第 i 个箱子放在第 j 个箱子下面就显然更优。所以进行排序再用01背包即可。 #include<iostream> #include<algorithm> #defi…

MVC和Filter

目录 MVC和三层架构模型的联系 Filter 概念 作用 应用场景 步骤 简单入门 MVC和三层架构模型的联系 m-->model即模型是三层架构模型的业务层&#xff08;service&#xff09;和持久层(dao) v-->views即视图是三层架构模型的表现层(web) c-->controller即控制器也…

python中pow是什么意思

pow()方法返回xy&#xff08;x的y次方&#xff09;的值。 语法 以下是math模块pow()方法的语法&#xff1a; import math math.pow( x, y ) 内置的pow()方法 pow(x, y[, z]) 函数是计算x的y次方&#xff0c;如果z在存在&#xff0c;则再对结果进行取模&#xff0c;其结果等效…

【JS红宝书学习笔记】第4章 变量、作用域和内存

第4章 变量、作用域和内存 1. 原始值和引用值&#xff08;面试题&#xff09; ECMAScript 变量可以包含两种不同类型的数据&#xff1a;原始值和引用值。原始值&#xff08;primitive value&#xff09;就是最简单的数据&#xff08;Undefined、Null、Boolean、Number、Strin…

鸿蒙应用模型:【Stage模型开发】概述

Stage模型开发概述 基本概念 下图展示了Stage模型中的基本概念。 图1 Stage模型概念图 [AbilityStage] 每个Entry类型或者Feature类型的HAP在运行期都有一个AbilityStage类实例&#xff0c;当HAP中的代码首次被加载到进程中的时候&#xff0c;系统会先创建AbilityStage实例…

TCP/IP协议族

基于这张图片的一篇blog TCP/IP模型通常被分为四个层次&#xff1a;应用层、传输层、网络层和网络接口层。在这个模型中&#xff0c;不同的网络协议负责完成不同的任务&#xff0c;以确保数据可以在网络中高效、可靠地传输。以下是对这张图中每个协议的解释&#xff1a; 应用层…

[自动驾驶技术]-6 Tesla自动驾驶方案之硬件(AI Day 2021)

1 硬件集成 特斯拉自动驾驶数据标注过程中&#xff0c;跨250万个clips超过100亿的标注数据&#xff0c;无论是自动标注还是模型训练都要求具备强大的计算能力的硬件。下图是特斯拉FSD计算平台硬件电路图。 1&#xff09;神经网络编译器 特斯拉AI编译器主要针对PyTorch框架&am…

企业为何广泛应用数据可视化?解析其背后原因

数据可视化为何能在企业当中广泛应用&#xff1f;这是一个值得探讨的话题。在当今信息爆炸的时代&#xff0c;企业每天都会产生和处理大量的数据&#xff0c;这些数据蕴含着丰富的信息和潜在的商业价值。然而&#xff0c;面对海量数据&#xff0c;如何高效地分析、理解和利用它…

特定车型专属AI模型解决方案,高清图像,稳定输出

美摄科技凭借其对人工智能领域的深刻理解和技术积累&#xff0c;为企业带来了一项革命性的解决方案——特定车型专属AI模型。这一方案以专属车型照片为基础&#xff0c;通过先进的AI生成模型训练&#xff0c;为企业提供个性化、高清、稳定的车辆图像和视频生成服务&#xff0c;…

景源畅信电商:抖音开店步骤是什么?

随着社交媒体的兴起&#xff0c;抖音已经成为一个不可忽视的电商平台。许多人都希望通过抖音开店来实现自己的创业梦想。那么&#xff0c;抖音开店的具体步骤是什么呢?接下来&#xff0c;我们将详细阐述这一问题。 一、明确回答问题抖音开店的步骤主要包括&#xff1a;注册账号…

程序猿转型做项目经理一定要注意这 5 个坑

前言 国内的信息系统项目经理&#xff0c;很多都是从技术骨干转型的&#xff0c;我就是这样一路走过来的&#xff0c;这样有很多好处&#xff0c;比如技术过硬容易服众、熟悉开发流程更容易把控项目进度和质量、开发过程中碰到难题时更好组织攻坚等等&#xff0c;但是所谓成也…

web练习

[CISCN 2022 初赛]ezpop ThinkPHP V6.0.12LTS 反序列化漏洞 漏洞分析 ThinkPHP6.0.12LTS反序列漏洞分析 - FreeBuf网络安全行业门户 解题过程 ThinkPHP V6.0.12LTS反序列化的链子可以找到&#xff0c;找到反序列化的入口就行 反序列化的入口在index.php/index/test 链子 …

MVC架构中的servlet层重定向404小坑

servlet层中的UserLoginServlet.java package com.mhys.servlet; /*** ClassName: ${NAME}* Description:** Author 数开_11* Create 2024-05-29 20:32* Version 1.0*/import com.mhys.pojo.User; import com.mhys.service.UserService; import com.mhys.service.impl.UserSer…

【busybox记录】【shell指令】mkdir

目录 内容来源&#xff1a; 【GUN】【mkdir】指令介绍 【busybox】【mkdir】指令介绍 【linux】【mkdir】指令介绍 使用示例&#xff1a; 创建文件夹 - 默认 创建文件夹 - 创建的同时指定文件权限 创建文件夹 - 指定多级文件路径&#xff0c;如果路径不存在&#xff0c…

电脑显示不出网络

你的电脑是否在开机后显示不出网络&#xff0c;或者有网络消失的现象&#xff1f;今天和大家分享我学到的一个办法&#xff0c;希望对大家有用。 分析出现这类现象的原因&#xff1a;可能是电脑网卡松动了&#xff0c;电脑中存在静电流。 解决办法&#xff1a;先将电脑关机&am…

redis 集群 底层原理以及实操

前言 上篇我们讲解了哨兵集群是怎么回事 也说了对应的leader选举raft算法 也说了对应的slave节点是怎么被leader提拔的 主要是比较优先级 比较同步偏移量 比较runid等等 今天我们再说说,其实哨兵也有很多缺点 虽然在master挂了之后能很快帮我们选举出新的master 但是对于单个ma…

python调用阿里云通义千问(q-wen-max)API-只能总结pdf文档内容

文章目录 通义千问插件PDF解析插件调用案例通义千问插件 Dashscope插件功能能够使得大模型的生成内容与外部三方应用结合,使得模型生成的内容更加准确和丰富,模型将拥有更好的生成能力。您也可以通过开发自定义插件,来使得模型生成更符合您预期的结果。 使用插件功能,大模…

【UnityShader入门精要学习笔记】第十五章 使用噪声

本系列为作者学习UnityShader入门精要而作的笔记&#xff0c;内容将包括&#xff1a; 书本中句子照抄 个人批注项目源码一堆新手会犯的错误潜在的太监断更&#xff0c;有始无终 我的GitHub仓库 总之适用于同样开始学习Shader的同学们进行有取舍的参考。 文章目录 使用噪声上…