ccs加载out文件_类加载流程、类加载机制及自定义类加载器详解

原文:juejin.im/post/5cffa528e51d4556da53d091

一、引言

当程序使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、链接、初始化三个步骤对该类进行类加载。

二、类的加载、链接、初始化

1、加载

类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象。类的加载过程是由类加载器来完成,类加载器由JVM提供。我们开发人员也可以通过继承ClassLoader来实现自己的类加载器。

1.1、加载的class来源
  • 从本地文件系统内加载class文件

  • 从JAR包加载class文件

  • 通过网络加载class文件

  • 把一个java源文件动态编译,并执行加载。

2、类的链接

通过类的加载,内存中已经创建了一个Class对象。链接负责将二进制数据合并到 JRE中。链接需要通过验证、准备、解析三个阶段。

2.1、验证

验证阶段用于检查被加载的类是否有正确的内部结构,并和其他类协调一致。即是否满足java虚拟机的约束。

2.2、准备

类准备阶段负责为类的类变量分配内存,并设置默认初始值。

2.3、解析

我们知道,引用其实对应于内存地址。思考这样一个问题,在编写代码时,使用引用,方法时,类知道这些引用方法的内存地址吗?显然是不知道的,因为类还未被加载到虚拟机中,你无法获得这些地址。

举例来说,对于一个方法的调用,编译器会生成一个包含目标方法所在的类、目标方法名、接收参数类型以及返回值类型的符号引用,来指代要调用的方法。

解析阶段的目的,就是将这些符号引用解析为实际引用。如果符号引用指向一个未被加载的类,或者未被加载类的字段或方法,那么解析将触发这个类的加载(但未必会触发解析与初始化)。

3、类的初始化

类的初始化阶段,虚拟机主要对类变量进行初始化。虚拟机调用< clinit>方法,进行类变量的初始化。

java类中对类变量进行初始化的两种方式:

  1. 在定义时初始化

  2. 在静态初始化块内初始化

3.1、< clinit>方法相关

虚拟机会收集类及父类中的类变量及类方法组合为< clinit>方法,根据定义的顺序进行初始化。虚拟机会保证子类的< clinit>执行之前,父类的< clinit>方法先执行完毕。

因此,虚拟机中第一个被执行完毕的< clinit>方法肯定是java.lang.Object方法。

public class Test {
    static int A = 10;
    static {
        A = 20;
    }
}

class Test1 extends Test {
    private static int B = A;
    public static void main(String[] args) {
        System.out.println(Test1.B);
    }
}
//输出结果
//20

从输出中看出,父类的静态初始化块在子类静态变量初始化之前初始化完毕,所以输出结果是20,不是10。

如果类或者父类中都没有静态变量及方法,虚拟机不会为其生成< clinit>方法。

接口与类不同的是,执行接口的<clinit>方法不需要先执行父接口的<clinit>方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>方法。

public interface InterfaceInitTest {
    long A = CurrentTime.getTime();

}

interface InterfaceInitTest1 extends InterfaceInitTest {
    int B = 100;
}

class InterfaceInitTestImpl implements InterfaceInitTest1 {
    public static void main(String[] args) {
        System.out.println(InterfaceInitTestImpl.B);
        System.out.println("---------------------------");
        System.out.println("当前时间:"+InterfaceInitTestImpl.A);
    }
}

class CurrentTime {
    static long getTime() {
        System.out.println("加载了InterfaceInitTest接口");
        return System.currentTimeMillis();
    }
}
//输出结果
//100
//---------------------------
//加载了InterfaceInitTest接口
//当前时间:1560158880660

从输出验证了:对于接口,只有真正使用父接口的类变量才会真正的加载父接口。这跟普通类加载不一样。

虚拟机会保证一个类的< clinit>方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的< clinit>方法,其他线程都需要阻塞等待,直到活动线程执行< clinit>方法完毕。

public class MultiThreadInitTest {
    static int A = 10;
    static {
           System.out.println(Thread.currentThread()+"init MultiThreadInitTest");
        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread() + "start");
            System.out.println(MultiThreadInitTest.A);
            System.out.println(Thread.currentThread() + "run over");
        };
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
    }
}
//输出结果
//Thread[main,5,main]init MultiThreadInitTest
//Thread[Thread-0,5,main]start
//10
//Thread[Thread-0,5,main]run over
//Thread[Thread-1,5,main]start
//10
//Thread[Thread-1,5,main]run over

从输出中看出验证了:只有第一个线程对MultiThreadInitTest进行了一次初始化,第二个线程一直阻塞等待等第一个线程初始化完毕。

3.2、类初始化时机
  1. 当虚拟机启动时,初始化用户指定的主类;

  2. 当遇到用以新建目标类实例的new指令时,初始化new指令的目标类;

  3. 当遇到调用静态方法或者使用静态变量,初始化静态变量或方法所在的类;

  4. 子类初始化过程会触发父类初始化;

  5. 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口初始化;

  6. 使用反射API对某个类进行反射调用时,初始化这个类;

  7. Class.forName()会触发类的初始化

3.3、final定义的初始化

注意:对于一个使用final定义的常量,如果在编译时就已经确定了值,在引用时不会触发初始化,因为在编译的时候就已经确定下来,就是“宏变量”。如果在编译时无法确定,在初次使用才会导致初始化。

public class StaticInnerSingleton {
    /**
     * 使用静态内部类实现单例:
     * 1:线程安全
     * 2:懒加载
     * 3:非反序列化安全,即反序列化得到的对象与序列化时的单例对象不是同一个,违反单例原则
     */
    private static class LazyHolder {
        private static final StaticInnerSingleton INNER_SINGLETON = new StaticInnerSingleton();
    }

    private StaticInnerSingleton() {
    }

    public static StaticInnerSingleton getInstance() {
        return LazyHolder.INNER_SINGLETON;
    }
}

看这个例子,单例模式静态内部类实现方式。我们可以看到单例实例使用final定义,但在编译时无法确定下来,所以在第一次使用StaticInnerSingleton.getInstance()方法时,才会触发静态内部类的加载,也就是延迟加载。

这里想指出,如果final定义的变量在编译时无法确定,则在使用时还是会进行类的初始化。

3.4、ClassLoader只会对类进行加载,不会进行初始化
public class Tester {
    static {
        System.out.println("Tester类的静态初始化块");
    }
}

class ClassLoaderTest {
    public static void main(String[] args) throws ClassNotFoundException {
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        //下面语句仅仅是加载Tester类
        classLoader.loadClass("loader.Tester");
        System.out.println("系统加载Tester类");
        //下面语句才会初始化Tester类
        Class.forName("loader.Tester");
    }
}
//输出结果
//系统加载Tester类
//Tester类的静态初始化块

从输出证明:ClassLoader只会对类进行加载,不会进行初始化;使用Class.forName()会强制导致类的初始化。

三、类加载器

类加载器负责将.class文件(不管是jar,还是本地磁盘,还是网络获取等等)加载到内存中,并为之生成对应的java.lang.Class对象。一个类被加载到JVM中,就不会第二次加载了。

那怎么判断是同一个类呢?

每个类在JVM中使用全限定类名(包名+类名)与类加载器联合为唯一的ID,所以如果同一个类使用不同的类加载器,可以被加载到虚拟机,但彼此不兼容。

1、JVM类加载器分类

1.1、Bootstrap ClassLoader

Bootstrap ClassLoader为根类加载器,负责加载java的核心类库。根加载器不是ClassLoader的子类,是有C++实现的。

public class BootstrapTest {
    public static void main(String[] args) {
        //获取根类加载器所加载的全部URL数组
        URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
        Arrays.stream(urLs).forEach(System.out::println);
    }
}
//输出结果
//file:/C:/SorftwareInstall/java/jdk/jre/lib/resources.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/rt.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/sunrsasign.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jsse.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jce.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/charsets.jar
//file:/C:/SorftwareInstall/java/jdk/jre/lib/jfr.jar
//file:/C:/SorftwareInstall/java/jdk/jre/classes

根类加载器负责加载%JAVA_HOME%/jre/lib下的jar包(以及由虚拟机参数 -Xbootclasspath 指定的类)。

ad1399cff83ed8610f5605ab6620fccb.png

我们将rt.jar解压,可以看到我们经常使用的类库就在这个jar包中。

1.2 、Extension ClassLoader

Extension ClassLoader为扩展类加载器,负责加载%JAVA_HOME%/jre/ext或者java.ext.dirs系统熟悉指定的目录的jar包。大家可以将自己写的工具包放到这个目录下,可以方便自己使用。

1.3、 System ClassLoader

System ClassLoader为系统(应用)类加载器,负责加载加载来自java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader.getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器默认都以系统类加载器作为父加载器。

四、类加载机制

1.1、JVM主要的类加载机制。

  1. 全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖和引用的其他Class也由该类加载器负责载入,除非显示使用另一个类加载器来载入。

  2. 父类委托(双亲委派):先让父加载器试图加载该Class,只有在父加载器无法加载时该类加载器才会尝试从自己的类路径中加载该类。

  3. 缓存机制:缓存机制会将已经加载的class缓存起来,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存中不存在该Class时,系统才会读取该类的二进制数据,并将其转换为Class对象,存入缓存中。这就是为什么更改了class后,需要重启JVM才生效的原因。

注意:类加载器之间的父子关系并不是类继承上的父子关系,而是实例之间的父子关系。

2223302e138b791ff8e95ccac6c8e413.png
public class ClassloaderPropTest {
    public static void main(String[] args) throws IOException {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系统类加载器:" + systemClassLoader);
        /*
        获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定,如果操作系统没有指定
        CLASSPATH环境变量,则默认以当前路径作为系统类加载器的加载路径
         */
        Enumeration eml = systemClassLoader.getResources("");while (eml.hasMoreElements()) {
            System.out.println(eml.nextElement());
        }//获取系统类加载器的父类加载器,得到扩展类加载器
        ClassLoader extensionLoader = systemClassLoader.getParent();
        System.out.println("系统类的父加载器是扩展类加载器:" + extensionLoader);
        System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs"));
        System.out.println("扩展类加载器的parant:" + extensionLoader.getParent());
    }
}//输出结果//系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2//file:/C:/ProjectTest/FengKuang/out/production/FengKuang///系统类的父加载器是扩展类加载器:sun.misc.Launcher$ExtClassLoader@1540e19d//扩展类加载器的加载路径:C:\SorftwareInstall\java\jdk\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext//扩展类加载器的parant:null

从输出中验证了:系统类加载器的父加载器是扩展类加载器。但输出中扩展类加载器的父加载器是null,这是因为父加载器不是java实现的,是C++实现的,所以获取不到。但扩展类加载器的父加载器是根加载器。

1.2、类加载流程图

681319fa36355c4c1788c49a033c49f8.png

图中红色部分,可以是我们自定义实现的类加载器来进行加载。

五、创建并使用自定义类加载器

1、自定义类加载分析

除了根类加载器,所有类加载器都是ClassLoader的子类。所以我们可以通过继承ClassLoader来实现自己的类加载器。

ClassLoader类有两个关键的方法:

  1. protected Class loadClass(String name, boolean resolve):name为类名,resove如果为true,在加载时解析该类。

  2. protected Class findClass(String name) :根据指定类名来查找类。

所以,如果要实现自定义类,可以重写这两个方法来实现。但推荐重写findClass方法,而不是重写loadClass方法,因为loadClass方法内部会调用findClass方法。

我们来看一下loadClass的源码

protected Class> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            //第一步,先从缓存里查看是否已经加载
            Class> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                //第二步,判断父加载器是否为null
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                   //第三步,如果前面都没有找到,就会调用findClass方法
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                   sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

loadClass加载方法流程:

  1. 判断此类是否已经加载;

  2. 如果父加载器不为null,则使用父加载器进行加载;反之,使用根加载器进行加载;

  3. 如果前面都没加载成功,则使用findClass方法进行加载。

所以,为了不影响类的加载过程,我们重写findClass方法即可简单方便的实现自定义类加载。

2、实现自定义类加载器

基于以上分析,我们简单重写findClass方法进行自定义类加载。

public class Hello {
   public void test(String str){
       System.out.println(str);
   }
}

public class MyClassloader extends ClassLoader {

    /**
     * 读取文件内容
     *
     * @param fileName 文件名
     * @return
     */
    private byte[] getBytes(String fileName) throws IOException {
        File file = new File(fileName);
        long len = file.length();
        byte[] raw = new byte[(int) len];
        try (FileInputStream fin = new FileInputStream(file)) {
            //一次性读取Class文件的全部二进制数据
            int read = fin.read(raw);
            if (read != len) {
                throw new IOException("无法读取全部文件");
            }
            return raw;
        }
    }

    @Override
    protected Class> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        //将包路径的(.)替换为斜线(/)
        String fileStub = name.replace(".", "/");
        String classFileName = fileStub + ".class";
        File classFile = new File(classFileName);

        //如果Class文件存在,系统负责将该文件转换为Class对象
        if (classFile.exists()) {
            try {
                //将Class文件的二进制数据读入数组
                byte[] raw = getBytes(classFileName);
                //调用ClassLoader的defineClass方法将二进制数据转换为Class对象
                clazz = defineClass(name, raw, 0, raw.length);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //如果clazz为null,表明加载失败,抛出异常
        if (null == clazz) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }

    public static void main(String[] args) throws Exception {
        String classPath = "loader.Hello";
        MyClassloader myClassloader = new MyClassloader();
        Class> aClass = myClassloader.loadClass(classPath);
        Method main = aClass.getMethod("test", String.class);
        System.out.println(main);
        main.invoke(aClass.newInstance(), "Hello World");
    }
}
//输出结果
//Hello World

ClassLoader还有一个重要的方法defineClass(String name, byte[] b, int off, int len)。此方法的作用是将class的二进制数组转换为Calss对象。

此例子很简单,我写了一个Hello测试类,并且编译过后放在了当前路径下(大家可以在findClass中加入判断,如果没有此文件,可以尝试查找.java文件,并进行编译得到.class文件;或者判断.java文件的最后更新时间大于.class文件最后更新时间,再进行重新编译等逻辑)。

六、总结

本篇从类加载的三大阶段:加载、链接、初始化开始细说每个阶段的过程;详细讲解了JVM常用的类加载器的区别与联系,以及类加载机制流程,最后通过自定义的类加载器例子结束本篇。

【面试题专栏】

2019年面试官最喜欢问的28道ZooKeeper面试题

2019年全网最热门的123个Java并发面试题总结

全网最热门的119个Spring问题,哪些你还不会?

2020面试还搞不懂MyBatis?看看这27道面试题!(含答案和思维导图)

2020年去一线大厂面试先过SSM框架这一关!

Spring Cloud+Spring Boot高频面试题解析

2019年常见的Linux面试题及答案解析,哪些你还不会?

2019年常见Elasticsearch面试题答案解析

18道kafka高频面试题哪些你还不会?(含答案和思维导图)

2019年12道RabbitMQ高频面试题你都会了吗?(含答案解析)

2019年Dubbo你掌握的如何?快看看这30道高频面试题!

2019年228道Java中高级面试题(8),哪些你还不会?

3ef2ce88c0700239d7bd8291ff0f2c41.png

7292cecf10a08100e3e4a4eb5cfd96dd.png 你在看吗?

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

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

相关文章

unity中单位是米还是厘米_【一步数学】小学数学单位换算公式大全及专项训练...

重量单位换算1吨1000千克 1吨1000 000克吨&#xff1a;吨是重量单位&#xff0c;公制一吨等于1000公斤&#xff1a;计算船只容积的单位&#xff0c;一吨等于2.83立方米&#xff08;合100立方英尺&#xff09;。1千克1000克 500克1斤千克&#xff1a;克&#xff0c;(符号kg或㎏)…

二分法查找是基于有序_201,查找顺序查找

查找算法中顺序查找算是最简单的了&#xff0c;无论是有序的还是无序的都可以&#xff0c;也不需要排序&#xff0c;只需要一个个对比即可&#xff0c;但其实效率很低。我们来看下代码1public static int search1(int[] a, int key) {2 for (int i 0, length a.length; i …

河南大学计算机组成原理,河南大学计算机组成原理考点

河南大学计算机组成原理考点 (34页)本资源提供全文预览&#xff0c;点击全文预览即可全文预览,如果喜欢文档就下载吧&#xff0c;查找使用更方便哦&#xff01;11.90 积分第&#xff11;章 计算机系统概论1、计算机硬件的五大部件&#xff1f;三大部分&#xff1f;运算器 存储器…

zipsys驱动签名工具_全球首发 300系列主板USB WIN7 64位驱动 SMXDIY

本版为改版驱动&#xff0c;仅供SMXDIY会员学习测试&#xff0c;禁止传播。众所周知&#xff0c;H310/B360/H370/Z390是没USB的WIN7驱动的。网上也找不到相关的资料&#xff0c;大家普遍默认没驱动&#xff0c;但这已经成为历史。我们这发布的是真的驱动&#xff0c;可以整合到…

excel筛选排序从小到大_excel表格怎么按字数的多少来排列!

excel表格怎么按字数的多少来排列为了更好的做好我们的语料编定及翻译工作&#xff0c;尤其是个别语料数量较多&#xff0c;最多可达2000条。我们可以将字数少的&#xff0c;意图明确的&#xff0c;不会有异议的&#xff0c;我们可以根据每句话的字数&#xff0c;用excel来进行…

计算机系统的工作方式,某计算机系统输入/输出采用双缓冲工作方式,其工作过程如下图所示,假设磁盘块与缓冲 - 信管网...

第2题&#xff1a;实时操作系统(RTOS)内核与应用程序之间的接口称为( )。A&#xff0e;I&#xff0f;O接口B&#xff0e;PCIC&#xff0e;APID&#xff0e;GUI答案解析与讨论&#xff1a;www.cnitpm.com/st/2867029381.html第3题&#xff1a;嵌入式处理器是嵌入式系统的核心部…

怎么从转移特性曲线上看dibl_「科普向」这篇让你快速搞懂IGBT的静态特性

IGBT的静态特性其实并非难以理解的东西,即便是对于外行人而言。刚接触那会儿&#xff0c;看到转移特性、输出特性之类的就想溜之大吉&#xff0c;加之网上查询的资料一概笼统简单&#xff0c;只描述特性曲线所表示的关系结果&#xff0c;却并不解释曲线为何这里弯曲、那里平直&…

对CORS OPTIONS预检请求的一些思考

前后端分离模大势所趋&#xff0c;跨域问题更是老生常谈。《程序员应对浏览器同源策略的姿势》一文提到三种跨域请求方案&#xff0c;重点讲述了w3c和浏览器厂商推出的CORS规范。同源策略 所谓同源是指域名、协议、端口相同。不同源的浏览器脚本(javascript、ActionScript、ca…

强烈推荐:SiteServer CMS开源免费的企业级CMS系统!

说到CMS&#xff0c;大家都知道织梦、帝国CMS、HPCMS、动易等知名老牌的&#xff01;这些东西也可以拿来就用&#xff0c;上次看到一个个人开源的cms&#xff1a;MCMS。基于SpringBoot 2架构&#xff0c;前端基于vue、element ui。每月28定期更新版本&#xff0c;为开发者提供…

计算机函数公式中怎么合并合并,Excel用函数和公式瞬间实现把表格全部合并到一个表中去...

将多张工作表汇总到一张工作表&#xff0c;这是什么意思呢&#xff1f;可以理解为把表格全部合并到一个表中去&#xff0c;之前使用VBA代码实现&#xff0c;本文将教会大家一种快速的方法&#xff0c;用函数和公式瞬间实现多表合并。例如&#xff1a;有N多个以月份命名的excel工…

. NET5一出,. NET岗面试普遍喊难,真相是…

.NET高级开发/架构师笔试题TOP101、如何设计一个高并发系统&#xff1f;2、如何实现一个.Net5 IOC框架&#xff1f;3、100W并发4G数据&#xff0c;10W并发400G数据&#xff0c;如何设计Redis存储方式&#xff1f;4、如何实现负载均衡Hash一致性算法&#xff1f;5、DotNetty能够…

数据库年月日时分秒_数据库基本使用系列(二)

书接上回&#xff0c;上回说到数据库的发展历史&#xff0c;存储引擎以及在库层面的一些操作&#xff0c;这次来讲一下数据库中的一些基本的一些数据格式以及对表的一些操作。数据类型数据库提供的数据类型&#xff0c;包括整数类型、浮点数类型、定点数类型、位类型、日期和时…

netcore读取json文件_【NET Core】.NET Core中读取json配置文件

在.NET Framework框架下应用配置内容一般都是写在Web.config或者App.config文件中&#xff0c;读取这两个配置文件只需要引用System.Configuration程序集&#xff0c;分别用System.Configuration.ConfigurationManager.AppSettings["SystemName"];//读取appSettings配…

前端数据层落地实践

源宝导读&#xff1a;天际移动平台经过重构改版&#xff0c;近期正式发布了1.0版本&#xff0c;我们在低代码开发方面做了进一步增强。本文主要围绕前端Model、前端业务逻辑(领域模型)、数据层与视图层解耦(包装器模式)3个方面&#xff0c;给大家分享一下统一数据层方案的设计思…

计算机内存条只认了一个,怎么解决Win10插入2个4G内存条却只显示4G?

为了提高 Win10系统 的运行速度&#xff0c;有用户在自己的电脑中&#xff0c;插入了2根4G内存&#xff0c;可是发现电脑只能显示4G而不是8G&#xff0c;奇怪的是使用卤蛋师却可以检测到8G内存&#xff0c;这是怎么回事呢&#xff1f;我们该如何解决呢&#xff1f;下面&#xf…

scanf 返回值_scanf函数

一、函数scanf()是C语言中的一个输入函数。与printf函数一样&#xff0c;都被声明在头文件stdio.h里&#xff0c;因此在使用scanf函数时要加上#include <stdio.h>。它是格式输入函数&#xff0c;即按用户指定的格式从键盘上把数据输入到指定的变量之中。函数的原型为&…

【Git】Git-常用命令备忘录(三)

git作为一个vcs&#xff08;version control system&#xff09;&#xff0c;是越用越香&#xff0c;那么还有哪些比较香的地方呢&#xff01;&#xff1f;1.远程仓库中拉取指定分支一定遇到这种情况&#xff0c;github看到一个心仪的开源仓库&#xff0c;但是分支太多&#xf…

mysql表类型_MySQL表类型的选择

P131)创建表时的默认引擎是InnoDB&#xff0c;如果要修改默认的存储引擎&#xff0c;可以在参数文件中设置default-table-type。查看当前的默认存储引擎&#xff0c;可以使用一下命令&#xff1a;创建新表的时候可以通过增加ENGINE关键字设置新建表的存储引擎。也可以通过ALTER…

ASP.NET Core Controller与IOC的羁绊

前言看到标题可能大家会有所疑问Controller和IOC能有啥羁绊&#xff0c;但是我还是拒绝当一个标题党的。相信有很大一部分人已经知道了这么一个结论&#xff0c;默认情况下ASP.NET Core的Controller并不会托管到IOC容器中&#xff0c;注意关键字我说的是"默认"&#…

引入Jaeger——扩展

Jaeger是收集全链路跟踪的信息&#xff0c;在Jaeger收集的信息中&#xff0c;有请求的url信息&#xff0c;有每个请求的时间间隔&#xff0c;借助这些信息可以进行报警&#xff0c;比如一次较长的请求&#xff0c;或者是某些请求的次数和先后等。不管报警的业务规则是什么&…