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的引用,即这种循环引用的情况下引用计数器无法归零,这也是引用计数法的缺陷之一。
引用计数法的缺陷:
无法处理循环引用: 如果存在循环引用的情况,即一组对象相互引用形成了一个环,那么这些对象的引用计数器就永远不会为0,导致它们无法被回收,从而引发内存泄漏。
计数器更新开销较大: 每次引用发生变化时,需要对相关对象的引用计数器进行更新,这会增加额外的开销,并且会在多线程环境下引入并发访问的问题。
无法处理跨代引用: 在分代垃圾回收算法中,对象通常被分为不同的代,而引用计数法无法处理跨代引用的情况,可能导致一些对象被错误地回收或长时间无法回收。
1.2.2、可达性分析
该算法通过从一组根对象(如线程栈、静态变量等)出发,递归地遍历所有可访问的对象,标记它们为可达对象,而未被标记的对象则被认为是不可达的,可以被回收。
可达性分析主要包括以下几个步骤:
-
确定根对象: 首先确定一组根对象,通常包括活跃线程的栈帧、静态变量以及常量池中的对象等。这些根对象是程序中已知的起始点,是可以直接或间接访问到的对象。
-
遍历可达对象: 从根对象开始,对每一个对象进行遍历,找出其引用的对象,并标记这些对象为可达对象。然后对这些被标记的对象再次进行遍历,重复这个过程,直到无法找到新的可达对象为止。
-
标记未被访问到的对象: 在完成可达对象的遍历后,所有未被标记的对象都可以被认为是不可达的,即无法从根对象出发访问到的对象。
-
回收不可达对象: 将所有不可达对象进行回收,释放它们占用的内存空间。
图中栈中的a,b即是根对象,当将a和b指向堆的地址设置成为null,A和B的实例就已经被标记为不可达,尽管内部依旧存在循环引用,但是依旧会被回收。
2、四种引用
2.1、强引用
强引用是最常见的引用类型,如果一个对象有强引用存在,那么它就不会被垃圾回收器回收。即使出现内存不足的情况,JVM也不会回收被强引用指向的对象。
什么时候一个对象会存在强引用?
- 对象被赋值给一个变量:例如,通过Object obj = new Object();将一个对象赋值给一个变量 obj ,此时obj持有该对象的强引用。
- 对象作为参数传递给方法:例如,调用方法时,将对象作为参数传递给方法,方法内部持有该对象的强引用。
- 对象作为实例变量存储在其他对象中:例如,一个类中的实例变量引用了一个对象,那么该对象就具有强引用,只要该类的实例还存在。
- 对象被设置为数组元素:例如,通过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("当前对象还存活");}@Overrideprotected 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、标记-整理算法
为了解决上述内存碎片导致的相关问题,引入了标记-整理算法,相当于对标记-清除算法的一种改进。
其核心思想在于:通过可达性分析,将所有使用中的对象进行标记,然后对其进行整理,使其存放在一块连续的内存上,然后对所有未标记的对象进行清理:
标记阶段:
整理阶段:
但是依旧存在一些缺陷:
-
执行时间较长:标记-整理算法在整理阶段需要移动存活对象,这可能导致执行时间较长,尤其在内存使用较大时,整理操作对程序的停顿时间会更加明显。
-
需要额外空间:标记-整理算法通常需要额外的空间来进行对象的移动操作,这可能会增加算法的空间复杂度,特别是当内存中存在大量存活对象时。
-
不适用于并发环境:标记-整理算法通常需要在整理阶段移动对象,这可能导致并发环境下的一些问题,比如需要暂停所有线程的执行,以保证整理操作的正确性。
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垃圾回收器在尽量减少停顿时间的同时进行垃圾回收。适用于对响应时间要求较高的应用。允许用户线程和垃圾回收线程在某些时刻同时运行。 使用的是标记-清除算法
执行主要分为四个阶段:
- 初始标记阶段:CMS会暂停所有应用线程来标记根对象。
- 并发标记阶段:CMS收集器会和应用程序线程并发地标记整个堆内存中的对象。
- 重新标记阶段:由于并发标记阶段没有暂停应用线程,某些对象可能发生改变,所以需要二次标记。(暂停所有应用线程)
- 并发清理阶段:与应用程序并发的执行,清理所有未被标记的对象。
它存在的缺陷:
- 因为使用的是标记-清除算法,所以会产生内存碎片。
- 在并发清理阶段,因为应用程序没有暂停,所以在清理的过程中也可能会存在新的垃圾。
- 如果老年代内存不足无法分配对象,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基础篇完结
相关文章:

JVM(四)
在上一篇中,介绍了JVM组件中的运行时数据区域,这一篇主要介绍垃圾回收器 JVM架构图: 1、垃圾回收概述 在第一篇中介绍JVM特点时,有提到过内存管理,即Java语言相对于C,C进行的优化,可以在适当的…...

Leetcode373.查找和最小的 K 对数字
文章目录 题目描述解题思路代码 题目链接 题目描述 给定两个以 非递减顺序排列 的整数数组 nums1 和 nums2 , 以及一个整数 k 。 定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。 请找到和最小的 k 个数对 (u1,v1), (u2,v2) … (…...

windows 安装 使用 nginx
windows 安装 使用 nginx nginx官网下载地址:https://nginx.org/en/download.html 下载稳定版本即可 下载压缩包解压到即可 进入文件夹中,打开命令行窗口,执行启动命令 start nginx.exe验证(默认是80端口)&#x…...

【运维】Linux 端口管理实用指南,扫描端口占用
在 Linux 系统中,你可以使用以下几种方法来查看当前被占用的端口,并检查 7860 到 7870 之间的端口: 推荐命令: sudo lsof -i :7860-7870方法一:使用 netstat 命令 sudo netstat -tuln | grep :78[6-7][0-9]这个命令…...

Android笔记--应用安装
这一节了解一下普通应用安装app的方式,主要是唤起系统来安装,直接上代码: 申请权限 <uses-permission android:name"android.permission.READ_EXTERNAL_STORAGE"/><uses-permission android:name"android.permission.WRITE_EXT…...

今日分享站
同志们,字符函数和字符串函数已经全部学习完啦,笔记也已经上传完毕,大家可以去看啦。字符函数和字符串函数and模拟函数 加油!!!!!...

基于python flask的旅游数据大屏实现,有爬虫有数据库
背景 随着旅游行业的快速发展,数据在旅游决策和规划中的重要性日益凸显。基于 Python Flask 的旅游数据大屏实现研究旨在结合爬虫技术和数据库存储,为用户提供全面、实时的旅游信息展示平台。 爬虫技术作为数据采集的重要手段,能够从各种网…...

海尔智家牵手罗兰-加洛斯,看全球创牌再升级
晚春的巴黎西郊,古典建筑群与七叶树林荫交相掩映,坐落于此的罗兰加洛斯球场内座无虚席。 来自全球各地的数万观众,正与场外街道上的驻足者们一起,等待着全世界最美好的网球声响起…… 当地时间5月26日,全球四大职业网…...

【busybox记录】【shell指令】unlink
目录 内容来源: 【GUN】【unlink】指令介绍 【busybox】【unlink】指令介绍 【linux】【unlink】指令介绍 使用示例: 删除文件 - 默认 常用组合指令: 指令不常用/组合用法还需继续挖掘: 内容来源: GUN &#x…...

如何恢复被盗的加密货币?
本世纪,网络犯罪的首要目标是加密货币。 这要归功于加密货币的日益普及和价值,网络犯罪分子已经认识到经济收益的潜力,并将重点转向利用这种数字资产中的漏洞。 在今天的文章中,我们将讨论加密货币恢复和被盗加密货币恢复。 我们…...

英语学习笔记29——Come in, Amy!
Come in, Amy! 进来,艾米! shut v. 关严 区别:shut the door 把门关紧 口语:Shut up! 闭嘴! 态度强硬,不礼貌 例句:请不要把门关严。 Don’t shut the door, please. bedroom n. …...

grpc NewClient 报错 name resolver error: produced zero addresses
场景 grpc版本: google.golang.org/grpc v1.64.0 连接客户端: import("google.golang.org/grpc""net" ) // 拿着设备ID 去获取连接 var connMap map[string]net.Conn conn, err : grpc.NewClient("device_id",grpc.WithContextDialer(func(ctx…...

【Docker】2、配置SSL证书远程访问Docker
1、使用 openssl 生成 ca 1、创建文件夹 mkdir -p /root/dockercd /root/docker2、创建 RSA 私钥 会提示 2 次输入证书密码,至少 4 位,创建后会生成一个 ca-key.pem 文件 openssl genrsa -aes256 -out ca-key.pem 4096得到 ca-key.pem 文件 3、创建…...

HFish蜜罐管理端搭建:构建网络安全的主动防御系统
引言 在网络攻防对抗日益激烈的今天,蜜罐技术作为一种有效的主动防御手段,越来越受到网络安全专家的青睐。HFish蜜罐以其强大的功能和灵活的部署方式,成为网络安全防护体系中的重要组成部分。本文将详细介绍如何在CentOS 7.6系统上搭建HFish…...

探秘AI艺术:揭开Midjourney绘画的神秘面纱
在当今这个数字化迅速发展的时代,AI技术已经深入到我们生活的方方面面,而最令人着迷的莫过于它在艺术创作领域的应用。“Midjourney绘画”就是这样一个令人惊叹的例子,它通过高级AI技术,能够帮助用户生成独一无二的艺术作品。但是…...

29-ESP32-S3-WIFI_Driver-00 STA模式扫描全部 AP
ESP32-S3 WIFI_Driver 引言 ESP32-S3是一款集成了Wi-Fi和蓝牙功能的芯片。关于WIFI的部分,其实内容比我想象的要多得多。所以通常来说,如果你想要编写自己的Wi-Fi应用程序,最快捷的方法就是先找一个类似的示例应用,然后将它的相…...

2024了,还有人在问为甚死锁?
大家好,我是javapub。 接上篇提到了锁,《InnoDB有哪些锁类型》。这么多的锁,你有遇到过死锁吗? 死锁是在事务数据库中会发生的一种特殊现象,多个事务在执行过程中,相互等待对方持有的资源,导致…...

Java中Arrays.toString与new String()字节数组使用的差异
Java 编程语言提供了许多内置方法和类,这使得程序员能够更加方便的处理数据和对象。本文将讨论 Arrays.toString 方法和 new String() 方法在处理字节数组时的不同。 文章目录 Arrays.toString 方法new String() 方法总结 Arrays.toString 方法 Arrays.toString() …...

开源表单流程设计器有哪几个突出的优势特点?
当前,传统的表单制作已经无法满足现在企业的发展需求了。想要实现高效率发展,需要引进先进的低代码技术平台、开源表单流程设计器等优秀软件平台助力发展。它们具有可视化操作界面、灵活好操作、易维护、效率高等诸多优势特点,在推动企业实现…...

景源畅信:抖音小店如何开橱窗?
在当今数字化时代,社交媒体平台不仅仅是人们交流和分享生活的工具,更成为了商家们展示和销售产品的重要场所。抖音作为一款流行的短视频社交应用,其内置的电商功能——抖音小店,为众多商家和个人提供了便捷的在线销售途径。其中&a…...

Unix环境高级编程--8-进程控制---8.7函数waitid 8.8函数wait3 wait4
1、Single Unix Specification支持一个取得进程终止状态的函数--waitid,此函数类似于waitpid: pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options); …...

window.addEventListener 用法
window.addEventListener 是JavaScript中用来为DOM元素(在本例中是浏览器窗口window)添加事件监听器的方法。这对于响应用户操作(如点击、滚动等)或页面/浏览器的特定状态变化非常有用。下面是如何使用window.addEventListener的基…...

【全开源】活动报名表单系统(ThinkPHP+Uniapp+uView)
轻松构建高效报名平台 一、引言 随着线上活动的日益增多,一个高效、易用的活动报名表单系统成为了举办各类活动的必备工具。为了满足不同组织和个人的需求,我们推出了功能强大的“活动报名表单系统源码”。本文将为您详细介绍该源码的特点、功能以及使…...

python接口自动化之会话保持
🍦 会话保持-token 有的网站登录需要token鉴权,是啥意思呢,现在有两个接口,一个接口是登录,一个接口是提交订单,那你怎么保证,提交登录这个用户是登录状态呢。登录成功的接接口会在response里面…...

script 标签中 defer 和 async 属性的区别
script 标签中的 defer Vs. async 在 HTML 中,script 标签可以使用 defer 和 async 属性来控制外部 JavaScript 脚本加载和执行的方式。defer 和 async 都可以提高页面的加载性能,主要区别整理如下。 区别点deferasync加载顺序按顺序加载异步加载&…...

【axios】的浅度分析
一、Axios的拦截器能干些什么? Axios拦截器的实现原理主要涉及两个方面:请求拦截器和响应拦截器,它们分别在请求发送前和响应返回后进行预处理和后处理。 Axios内部维护了两个数组,一个用于存储请求拦截器,另一个用于…...

基于单片机的步进电机控制系统研究
摘 要 : 近年来 , 步进电机凭借其定位精度高 、 使用方便 、 性价比高 、 容易控制等优点 , 在各领域受到广泛应用 。 文中利用C52 单片机设计了一种步进电机控制系统 , 介绍了其总体方案 、 主控制模块 、 驱动电路 、 键盘 、 晶…...

Fine-tuning和模型训练的关系
概述 Fine-tuning和模型训练不是完全相同的概念,但它们之间有密切的关系,都是机器学习和深度学习过程中的重要步骤。 模型训练是一个更广泛的概念,指的是使用数据去调整模型的内部参数,以使得模型能够从输入数据中学习并做出预测…...

【ai】livekit:Agents 3 : pythonsdk和livekit-agent的可编辑模式下的安装
livekit-agent 依赖于livekit、livekit-api、livekit-protocol 其中livekit就是livekkit-rtc: 包含俩sdk 实时互动sdkReal-time SDK for connecting to LiveKit as a participant livekit-api : 服务端sdk https://pypi.org/project/livekit-api/ livekit的python sdk...

【传知代码】BERT论文解读及情感分类实战-论文复现
文章目录 概述原理介绍BERT模型架构任务1 Masked LM(MLM)任务2 Next Sentence Prediction (NSP)模型输入下游任务微调GLUE数据集SQuAD v1.1 和 v2.0NER 情感分类实战IMDB影评情感数据集数据集构建模型构建 核心代码超参数设置训练结果注意事项 小结 本文…...