JVM垃圾回收GC 详解(java1.8)

news2024/12/23 10:09:54

目录

垃圾判断算法(你是不是垃圾?)

引用计数法

可达性算法

对象的引用

强引用

软引用

弱引用

虚引用

对象的自我救赎

垃圾回收算法--分代

标记清除算法

复制算法

标记整理法

垃圾处理器


垃圾判断算法(你是不是垃圾?)

引用计数法

最容易想到的一种方式是引用计数法,啥叫引用计数法,简单地说,就是对象被引用一次,在它的对象头上加一次引用次数,如果没有被引用(引用次数为 0),则此对象可回收

String ref = new String("Java");

以上代码 ref 引用了右侧定义的对象,所以引用次数是 1
 

如果在上述代码后面添加一个 ref = null,则由于对象没被引用,引用次数置为 0,由于不被任何变量引用,此时即被回收,动图如下

看起来用引用计数确实没啥问题了,不过它无法解决一个主要的问题:循环引用!啥叫循环引用

public  class TestRC {    
    TestRC instance;    
    public TestRC(String name) {    
    }    
    public static  void main(String[] args) {        
        // 第一步	
        TestRC a = new TestRC("a");	
        TestRC b = new TestRC("b");        
        
        // 第二步	
        a.instance = b;	
        b.instance = a;        
        
        // 第三步	
        a = null;	
        b = null;    
    }
}

按步骤一步步画图

到了第三步,虽然 a,b 都被置为 null 了,但是由于之前它们指向的对象互相指向了对方(引用计数都为 1),所以无法回收,也正是由于无法解决循环引用的问题,所以现代虚拟机都不用引用计数法来判断对象是否应该被回收。

可达性算法

现代虚拟机基本都是采用这种算法来判断对象是否存活,可达性算法的原理是以一系列叫做 GC Root 的对象为起点出发,引出它们指向的下一个节点,再以下个节点为起点,引出此节点指向的下一个结点。(这样通过 GC Root 串成的一条线就叫引用链),直到所有的结点都遍历完毕, 如果相关对象不在任意一个以 GC Root 为起点的引用链中,则这些对象会被判断为「垃圾」, 会被 GC 回收。

如图示,如果用可达性算法即可解决上述循环引用的问题,因为从GC Root 出发没有到达 a,b, 所以 a,b 可回收

a, b 对象可回收,就一定会被回收吗? 并不是,对象的 finalize 方法给了对象一次垂死挣扎的机会,当对象不可达(可回收)时,当发生 GC 时,会先判断对象是否执行了 finalize 方法,如果未执行,则会先执行 finalize 方法,我们可以在此方法里将当前对象与 GC Roots 关联,这样执行 finalize 方法之后,GC 会再次判断对象是否可达,如果不可达,则会被回收,如果可达,则不回收!

注意: finalize 方法只会被执行一次,如果第一次执行 finalize 方法此对象变成了可达确实不会回收,但如果对象再次被 GC,则会忽略 finalize 方法,对象会被回收!这一点切记!

虽然可达性分析算法解决了循环依赖的问题,但是也产生了许多新的问题。进行可达性分析(搜索)的过程是需要时间的,而此时程序也是在并行运行着,不断产生新的对象,丢弃无用的对象。整个内存的状态是在变化的,所以目前主流的垃圾回收算法大多都要进行一种操作 Stop-The-World,翻译过来即停止世界,即暂停所有的用户线程,避免内存堆的状态发生变化。

那么这些 GC Roots 到底是什么东西呢,哪些对象可以作为 GC Root 呢,有以下几类

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

虚拟机栈中引用的对象

如下代码所示,a 是栈帧中的本地变量,当 a = null 时,由于此时 a 充当了 GC Root 的作用,a 与原来指向的实例 new Test() 断开了连接,所以对象会被回收。

public class Test {
    public static  void main(String[] args) {
        Test a = new Test();	
        a = null;    
    }
}

方法区中类静态属性引用的对象

如下代码所示,当栈帧中的本地变量 a = null 时,由于 a 原来指向的对象与 GC Root (变量 a) 断开了连接,所以 a 原来指向的对象会被回收,而由于我们给 s 赋值了变量的引用,s 在此时是类静态属性引用,充当了 GC Root 的作用,它指向的对象依然存活!

public  class Test {
    public  static Test s;    
    public static  void main(String[] args) {
        Test a = new Test();	
        Test.s = new Test();	
        a = null;    
    }
}

方法区中常量引用的对象

如下代码所示,常量 s 指向的对象并不会因为 a 指向的对象被回收而回收

public  class Test {
    public  static  final Test s = new Test();        
    public static void main(String[] args) {	    
        Test a = new Test();	    
        a = null;        
    }
}

 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

方法调用中的时候,是作为 GC Root ,如果方法调用完成了,这些 JNI 引用对象是可以被回收的,如果不想要这些 JNI 引用对象被回收,可以设置为全局变量。

如果想让某些局部引用在从 C 函数返回后不被 JVM 回收,则可以借助 JNI 函数 NewGlobalRef,将该局部引用转换为全局引用。被全局引用的对象,不会被 JVM 回收,只能通过 JNI 函数 DeleteGlobalRef 消除全局引用后,才可以被回收。

这是简单给不清楚本地方法为何物的童鞋简单解释一下:所谓本地方法就是一个 java 调用非 java 代码的接口,该方法并非 Java 实现的,可能由 C 或 Python 等其他语言实现的, Java 通过 JNI 来调用本地方法, 而本地方法是以库文件的形式存放的(在 WINDOWS 平台上是 DLL 文件形式,在 UNIX 机器上是 SO 文件形式)。

当调用 Java 方法时,虚拟机会创建一个栈桢并压入 Java 栈,而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,不会在 Java 栈祯中压入新的祯,虚拟机只是简单地动态连接并直接调用指定的本地方法。

JNIEXPORT void JNICALL Java_com_pecuyu_jnirefdemo_MainActivity_newStringNative(JNIEnv *env, jobject instance,jstring jmsg) {...   // 缓存String的class   jclass jc = (*env)->FindClass(env, STRING_PATH);}

 

如上代码所示,当 java 调用以上本地方法时,jc 会被本地方法栈压入栈中, jc 就是我们说的本地方法栈中 JNI 的对象引用,因此只会在此本地方法执行完成后才会被释放。一旦从 C 函数中返回至 Java 方法中,那么局部引用将会失效,JVM 在整个 Tracing 过程中就不再考虑这些局部引用,也就是说,一段时间后,局部引用占用的内存将会被回收。

对象的引用

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可达,判定对象是否存活都和“引用”离不开关系。

在JDK 1.2版之前,Java里面的引用是很传统的定义:如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表某块内存、某个对象的引用。这种定义并没有什么不对,只是现在看来有些过于狭隘了,一个对象在这种定义下只有“被引用”或者“未被引用”两种状态,对于描述一些“食之无味,弃之可惜”的对象就显得无能为力。譬如我们希望能描述一类对象:当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象——很多系统的缓存功能都符合这样的应用场景。

在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Re-ference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。

并不是说只要是和 GC Roots 有一条联系(Reference Chain),对象就是存活的,它还与对象引用级别有关。

强引用

属于普通常见的那种,也就是我们 new 出来的对象,无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。

软引用

只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。

弱引用

弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止(不太对,如果是 Young GC 不一定会回收,如果是 Full GC 则一定会回收,参考下面例子)。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用。

虚引用

虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供了PhantomReference类来实现虚引用。

栗子说明一下

添加配置JVM参数 -XX:+PrintGC -XX:+PrintGCDateStamps -Xmx5m -Xms5m

public class test {

    static class MyOb {
        private int i = 1;

        // 覆盖finalize()方法
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("====== obj [" + this.toString() + "] is gc =======");
        }
    }

    // 消耗大量内存
    public static void drainMemory() {
        String[] array = new String[1024 * 10];
        for(int i = 0; i < 1024 * 10; i++) {
            for(int j = 'a'; j <= 'e'; j++) {
                array[i] += (char)j;
            }
        }
    }
}

首先先写一个 覆盖 finalize 方法的对象,进行 GC 的时候会进入,另外有个消耗大量内存的方法。

    public static void main(String[] args) {
        MyOb ob = new MyOb();
        drainMemory();
    }

强引用的,我们看看输出

2020-08-28T16:33:56.546+0800: [GC (Allocation Failure)  1024K->616K(5632K), 0.0011773 secs]
2020-08-28T16:33:56.578+0800: [GC (Allocation Failure)  1633K->827K(5632K), 0.0006751 secs]
2020-08-28T16:33:56.591+0800: [GC (Allocation Failure)  1851K->1049K(5632K), 0.0022424 secs]
2020-08-28T16:33:56.594+0800: [GC (Allocation Failure)  2073K->1273K(5632K), 0.0010249 secs]
2020-08-28T16:33:56.596+0800: [GC (Allocation Failure)  2297K->1521K(5632K), 0.0011336 secs]

明显内存不足进行 GC 的时候,判断到 ob 可达的,是不会进行回收的。

然后看看软引用

    public static void main(String[] args) {
        SoftReference<MyOb> ob = new SoftReference<>(new MyOb());
        drainMemory();
    }
2020-08-28T16:37:38.730+0800: [GC (Allocation Failure)  1024K->584K(5632K), 0.0013387 secs]
2020-08-28T16:37:38.764+0800: [GC (Allocation Failure)  1601K->842K(5632K), 0.0007544 secs]
2020-08-28T16:37:38.784+0800: [GC (Allocation Failure)  1866K->1066K(5632K), 0.0011751 secs]
2020-08-28T16:37:38.787+0800: [GC (Allocation Failure)  2090K->1314K(5632K), 0.0010511 secs]
2020-08-28T16:37:38.789+0800: [GC (Allocation Failure)  2338K->1554K(5632K), 0.0009488 secs]

同样在没有内存溢出时候,他和强引用是没有区别的

我们把消耗内存的算法改一下,让它产生内存溢出

 // 消耗大量内存
    public static void drainMemory() {
        String[] array = new String[1024 * 1000];
        for(int i = 0; i < 1024 * 10; i++) {
            for(int j = 'a'; j <= 'b'; j++) {
                array[i] += (char)j;
            }
        }
    }

再重新跑一下

2020-08-28T16:39:38.310+0800: [GC (Allocation Failure)  1024K->624K(5632K), 0.0012934 secs]
2020-08-28T16:39:38.351+0800: [GC (Allocation Failure)  1641K->884K(5632K), 0.0011559 secs]
2020-08-28T16:39:38.365+0800: [GC (Allocation Failure)  1407K->934K(5632K), 0.0011081 secs]
2020-08-28T16:39:38.366+0800: [GC (Allocation Failure)  934K->942K(5632K), 0.0007891 secs]
2020-08-28T16:39:38.367+0800: [Full GC (Allocation Failure)  942K->706K(5632K), 0.0035185 secs]
2020-08-28T16:39:38.371+0800: [GC (Allocation Failure)  706K->706K(5632K), 0.0002779 secs]
2020-08-28T16:39:38.371+0800: [Full GC (Allocation Failure)  706K->690K(5632K), 0.0051638 secs]
====== obj [com.example.demo.conf.test$MyOb@51fe5ed6] is gc =======
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.example.demo.conf.test.drainMemory(test.java:33)
	at com.example.demo.conf.test.main(test.java:27)

 可以看到再抛出 OOM 之前,会先清理掉这个对象(强引用 OOM 前如果还是可达的,也不会清理掉,可以自己试一下)

然后看弱引用

    public static void main(String[] args) {
        ReferenceQueue<MyOb> queue = new ReferenceQueue<>();
        //创建一个引用给定对象并在给定队列中注册的新的弱引用。
        WeakReference<MyOb> ob = new WeakReference<>(new MyOb() , queue);
        drainMemory();
        Thread.sleep(1000);
        System.out.println("ob = " + ob.get());
        System.out.println("queue poll = " + queue.poll());
    }

记得把之前的调OOM的内存消耗部分修改回来

得到的结果有多种情况

2020-08-28T17:38:16.625+0800: [GC (Allocation Failure)  1024K->600K(5632K), 0.0008772 secs]
2020-08-28T17:38:16.657+0800: [GC (Allocation Failure)  1624K->839K(5632K), 0.0007530 secs]
2020-08-28T17:38:16.671+0800: [GC (Allocation Failure)  1863K->1009K(5632K), 0.0005883 secs]
====== obj [com.example.demo.conf.test$MyOb@51fe5ed6] is gc =======
2020-08-28T17:38:16.672+0800: [GC (Allocation Failure)  2033K->1201K(5632K), 0.0006779 secs]
2020-08-28T17:38:16.674+0800: [GC (Allocation Failure)  2225K->1353K(5632K), 0.0005505 secs]
2020-08-28T17:38:16.675+0800: [GC (Allocation Failure)  2377K->1529K(5632K), 0.0010291 secs]
ob = null
queue poll = java.lang.ref.WeakReference@270421f5
2020-08-28T17:39:50.249+0800: [GC (Allocation Failure)  1024K->592K(5632K), 0.0007809 secs]
2020-08-28T17:39:50.283+0800: [GC (Allocation Failure)  1610K->843K(5632K), 0.0029328 secs]
2020-08-28T17:39:50.304+0800: [GC (Allocation Failure)  1867K->1045K(5632K), 0.0008099 secs]
2020-08-28T17:39:50.306+0800: [GC (Allocation Failure)  2069K->1157K(5632K), 0.0012691 secs]
2020-08-28T17:39:50.308+0800: [GC (Allocation Failure)  2181K->1341K(5632K), 0.0009482 secs]
2020-08-28T17:39:50.313+0800: [GC (Allocation Failure)  2365K->1485K(5632K), 0.0014565 secs]
ob = com.example.demo.conf.test$MyOb@270421f5
queue poll = null

可以发现,Young GC 是不一定会回收,由系统来判断回收的时机。

然后我们改一下代码,改用 System.gc() 触发 FULL GC 看看有什么不同

    public static void main(String[] args) {
        ReferenceQueue<MyOb> queue = new ReferenceQueue<>();
        //创建一个引用给定对象并在给定队列中注册的新的弱引用。
        WeakReference<MyOb> ob = new WeakReference<>(new MyOb() , queue);
        System.gc();
        System.out.println("ob = " + ob.get());
        System.out.println("queue poll = " + queue.poll());
    }
2020-08-28T18:25:22.967+0800: [GC (Allocation Failure)  1024K->632K(5632K), 0.0014284 secs]
2020-08-28T18:25:23.001+0800: [GC (Allocation Failure)  1650K->846K(5632K), 0.0008219 secs]
2020-08-28T18:25:23.016+0800: [GC (System.gc())  1370K->886K(5632K), 0.0005198 secs]
2020-08-28T18:25:23.017+0800: [Full GC (System.gc())  886K->707K(5632K), 0.0045606 secs]
====== obj [com.example.demo.conf.test$MyOb@51fe5ed6] is gc =======
ob = null
queue poll = null

可以发现 GC 后肯定会取消对象的引用

注意:上面的控制台顺序不一定和真实的gc顺序一样,关键得看 ob.get() 是否为空去判断

对象的自我救赎

即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,那么虚拟机在发生GC的时候,只需要判断对象可达 GC Roots 即可判定是否可回收。

如果这个对象被判定为确有必要执行 finalize() 方法,那么该对象将会被放置在一个名为 F-Queue 的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的 Finalizer 线程去执行它们的 finalize() 方法。这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束。这样做的原因是,如果某个对象的 finalize() 方法执行缓慢,或者更极端地发生了死循环,将很可能导致 F-Queue 队列中的其他对象永久处于等待,甚至导致整个内存回收子系统的崩溃。

finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后收集器将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了。从代码清单3-2中我们可以看到一个对象的 finalize() 被执行,但是它仍然可以存活。

并不鼓励大家使用这个方法来拯救对象。相反,笔者建议大家尽量避免使用它,因为它并不能等同于C和C++语言中的析构函数,而是Java刚诞生时为了使传统C、C++程序员更容易接受Java所做出的一项妥协。它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序,如今已被官方明确声明为不推荐使用的语法。有些教材中描述它适合做“关闭外部资源”之类的清理性工作,这完全是对finalize()方法用途的一种自我安慰。finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、更及时,所以笔者建议大家完全可以忘掉Java语言里面的这个方法。

垃圾回收算法--分代

标记清除算法

步骤很简单

  1. 先根据可达性算法标记出相应的可回收对象(图中黄色部分)
  2. 对可回收的对象进行回收

优点是不需要额外空间

缺点是两次扫描耗时严重,会产生内存碎片

复制算法

把堆等分成两块区域, A 和 B,区域 A 负责分配对象,区域 B 不分配, 对区域 A 使用以上所说的标记法把存活的对象标记出来,然后把区域 A 中存活的对象都复制到区域 B(存活对象都依次紧邻排列)最后把 A 区对象全部清理掉释放出空间,这样就解决了内存碎片的问题了。

不过复制算法的缺点很明显,比如给堆分配了 500M 内存,结果只有 250M 可用,空间平白无故减少了一半!这肯定是不能接受的!另外每次回收也要把存活对象移动到另一半,效率低下(我们可以想想删除数组元素再把非删除的元素往一端移,效率显然堪忧)

标记整理法

前面两步和标记清除法一样,不同的是它在标记清除法的基础上添加了一个整理的过程 ,即将所有的存活对象都往一端移动, 紧邻排列(如图示),再清理掉另一端的所有区域,这样的话就解决了内存碎片的问题。

但是缺点也很明显:每进一次垃圾清除都要频繁地移动存活的对象,效率十分低下。

垃圾处理器

名称

负责区域

使用算法

描述

Serial

young

复制算法

单线程,Client默认新生代处理器

ParNew

young

复制算法

多线程,Serial的多线程版

Parallel Scavenge

young

复制算法

多线程,注重停顿时间和吞吐量

Serial Old

old

标记整理

Client默认老年代处理器,Serial的老年版本,CMS的后备方案

Parallel Old

old

标记整理

多线程,注重停顿时间和吞吐量

CMS

old

标记清除

整个过程分为4个步骤:

  1. 初始标记(CMS initial mark) -stop the world
  2. 并发标记(CMS concurrent mark)
  3. 重新标记(CMS remark) -stop the world
  4. 并发清除(CMS concurrent sweep)

G1

young/old

整体标记整理,

局部复制

多线程,内存分区(各分区大小相同,同一分区逻辑角色相同,都是新生代等,回收以分区为单位,复制到另一个分区),首先回收垃圾最多的分区,低停顿,使用暂停预测模型。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Anaconda+PyTorch环境搭建

AnacondaPyTorch环境搭建 环境Anaconda安装配置下载镜像 cuda和cudnncudacudnn pytorch参考文章 环境 win10 22hx nvidia driver 528.89 Anaconda安装 在清华镜像中选择合适的版本及对应系统 https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 下载好之后一路next即可…

vscode笔记

vscode怎么中止运行 控制台—输出&#xff0c;结束运行&#xff1a; 控制台右击&#xff0c;然后点击Stop Code Run或者CtrlAltM快捷键 停止正在运行的Python脚本 在VS Code的右下角&#xff0c;选择正在运行的终端&#xff0c; 点右键&#xff0c;终止终端 VS code怎么终止…

简单的小型C++项目怎么用CMAKE进行管理

项目目录&#xff1a; 根目录下共有两个文件夹&#xff0c;分别为include、src&#xff0c;有两个文件&#xff0c;分别为CMakeLists.txt和main.cpp main函数 可以看出&#xff0c;include了func.h&#xff0c;且func.h的声明在include文件夹下&#xff0c;定义在src文件夹下的…

全网唯一!Matlab世界顶尖艺术品配色包Rmetbrewer

想要绘制一幅颜色搭配合理、好看又不花哨的论文插图&#xff0c;该如何操作呢&#xff1f; 正所谓求其上者得其中&#xff0c;求其中者得其下。 那么&#xff0c;向高手借鉴思路&#xff0c;无疑是一种不落下乘的好策略。 而在色彩搭配领域&#xff0c;像莫奈、梵高这些世界…

WPS表格数据出现绿色小三角,单引号,E+的原因说明和完美解决方案,终结版。

复盘问题的原因&#xff0c;了解原因的原因&#xff0c;预测事件的结果&#xff0c;推测结果产生的结果。 问题描述&#xff1a;好好的数据&#xff0c;复制进wps&#xff0c;左上角就会出现绿色三角。怎么去掉呢&#xff1f; 迷惑问题2&#xff1a;点击之后&#xff0c;绿色三…

代码优化- 前端优化

常量折叠 基本思想&#xff1a;在编译期间计算表达式的值&#xff08;编译时静态计算&#xff09; 例如&#xff1a;a 3 5 > a 8&#xff0c;if (true && false) ... > if (false) 好处是&#xff1a;语法树的节点数量减少了&#xff0c;意味着编译器要维护…

STM32—0.96寸OLED液晶显示

本文主要介绍基于STM32F103的0.96寸的OLED液晶显示&#xff0c;详细关于0.96寸OLED液晶屏幕的介绍可参考这篇博客&#xff1a;https://blog.csdn.net/u011816009/article/details/130119426 一、简介 OLED被称为有机激光二极管&#xff0c;也被称为有机激光显示&#xff0c;O…

学生宿舍管理系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87700423 更多系统资源库…

Sprinboot聚合项目归荑

1、前言 在创建springboot项目时&#xff0c;都会有一个Main方法。如果将Springboot项目设计成聚合项目时&#xff0c;我们是不是要把所有的子项目都按照Springboot的方式创建呢&#xff1f;如果是会出现什么问题&#xff0c;以及我们怎么解决呢&#xff1f; 如果我们使用maven…

ChatGPT原理详解+实操

言 ChatGPT已近火了快大半年了&#xff0c;从去年刚出来的时候小编就关注了一下&#xff0c;也具体的使用过&#xff0c;然后就惊为天人&#xff0c;再然后就没有然后了&#xff0c;因为小编那段时间沉迷于AIGC了。ChatGPT今年开年后更是火的一塌糊涂&#xff0c;无论是行业内…

chatgpt智能提效职场办公-ppt怎么加音乐背景

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; 在 PowerPoint 中&#xff0c;您可以轻松地将音乐作为背景音乐添加到您的演示文稿中。下面是步骤&#xff1a; 打开您的 PowerPoint 演…

【大厂直通车】哔哩哔哩日常实习_测开面经

📑哈喽,大家好,我是小浪;本篇博客更新的是最新B站测开面经,本专栏非常适合目前准备找实习,或者准备冲秋招测试,测开方向的同学阅读订阅,持续更新各大厂真题面经,带你成为offer收割机!! 🧃对于订阅本专栏的同学们,博主在努力更新,只需要一杯奶茶钱,订阅本专栏,…

【命名空间】using namespace std是什么意思?

目录 一、前言二、命名空间2.1命名空间的定义2.2命名空间的成员调用2.2.1加命名空间名称及作用域限定符2.2.2使用using将命名空间中某个成员引入2.2.3使用using namespace 命名空间名称 引入 2.3命名空间的成员的优先级 三、using namespace std 一、前言 我们很多接触过C编程…

考了华为认证,如何找工作?能进哪些公司?有没有前景?

哪些人适合考华为&#xff1f;考了华为认证好不好找工作&#xff1f;这应该是困扰很多网工的问题。 俗话说&#xff0c;男怕入错行&#xff0c;女怕嫁错郎。如何选择一条适合自己的道路&#xff0c;提前做好职业规划&#xff0c;对个人职业发展至关重要。 下面就为大家整理了…

C#基础学习--预处理指令

目录 什么是预处理指令 基本规则 #define 和 #undef 指令 条件编译 条件编译结构 诊断指令 行号指令 ​编辑 区域指令 #pragam warning 指令 什么是预处理指令 源代码指定了程序的定义&#xff0c;预处理指令指示编译器如何处理源代码 基本规则 #define 和 #undef 指令…

基于uniapp+vue微信小程序的在职考研学习辅导系统

1系统具有良好的集成性&#xff0c;提供标准接口&#xff0c;以实现与其他相关系统的功能和数据集成。开放性好&#xff0c;便于系统的升级维护、以及与各种信息系统进行集成。功能定位充分考虑平台服务对象的需求。 一个微信小程序由.js、.json、.wxml、.wxss四种文件构成&…

CSS学习(4) - 链接,列表和表格

文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 文章目录 CSS链接CSS列表CSS表格 CSS链接 链接样式&#xff0c;通过 color font-family background 等来设置&#xff1a; a{color: hotpink;font-size: 30px; }可以指定不同的链接时刻来指定不同的样式&#xff1a; l…

Python中随机梯度下降法

随机梯度下降法 批量梯度下降使用全部的训练样本来计算梯度&#xff0c;并更新模型参数&#xff0c;因此它的每一次迭代计算量较大&#xff0c;但对于凸优化问题&#xff0c;可以保证每次迭代都朝着全局最优解的方向前进&#xff0c;收敛速度较快&#xff0c;最终收敛到的结果…

synchronized原理:

vm中每个对象都会有一个监视器Monitor&#xff0c;监视器和对象一起创建、销毁。监视器相当于一个用来监视这些线程进入的特殊房间&#xff0c;其义务是保证&#xff08;同一时间&#xff09;只有一个线程可以访问被保护的临界区代码块。每一个锁都对应一个monitor对象&#xf…

如何训练自己的大型语言模型

如何使用 Databricks、Hugging Face 和 MosaicML 训练大型语言模型 (LLM) 介绍 大型语言模型&#xff0c;如 OpenAI 的 GPT-4 或谷歌的 PaLM&#xff0c;已经席卷了人工智能世界。然而&#xff0c;大多数公司目前没有能力训练这些模型&#xff0c;并且完全依赖少数大型科技公司…