3-3-多线程-TheadLocal内存泄漏

news2024/11/20 20:34:28

Java TheadLocal内存泄漏

1、引言

组内来了一个实习生,看这小伙子春光满面、精神抖擞、头发微少,我心头一喜:绝对是个潜力股。为了帮助小伙子快速成长,我给他分了一个需求,这不需求刚上线几天就出网上问题了😭后台监控服务发现内存一直在缓慢上升,初步怀疑是内存泄露。

把实习生的PR都找出来仔细review,果然发现问题了。由于公司内部代码是保密的,这里简单写一个demo还原场景(忽略代码风格问题)。

public class ThreadPoolDemo {
    private static final ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 100; ++i) {
            poolExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    ThreadLocal<BigObject> threadLocal = new ThreadLocal<>();
                    threadLocal.set(new BigObject());
                    // 其他业务代码
                }
            });
            Thread.sleep(1000);
        }
    }
    static class BigObject {
        // 100M
        private byte[] bytes = new byte[100 * 1024 * 1024];
    }
}

代码分析:

  • 创建一个核心线程数和最大线程数都为5的线程池,保证线程池里一直会有5个线程在运行。
  • 使用for循环向线程池中提交了100个任务。
  • 定义了一个ThreadLocal类型的变量,Value类型是大对象。
  • 每个任务会向threadLocal变量里塞一个大对象,然后执行其他业务逻辑。
  • 由于没有调用线程池的shutdown方法,线程池里的线程还是会在运行。

乍一看这代码好像没有什么问题,那为什么会导致服务内存还高居不下呢?

代码中给threadLocal赋值了一个大的对象,但是执行完业务逻辑后没有调用remove方法,最后导致线程池中5个线程的threadLocal变量中包含的大对象没有被释放掉,出现了内存泄露。

大家说说这样的实习生还能留不?

2、谈谈ThreadLocal

可以把 ThreadLocal 视为一个普通变量,他与普通的变量之间的区别在于,ThreadLocal 变量只属于某个线程

案例:

class Person{
    String name = "zhangsan";
}
public class ThreadLocal1 {
    static ThreadLocal<Person> t1 = new ThreadLocal<>();
    static ThreadLocal<String> t2 = new ThreadLocal<>();
    public static void main(String[] args) {
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(t1.get());   //null 即使第二个线程new Person() 
        }).start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t1.set(new Person());  //只有自己的线程能拿到这个Person对象
            //t1.remove();
        }).start();
    }
}

在这个案例中,大家可以跟踪set方法查看源码, 首先是获取当前线程,通过当前线程拿去一个threadlocalmap,也就是说每一个线程内部都维护自己的一个属性Map,这个map类似于hashmap,不过数组是一个Entry数组,元素是一个Entry对象,然后把ThreadLocal对象作为entry的key

3、ThreadLocal的value值存在哪

实习生说他以为线程任务结束了threadLocal赋值的对象会被JVM垃圾回收,很疑惑为什么会出现内存泄露。作为师傅我肯定要给他把原理讲透呀。

ThreadLocal类提供set/get方法存储和获取value值,但实际上ThreadLocal类并不存储value值,真正存储是靠ThreadLocalMap这个类,ThreadLocalMap是ThreadLocal的一个静态内部类,它的key是ThreadLocal实例对象,value是任意Object对象。

ThreadLocalMap类的定义

static class ThreadLocalMap {
    // 定义一个table数组,存储多个threadLocal对象及其value值
    private Entry[] table;
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
    // 定义一个Entry类,key是一个弱引用的ThreadLocal对象
    // value是任意对象
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    // 省略其他
}

进一步分析ThreadLocal类的代码,看set和get方法如何与ThreadLocalMap静态内部类关联上。

2-1 ThreadLocal类set方法

public class ThreadLocal<T> {
 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    // 省略其他方法
}

set的逻辑比较简单,就是获取当前线程的ThreadLocalMap,然后往map里添加KV,K是当前ThreadLocal实例,V是我们传入的value。这里需要注意一下,map的获取是需要从Thread类对象里面取,看一下Thread类的定义。

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
    //省略其他
}

Thread类维护了一个ThreadLocalMap的变量引用。

2-2 ThreadLocal类get方法

get获取当前线程的对应的私有变量,是之前set或者通过initialValue的值,代码如下:

class ThreadLocal<T> {
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
}

代码逻辑分析:

  • 获取当前线程的ThreadLocalMap实例;
  • 如果不为空,以当前ThreadLocal实例为key获取value;
  • 如果ThreadLocalMap为空或者根据当前ThreadLocal实例获取的value为空,则执行setInitialValue();

2-3 ThreadLocal相关类的关系

看了上面的分析是不是对Thread,ThreadLocal,ThreadLocalMap,Entry这几个类之间的关系有点晕了,没关系我专门画了一个UML类图来总结(忽略UML标准语法)。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UShOViSg-1675328414662)(assets/image-20210715193126972.png)]

  • 每个线程是一个Thread实例,其内部维护一个threadLocals的实例成员,其类型是ThreadLocal.ThreadLocalMap。
  • 通过实例化ThreadLocal实例,我们可以对当前运行的线程设置一些线程私有的变量保存到ThreadLocalMap中,通过调用ThreadLocal的set和get方法存取。
  • ThreadLocal本身并不是一个容器,我们存取的value实际上存储在ThreadLocalMap中,ThreadLocal只是作为TheadLocalMap的key。
  • 每个线程实例都对应一个TheadLocalMap实例,我们可以在同一个线程里实例化很多个ThreadLocal来存储很多种类型的值,这些ThreadLocal实例分别作为key,对应各自的value,最终存储在Entry table数组中。
  • 当调用ThreadLocal的set/get进行赋值/取值操作时,首先获取当前线程的ThreadLocalMap实例,然后就像操作一个普通的map一样,进行put和get。

3、ThreadLocal内存模型原理

经过上面的分析我们对ThreadLocal相关的类设计已经非常清楚了,下面通过一张图更加深入理解一下ThreadLocal的内存存储。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uSZfb9ez-1675328414665)(assets/image-20210715193455116.png)]

图中左边是栈,右边是堆。线程的一些局部变量和引用使用的内存属于Stack(栈)区,而普通的对象是存储在Heap(堆)区。

  • 线程运行时,我们定义的TheadLocal对象被初始化,存储在Heap,同时线程运行的栈区保存了指向该实例的引用,也就是图中的ThreadLocalRef。
  • 当ThreadLocal的set/get被调用时,虚拟机会根据当前线程的引用也就是CurrentThreadRef找到其对应在堆区的实例,然后查看其对用的TheadLocalMap实例是否被创建,如果没有,则创建并初始化。
  • Map实例化之后,也就拿到了该ThreadLocalMap的句柄,那么就可以将当前ThreadLocal对象作为key,进行存取操作。
  • 图中的虚线,表示key对应ThreadLocal实例的引用是个弱引用。

再细说一下弱引用,如图:

WeakReference weak = new WeakReference(new test());

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2t2wO8gk-1675328414667)(assets/image-20210806151605284.png)]

weak是一个强引用,指向WR空间,但是WR里又有一个弱引用指向了new test()对象,所以在垃圾回收器清理时清理的是new test()而不是weak。所以这样看是不是一目了然了呢

4、强引用弱引用的概念

ThreadLocalMap的key是一个弱引用类型,源代码如下:

static class ThreadLocalMap {
    // 定义一个Entry类,key是一个弱引用的ThreadLocal对象
    // value是任意对象
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    // 省略其他
}

下面解释一下常见的几种引用概念。

4-1 强引用

强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下

public class T1_StrongReference {
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(o);
        System.gc();
        System.out.println(o);
    }
}

内存空间不足时,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用对象来解决内存不足的问题

如果强引用对象不使用时,需要弱化从而使GC能够回收,如下

o = null;
显式地设置o对象null,或让其超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象

在一个方法的内部有一个强引用,这个引用保存在Java栈中,而真正的引用内容(Object)保存在Java堆中。当这个方法运行完成后,就会退出方法栈,则引用对象的引用数为0,这个对象会被回收。但是如果这个strongReference是全局变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收,如下代码

 public void test() {
        Object strongReference = new Object();
        // 省略其他操作
}

4-2 弱引用

弱引用软引用的区别在于:具有弱引用的对象拥有更短暂生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定很快发现那些只具有弱引用的对象

String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str); //str 变成了一个弱引用对象

注意:如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference来记住此对象。

String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
// 弱引用转强引用
String strongReference = weakReference.get();

案例解析

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

从运行结果看:

一旦垃圾回收线程发现了弱引用对象,在下一次GC过程中就会对其进行回收

4-3 软引用

如果一个对象只具有软引用,则内存空间充足时,垃圾回收器不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。应用场景:一般用来做图片缓存

软引用可用来实现内存敏感的高速缓存。在JDK 1.2之后,提供了SoftReference类来实现软引用。

public class T2_SoftReference {
    public static void main(String[] args) throws InterruptedException {
        SoftReference<byte[]> soft = new SoftReference<>(new byte[1024*1024*10]);
        System.out.println(soft.get());
        System.gc();
        TimeUnit.SECONDS.sleep(3);
        System.out.println(soft.get());

        byte[] b = new byte[1024*1024*15];
        System.out.println(soft.get());
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s2F5sIwL-1675328414668)(assets/image-20210806142459485.png)]

-Xmx20m

注意:软引用对象是在jvm内存不够的时候才会被回收,我们调用System.gc()方法只是起通知作用,JVM什么时候扫描回收对象是JVM自己的状态决定的。就算扫描到软引用对象也不一定会回收它,只有内存不够的时候才会回收。该案例中,因为第二次要存15m的对象到堆内存,由于堆内存一共才20m,故回收第一个10m的byte数组

软引用的使用场景

浏览器的后退按钮。按后退时,这个后退时显示的网页内容是重新进行请求还是从缓存中取出呢?这就要看具体的实现策略了。

如果一个网页在浏览结束时就进行内容的回收,则按后退查看前面浏览过的页面时,需要重新构建;
如果将浏览过的网页存储到内存中会造成内存的大量浪费,甚至会造成内存溢出

4-4 虚引用

也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。引用顾名思义,就是形同虚设。与其他几种引用都不同,虚引用不会决定对象的生命周期。如果一个对象是虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

应用场景:

虚引用主要用来跟踪对象被垃圾回收器回收的活动。

虚引用软引用弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。

String str = new String("abc");
ReferenceQueue queue = new ReferenceQueue();
// 创建虚引用,要求必须与一个引用队列关联
PhantomReference pr = new PhantomReference(str, queue);

程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要进行垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动

Java中4种引用的级别和强度由高到低依次为:强引用 -> 软引用 -> 弱引用 -> 虚引用

5、ThreadLocal内存泄漏分析

在每一个线程的threadlocalmap中有很多的Entry条目,每一个条目里包含的都是key和value。其中k是一个弱引用,在这个弱引用里包含了一个Threadlocal。而此时的t1是强引用指向了Threadlocal,所以此时不会发生问题,即使gc发生也不会,但是如果t1被置空了(t1 = null),那么就表示key中包含的threadlocal只有一个弱引用指向了,那么在垃圾回收器来回收时会将这个key回收掉,注意此时的这个key指的是传递给了父类的ThreadLocal,所以如果ThreadLocal被回收了的话那么就表示此时的key直接为null了。那么此时就会使一个null指向了value对象,一个null对应一个value,垃圾回收器无法通过null找到这个value,因此这个value对象永远无法被回收。即此时内存泄漏发生了。所以此时的建议是将为空的条目手动remove掉,即调用t1的remove( )方法,该方法是直接删除entry键值对。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9HRYTjeZ-1675328414670)(assets/image-20210806153259723.png)]

对此需要解答的是ThreadLocal为什么在key上面使用弱引用:

1、如果key使用的是强引用,那么即使t1为null了,但是key的引用指向了ThreadLocal对象,就使得ThreadLocal没办法在这个线程就结束前释放掉,那么这个内存就是一个没办法被访问到的内存了,从而发生内存泄漏。所以这里key不能使用强引用。
2、为什么使用弱引用而不是其他的呢,如果是软引用那么除非内存不够发生gc,否则这片内存一直在,不好,虚引用作用的时对外内存,都不适合。所以最适合的就是弱引用了。当t1被置空了,那么就只有一个弱引用指向了ThreadLocal了,那么当发生gc时就直接回收了。但是这个也会产生问题就是entry中的value的内存泄露问题,所以此时的做法是将key为null的条目remove掉。

所以解决ThreadLocal内存泄漏的问题

当线程运行没结束时,要想释放掉当前线程的ThreadLocalMap保存的entry键值对,那么要首先把ThreadLocal释放掉,即t1 = null,那么让Gc自动回收ThreadLocal对象,其次还要把key对应的Value删除,即tl.remove()
class Person{
    String name = "zhangsan";
}
public class ThreadLocal1 {
    static ThreadLocal<Person> t1 = new ThreadLocal<>();
    static ThreadLocal<String> t2 = new ThreadLocal<>();
    public static void main(String[] args) {
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
                t1.set(new Person());
                t2.set("张三");
                System.out.println(t1.get());
                System.out.println(t2.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                t1.remove(); //删除t1对应的value
                t1 = null;  //让gc回收t1,同理t2 也一样
            } 
        }).start();
    }
}
//当前线程的ThreadLocalMap保存2个key-value键值对

6、ThreadLocal最佳实践

通过前面几小节我们分析了ThreadLocal的类设计以及内存模型,同时也重点分析了发生内存泄露的条件和特定场景。最后结合项目中的经验给出建议使用ThreadLocal的场景:

  • 当需要存储线程私有变量的时候。
  • 当需要实现线程安全的变量时。
  • 当需要减少线程资源竞争的时候。

综合上面的分析,我们可以理解ThreadLocal内存泄漏的前因后果,那么怎么避免内存泄漏呢?

答案就是:每次使用完ThreadLocal,建议调用它的remove()方法,清除数据。

另外需要强调的是并不是所有使用ThreadLocal的地方,都要在最后remove(),因为他们的生命周期可能是需要和项目的生存周期一样长的,所以要进行恰当的选择,以免出现业务逻辑错误!

如果Thread是从Thread Pool中取出,它可能会被复用,此时就一定要保证这个Thread在上一次结束的时候,其关联的ThreadLocal被清空掉,否则就会串到下一次使用

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

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

相关文章

C++ 树进阶系列之线段树和它的延迟更新

1. 前言 线段树和树状数组有相似之处&#xff0c;可以用于解决区间类型的问题。 但两者又各个千秋&#xff0c;树状数组本质是数组&#xff0c;有着树的形&#xff0c;可以借用树的一些概念。线段树是典型的二叉树结构&#xff0c;无论神和形都是树&#xff0c;可以应用树的所…

用 Python 的 tkinter 模块编写一个好看又强大的中国象棋

继上次我的第一版的《中国象棋》程序之后&#xff0c;我又编写了第二版的《中国象棋》程序&#xff0c;关注我的粉丝知道&#xff0c;我在第一篇《中国象棋》的文章末尾说了&#xff0c;我会出第二版的&#xff0c;对第一版感兴趣的朋友们&#xff0c;可以去看看&#xff0c;也…

VueJS 之样式冲突与样式穿透

文章目录参考描述样式冲突现象scoped原理样式穿透深度选择器使用原理顶层元素局限性参考 项目描述搜索引擎Bing哔哩哔哩黑马程序员 描述 项目描述Edge109.0.1518.70 (正式版本) (64 位)操作系统Windows 10 专业版vue/cli5.0.8npm8.19.3VueJS2.6.14 样式冲突 在使用 Vue 进行…

大文件上传/下载

一、前言 大文件上传下载一直以来是前端常用且常考的热门话题。本文将分别介绍大文件上传/下载的思路和前端实现代码。 二、分片上传 整体流程 对文件做切片&#xff0c;选择文件后&#xff0c;对获取到的file对象使用slice方法可以将其按照制定的大小进行切片&#xff0c;…

使用matplotlib,pylab进行python绘图

一提到python绘图&#xff0c;matplotlib是不得不提的python最著名的绘图库&#xff0c;它里面包含了类似matlab的一整套绘图的API。因此&#xff0c;作为想要学习python绘图的童鞋们就得在自己的python环境中安装matplotlib库了&#xff0c;安装方式这里就不多讲&#xff0c;方…

openmmlab学习打卡1

openmmlab学习打卡1通用视觉框架 OpenMMLab通过 conda 安装通用视觉框架 OpenMMLab 基于pytorch实现 其中&#xff1a; 分类算法在 mmclassification 模块下 目标检测在 mmdetection 模块下 分割模型在 mmsegmentation 模块下&#xff08;openmmlab 2.0 版本中加入&#xff09…

洛谷P1885 Moo —— 搜索

This way 题意&#xff1a; 奶牛 Bessie 最近在学习字符串操作&#xff0c;它用如下的规则逐一的构造出新的字符串&#xff1a; S(0)S(0) S(0) moo S(1)S(0)S(1) S(0) S(1)S(0) m ooo S(0) S(0) S(0) moo m ooo moo moomooomoo S(2)S(1)S(2) S(1) S(2)S(1) m oooo S(…

无js实现拖拽边框改变大小的笔记

前言 最近刷抖音看到一款游戏"拣爱",看到这个人手动拖动的很有意思,就想着能不能前端实现,来学习学习,虽然说最终的效果没有gif图片那么好,但是也算实现了,吧… 具体原理 利用resize属性所出现的小拖拽条 再配合::-webkit-scrollbar设置拖拽区域宽度,高度,结合opac…

手动签发证书配置nginx

openssl和ssh基本用法 通过OpenSSL工具生成证书 创建私钥 openssl genrsa -des3 -out server.key 2048 注意&#xff0c;centos版本如果是CentOS Linux release 8.0.1905 (Core)版本&#xff0c;私钥长度不能设置成1024位&#xff0c;必须2048位。不然再最后启动nginx时会出…

java之数组模块

数组定义格式1.1数组概述一次性声明大量的用于存储数据的变量要存储的数据通常都是同类型数据&#xff0c;例如&#xff1a;考试成绩1.2什么是数组数组(array)是一种用于存储多个相同类型数据的存储模型1.3数组的定义格式格式一&#xff1a;数据类型[] 变量名范例&#xff1a; …

h5实现相机

什么是取景器 取景器是什么&#xff1f;取景器是相机的一个专业术语&#xff0c;在前端就是扫描拍照 取景器的实现原理 请求手机的一个媒体类型的视频轨道&#xff0c;利用一个div或者图片作为上层蒙层&#xff0c;然后在利用canvas绘制视频中某一帧的画面绘制为图片。 前期…

HTML基础知识

一个网站由两部分组成&#xff1a;前端和后端。前端主流语言目前是HTML、CSS、JS等。HTML只是描述了页面的内容&#xff08;骨架&#xff09;&#xff0c;CSS才是描述了页面的样式。HTML结构HTML标签HTML代码是由“标签”构成的&#xff0c;HTML描述了页面上有什么东西&#xf…

数字化转型导师坚鹏:银行数字化转型为什么需要融合王阳明心学

在BLM银行数字化转型方法论中&#xff0c;我之所以融合BLM模型与王阳明心学&#xff0c;作为一个工科背景并拥有多年软硬件产品研发经验的人来说&#xff0c;深刻地知道很多人利用了科技的力量做了大量的恶事&#xff0c;而不是善事&#xff0c;如黑客大量盗取、泄漏、贩卖客户…

ESLint 的一些理解

ESLint ESLint 是在 ECMAScript/JavaScript 代码中识别和报告模式匹配的工具&#xff0c;它的目标是保证代码的一致性和避免错误。 为什么要使用ESLint 有的可以帮我们避免错误&#xff1b;有的可以帮我们写出最佳实践的代码&#xff1b;有的可以帮我们规范变量的使用方式&a…

Docker入门之使用Dockerfile 构建镜像(七)

文章目录1. 前言2. Docker file 核心要点2.1 注意事项2.2 Docker file 执行流程2.3 Docker Image、Docker file、Docker Container区别2.4 Dockerfile常用保留字指令2.4.1 FROM2.4.2 MAINTAINER2.4.3 RUN2.4.4 EXPOSE2.4.5 WORKDIR2.4.6 USER2.4.7 ENV2.4.8 ADD2.4.9 COPY2.4.1…

ansible 简单使用

运行过程 1.加载自己的配置文件&#xff0c;默认/etc/ansible/ansible.cfg&#xff1b; 2.查找对应的主机配置文件&#xff0c;找到要执行的主机或者组&#xff1b; 3.加载自己对应的模块文件&#xff0c;如 command&#xff1b; 4.通过ansible将模块或命令生成对应的临时py文…

OpenMMLab 实战营打卡 - 第 一 课

OpenMMLab 实战营打卡 - 第 一 课 复习下总忘的基础知识 卷积的通道数变化 前一层特征纬度&#xff08;通道数&#xff09;决定核的通道数 当前层输出的特征纬度&#xff0c;由核的数量决定 图像尺寸变化 padding 公式&#xff1a;H′H−K12pH^{\prime}H-K12 pH′H−K12p…

电源技术中的安森美 单通道电压电平转换器件FXLP34P5X 适合便携式应用方案

电源技术中的安森美 单通道电压电平转换器件FXLP34P5X 适合便携式应用方案 &#xff1a;输入转换器电源电压为VCC1&#xff0c;输出转换器电源电压为VCC。 该器件使用1.0V至3.6V的VCC值运行&#xff0c;主要用于要求超低功耗的便携式应用。内部电路由最小量的缓冲器级组成&…

普通大学生自学 JAVA 怎样才能进大厂?

前言 可以看一下现在大厂对于Java方面的要求 阿里 百度 腾讯 从上面可以看出&#xff0c;无论是阿里、百度亦或是腾讯对于Java方面的要求是比较高的&#xff0c;可以说要求的是一个全面&#xff0c;所以想要进入大厂&#xff0c;不能操之过急&#xff0c;需要先从基础做起&am…

php报错SERVER SENT CHARSET (255) UNKNOWN

配置文件PHP.ini修改打开; extension_dir "ext"&#xff0c;修改成; extension_dir "./" ; On windows: extension_dir "自己php的存放路径\ext"2.打开extensionmsql.dll; For example, on Windows: ;extensionmsql.dll3.修改配置&#xff08…