java的深入探究JVM之类加载与双亲委派机制

前言

前面学习了虚拟机的内存结构、对象的分配和创建,但对象所对应的类是怎么加载到虚拟机中来的呢?加载过程中需要做些什么?什么是双亲委派机制以及为什么要打破双亲委派机制?

类的生命周期


类的生命周期包含了如上的7个阶段,其中验证准备解析统称为连接 ,类的加载主要是前五个阶段,每个阶段基本上保持如上顺序开始(仅仅是开始,实际上执行是交叉混合的),只有解析阶段不一定,在初始化后也有可能才开始执行解析,这是为了支持动态语言。

加载

加载就是将字节码的二进制流转化为方法区的运行时数据结构,并生成类所对象的Class对象,字节码二进制流可以是我们编译后的class文件,也可以从网络中获取,或者运行时动态生成(动态代理)等等。
那什么时候会触发类加载呢?这个在虚拟机规范中没有明确定义,只是规定了何时需要执行初始化(稍后详细分析)。

验证

这个阶段很好理解,就是进行必要的校验,确保加载到内存中的字节码是符合要求的,主要包含以下四个校验步骤(了解即可):

  • 文件格式校验:这个阶段要校验的东西非常多,主要的有下面这些(实际上远远不止)
    • 是否以魔数0xCAFEBABE开头。
    • 主、次版本号是否在当前Java虚拟机接受范围之内。
    • 常量池的常量中是否有不被支持的常量类型(检查常量tag标志)。
    • 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量。
    • CONSTANT_Utf8_info型的常量中是否有不符合UTF-8编码的数据。
    • Class文件中各个部分及文件本身是否有被删除的或附加的其他信息。
    • 。。。。。。
  • 元数据校验:对字节码描述信息进行语义分析。
    • 这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)。
    • 这个类的父类是否继承了不允许被继承的类(被final修饰的类)。
    • 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
    • 类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等)。
    • 。。。。。。
  • 字节码校验:确保程序没有语法和逻辑错误,这是整个验证阶段最复杂的一个步骤。
    • 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似于“在操作栈放置了一个 int 类型的数据,使用时却按 long 类型来加载入本地变量表中”这样的情况。
    • 保证任何跳转指令都不会跳转到方法体以外的字节码指令上。
    • 保证方法体中的类型转换总是有效的,例如可以把-个子类对象赋值给父类数据类型,这是安全的,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相干的一个数据类型,则是危险和不合法的。
    • 。。。。。。
  • 符号引用验证:这个阶段发生在符号引用转为直接引用的时候,即实际上是在解析阶段中进行的。
    • 符号引用中通过字符串描述的全限定名是否能找到对应的类。
    • 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段。
    • 符号引用中的类、字段、方法的可访问性( private、 protected. public、 )。
    • 是否可被当前类访问。
    • 。。。。。。

准备

该阶段是为类变量(static)分配内存并设置零值,即类只要经过准备阶段其中的静态变量就是可使用的了,但此时类变量的值还不是我们想要的值,需要经过初始化阶段才会将我们希望的值赋值给对应的静态变量。

解析

解析就是将常量池中的符号引用替换为直接引用的过程。符号引用就是一个代号,比如我们的名字,而这里可以理解为就是类的完全限定名直接引用则是对应的具体的人、物,这里就是指目标的内存地址。为什么需要符号引用呢?因为类在加载到内存之前还没有分配内存地址,因此必然需要一个东西指代它。这个阶段包含了类或接口的解析字段解析类方法解析接口方法解析,在解析的过程中可能会抛出以下异常:

  • java.lang.NoSuchFieldError:找不到字段
  • java.lang.IllegalAccessError:不具有访问权限
  • java.lang.NoSuchMethodError:找不到方法

初始化

这是类加载过程中的最后一个步骤,主要是收集类的静态变量的赋值动作static块中的语句合成<cinit>方法,通过该方法根据我们的意愿为静态变量赋值以及执行static块该方法会被加锁,确保多线程情况下只有一个线程能初始化成功,利用该特性可以实现单例模式。虚拟机规定了有且只有遇到以下情况时必须先确保对应类的初始化完成(加载、准备必然在此之前):

  • 遇到new、getstatic、putstatic或invokestatic这四条字节码指令时。能够生成这四条指令的典型Java代码场景有:
    • 使用new关键字实例化对象的时候。
    • 读取或设置一个类型的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)的时候。
    • 调用一个类型的静态方法的时候。
  • 反射调用类时。
  • 当初始化类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。
  • 当使用JDK 7新加入的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
  • 当一个接口中定义了JDK 8新加入的默认方法(被default关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

下面分析几个案例代码,读者们可以先思考后再运行代码看看和自己想的是否一样。

案例一

先定义如下两个类:

public class SuperClazz {static  {System.out.println("SuperClass init!");}public static int value=123;public static final String HELLOWORLD="hello world";public static final int WHAT = value;}public class SubClaszz extends SuperClazz {static{System.out.println("SubClass init!");}}

然后进行下面的调用:

public class Initialization {public static void main(String[]args){Initialization initialization = new Initialization();initialization.M1();}public void M1(){System.out.println(SubClaszz.value);}}

第一个案例是通过子类去引用父类中的静态变量,两个类都会加载和初始化么?打印结果看看:

SuperClass init!123

可以看到只有父类初始化了,那么父类必然是加载了的,问题就在于子类有没有被加载呢?可以加上参数:-XX:+TraceClassLoading再执行(该参数的作用就是打印被加载了的类),可以看到子类是被加载了的。所以通过子类引用父类静态变量,父子类都会被加载,但只有父类会进行初始化
为什么呢?反编译后可以看到生成了如下指令:

0: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;

3: getstatic     #6                  // Field ex7/init/SubClaszz.value:I

6: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V

9: return

关键就是getstatic指令就会触发类的初始化,但是为什么子类不会初始化呢?因为这个变量是来自于父类的,为了提高效率,所以虚拟机进行了优化,这种情况只需要初始化父类就行了。

案例二

调用下面的方法:

public void M2(){SubClaszz[]sca = new SubClaszz[10];}

执行后可以发现,使用数组,不会触发初始化,但父子类都会被加载

案例三

public void M3(){System.out.println(SuperClazz.HELLOWORLD);}

引用常量不会触发类的加载和初始化,因为常量在编译后就已经存在当前class的常量池。

常量本质含义

静态加final修饰的常量在编译的时能确定其值时,就被放置该方法所属类的常量池中,即使将常量所在类的字节码文件删除,也不会影响常量的使用,所以常量所在的类也不会被初始化。但是当常量值不确定时如用UUID生成时,只能在运行时才确定时,常量所在的类就会被初始化

案例四

public void M4(){System.out.println(SubClaszz.WHAT);}

通过常量去引用其它的静态变量会发生什么呢?这个和案例一结果是一样的。

类加载器

命名空间

表示的是一个范围,指加载器自己和所有父加载器加载的类,加载器指的是具体的对象

子命名空间中类对象可以看到父命名空间的类对象,反之则不行

父子加载器其实是包含的关系

每个类加载器都有自己的命名空间的,一个命名空间中同一个类只能有一个,就是说加载前判断类之前有没有加载过

同一个类被不同命名空间的加载器加载时,同时加载到内存的类是属于不同的类型的

自定义的类加载器,当没有包含父加载器时,默认的父加载器是系统加载器

怎样确定一个类

包名加类名的形式

类的卸载

只有字节码对象没有被引用时类在方法区中就会被卸载;而java虚拟机自带的类加载器,在加载类时内部有集合里存放被加载类的引用,所以虚拟机运行期间是不会被卸载的。而自定义的类加载器加载的类是有可能被卸载的

特性

在类里有对其他类的使用时,其他类的加载器是只能用这个类加载器及其父类进行加载

其他加载器都是由根加载器进行加载的,而根加载器是内嵌在java虚拟机的,随虚拟机启动进行初始化的

应用类加载器可以通过设置系统属性Java.system.class.loader来设置其他加载器作为应用类加载器

类加载器和双亲委派模型

在我们平时开发中,确定一个类需要通过完全限定名,而不能简单的通过名字,因为在不同的路径下我们是可以定义同名的类的。那么在虚拟机中又是怎么区分类的呢?在虚拟机中需要加载器+完全限定名一起来指定一个类的唯一性即相同限定名的类若由两个不同的类加载器加载,那虚拟机就不会把它们当做一个类。从这里我们可以看出类加载器一定是有多个的,那么不同的类加载器是怎么组织的?它们又分别需要加载哪些类呢?
 


从虚拟角度看,只有两种类型的类加载器:启动类加载器(BootstrapClassLoader)非启动类加载器。前者是C++实现,属于虚拟机的一部分,后者则是由Java实现的,独立于虚拟机的外部,并且全部继承自抽象类java.lang.ClassLoader。
但从Java本身来看,一直保持着三层类加载器双亲委派的结构,当然除了Java本身提供的三层类加载器,我们还可以自定义实现类加载器。如上图,上面三个就是原生的类加载器,每一个都是下一个类加载器的父加载器,注意这里都是采用组合而非继承当开始加载类时,首先交给父加载器加载,父加载器加载了子加载器就不用再加载了,而若是父加载器加载不了,就会交给子加载器加载,这就是双亲委派机制。这就好比工作中遇到了无法处理的事,你会去请示直接领导,直接领导处理不了,再找上层领导,然后上层领导觉得这是个小事,不用他亲自动手,就让你的直接领导去做,接着他又交给你去做等等。下面来看看每个类加载器的具体作用:

  • BootstrapClassLoader:启动类加载器,顾名思义,这个类加载器主要负责加载JDK lib包,以及-Xbootclasspath参数指定的目录,并且虚拟机对文件名进行了限定,也就是说即使我们自己写个jar放入到上述目录,也不会被加载。由于该类加载器是C++使用,所以我们的Java程序中无法直接引用,调用java.lang.ClassLoader.getClassLoader()方法时默认返回的是null。
  • ExtClassLoader:扩展类加载器,主要负责加载JDK lib/ext包,以及被系统变量java.ext.dirs指向的所有类库,这个类库可以存放我们自己写的通用jar。
  • AppClassLoader:应用程序类加载器,负责加载用户classpath上的所有类。它是java.lang.ClassLoader.getSystemClassLoader()的返回值,也是我们程序的默认类加载器(如果我们没有自定义类加载器的话)。

通过这三个类加载以及双亲委派机制,一个显而易见的好处就是,不同的类随它的类加载器天然具有了加载优先级,像Object、String等等这些核心类库自然就会在我们的应用程序类之前被加载,保证了系统的安全性,用户自定义类加载器不可能去加载由父加载器去加载的基础可靠类,避免了基础代码的替换和被覆盖,Spring的父子容器也是这样的一个设计。通过下面这段代码可以看到每个类所对应的类加载器:

public class ClassLoader {public static void main(String[] args) {System.out.println(String.class.getClassLoader()); //启动类加载器System.out.println(sun.net.spi.nameservice.dns.DNSNameService.class.getClassLoader());//拓展类加载器System.out.println(ClassLoader.class.getClassLoader());//应用程序类加载器}}

输出:

null

sun.misc.Launcher$ExtClassLoader@4b67cf4d

sun.misc.Launcher$AppClassLoader@14dad5dc

破坏双亲委派模型

刚刚我举了工作中的一个例子来说明双亲委派机制,但现实中我们不需要事事都去请示领导,同样类加载器也不是完全遵循双亲委派机制,在必要的时候是可以打破这个规则的。下面列举四个破坏的情况,在此之前我们需要先了解下双亲 委派的代码实现原理,在java.lang.ClassLoader类中有一个loadClass以及findClass方法:

   

 protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException{synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loadedClass<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {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) {// If still not found, then invoke findClass in order// to find the class.long t1 = System.nanoTime();c = findClass(name);// this is the defining class loader; record the statssun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}}if (resolve) {resolveClass(c);}return c;}}protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);}

从上面可以看到首先是调用parent去加载类,没有加载到才调用自身的findClass方法去加载。也就是说用户在实现自定义类加载器的时候需要覆盖的是fiindClass而不是loadClass,这样才能满足双亲委派模型
下面具体来看看破坏双亲委派的几个场景。

第一次

第一次破坏是在双亲委派模型出现之前, 因为该模型是在JDK1.2之后才引入的,那么在此之前,抽象类java.lang.ClassLoader就已经存在了,用户自定义的类加载器都会去覆盖该类中的loadClass方法,所以双亲委派模型出现后,就无法避免用户覆盖该方法,因此新增了findClass引导用户去覆盖该方法实现自己的类加载逻辑。

SPI

第二次破坏是由于这个模型本身缺陷导致的,因为该模型保证了类的加载优先级,但是有些接口是Java定义在核心类库中,但具体的服务实现是由用户提供的,这时候就不得不破坏该模型才能实现,典型的就是Java中的SPI机制(SPI服务提供接口,一般是java制定标准接口,再由外部厂商进行实现,这种情况下接口是由根加载器加载,实现类的jar包是放在classpath中由应用加载器进行加载,导致引用看不到对象的情况,如jdbc的应用)。


DBC的驱动加载就是SPI实现的,所以直接看到java.sql.DriverManager类(核心类),该类中有一个静态初始化块:

    

static {loadInitialDrivers();println("JDBC DriverManager initialized");}private static void loadInitialDrivers() {String drivers;try {drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {public String run() {return System.getProperty("jdbc.drivers");}});} catch (Exception ex) {drivers = null;}AccessController.doPrivileged(new PrivilegedAction<Void>() {public Void run() {ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);Iterator<Driver> driversIterator = loadedDrivers.iterator();try{while(driversIterator.hasNext()) {driversIterator.next();}} catch(Throwable t) {// Do nothing}return null;}});println("DriverManager.initialize: jdbc.drivers = " + drivers);if (drivers == null || drivers.equals("")) {return;}String[] driversList = drivers.split(":");println("number of Drivers:" + driversList.length);for (String aDriver : driversList) {try {println("DriverManager.Initialize: loading " + aDriver);Class.forName(aDriver, true,ClassLoader.getSystemClassLoader());} catch (Exception ex) {println("DriverManager.Initialize: load failed: " + ex);}}}

主要看ServiceLoader.load方法,这个就是通过SPI去加载我们引入java.sql.Driver实现类(比如引入mysql的驱动包就是com.mysql.cj.jdbc.Driver):

    

public static <S> ServiceLoader<S> load(Class<S> service) {ClassLoader cl = Thread.currentThread().getContextClassLoader();return ServiceLoader.load(service, cl);}

这个方法主要是从当前线程中获取类加载器,然后通过这个类加载器去加载驱动实现类(这个叫线程上下文类加载器,我们也可以使用这个技巧去打破双亲委派),那这里会获取到哪一个类加载器呢?具体的设置是在sun.misc.Launcher类的构造器中:

   

 public Launcher() {Launcher.ExtClassLoader var1;try {var1 = Launcher.ExtClassLoader.getExtClassLoader();} catch (IOException var10) {throw new InternalError("Could not create extension class loader", var10);}try {this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);} catch (IOException var9) {throw new InternalError("Could not create application class loader", var9);}Thread.currentThread().setContextClassLoader(this.loader);String var2 = System.getProperty("java.security.manager");if (var2 != null) {SecurityManager var3 = null;if (!"".equals(var2) && !"default".equals(var2)) {try {var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();} catch (IllegalAccessException var5) {} catch (InstantiationException var6) {} catch (ClassNotFoundException var7) {} catch (ClassCastException var8) {}} else {var3 = new SecurityManager();}if (var3 == null) {throw new InternalError("Could not create SecurityManager: " + var2);}System.setSecurityManager(var3);}}

可以看到设置的就是AppClassLoader。你可能会有点疑惑,这个类加载器加载类的时候不也是先调用父类加载器加载么,怎么就打破双亲委派了呢?其实打破双亲委派指的就是类的层次结构,延伸意思就是类的加载优先级,这里本应该是在加载核心类库的时候却提前将我们应用程序中的类库给加载到虚拟机中来了。

当前类加载器(Current classloader)
  • 每个类都会使用自己的类加载器(即加载自身的类加载器)来去加载其他类(指的是所依赖的类)。
  • 如果classX引用了classY,那么ClassX的类加载器就会去加载classY(前提是classY尚未被加载)。

在双亲委托模型下,类加载是由下至上的,即下层的类加载器会委托上层进行加载。但是对于SPI来说,有些接口是Java核心库所提供的,而Java核心库是由启动类加载器来加载的,而这些接口的实现却来自于不同的jar包(厂商提供), Java的启动类加载器是不会加载其他来源的jar包,这样传统的双亲委托模型就无法满足SPI的要求。而通过给当前线程设置上下文类加载器,就可以由设置的上下文类加载器来实现对于接口实现类的加载。

上下文加载器(线程中默认使用)

可以通过Thread中getContextClassload()和setContextClassload(Classloader class)进行获得和设置

机制:若是没有手动进行设置上下文加载器是自动继承父线程的上下文加载器的,java应用初始上下文加载器是应用加载器,后面几乎所有线程都可以用该加载器进行加载类和资源

上下文加载的类可以被父加载器加载的类进行使用

怎么使用(一般写底层框架时使用)

首先是获取上下文加载器

再是可以将上下文加载器设置成你所需要的加载器,进行加载相关的类

最后还原成第一步获取的默认加载器

Tomcat


上图是Tomcat类加载的类图,前面三个不用说,CommonClassLoaderCatalinaClassLoaderSharedClassLoaderWebAppClassLoaderJspClassLoader则是Tomcat自己实现的类加载器,分别加载common包server包shared包WebApp/WEB-INF/lib包以及JSP文件,前面三个在tomcat 6之后已经合并到根目录下的lib目录下。而WebAppClassLoader则是每一个应用程序对应一个,JspClassLoader是每一个JSP文件都会对应一个,并且这两个类加载器都没有父类加载器,这也就违背了双亲委派模型
为什么每个应用程序需要单独的WebAppClassLoader实例?因为每个应用程序需要彼此隔离,假如在两个应用中定义了一样的类(完全限定名),如果遵循双亲委派那就只会存在一份了,另外不同的应用还有可能依赖同一个类库的不同版本,这也需要隔离,所以每一个应用程序都会对应一个WebAppClassLoader,它们共享的类库可以让SharedClassLoader加载,另外这些类加载加载的类对Tomcat本身来说也是隔离的(CatalinaClassLoader加载的)。
为什么每个JSP文件需要对应单独的一个JspClassLoader实例?这是由于JSP是支持运行时修改的,修改后会丢弃掉之前编译生成的class,并重新生成一个JspClassLoader实例去加载新的class。
以上就是Tomcat为什么要打破双亲委派模型的原因。

OSGI

OSGI是用于实现模块热部署,像Eclipse的插件系统就是利用OSGI实现的,这个技术非常复杂同时使用的也越来越少了,感兴趣的读者可自行查阅资料学习,这里不再进行阐述。

总结

类加载的过程让我们了解到一个类是如何被加载到内存中,需要经过哪些阶段;而类加载器和双亲委派模型则是告诉我们应该怎么去加载类、类的加载优先级是怎样的,其中的设计思想我们也可以学习借鉴;最后需要深刻理解的是为什么需要打破双亲委派,在遇到相应的场景时应该怎么做。

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

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

相关文章

光场相机建模与畸变校正改进方法

摘要&#xff1a;光场相机作为一种新型的成像系统&#xff0c;可以直接从一次曝光的图像中得到三维信息。为了能够更充分有效地利用光场数据包含的角度和位置信息&#xff0c;完成更加精准的场景深度计算&#xff0c;从而提升光场相机的三维重建的精度&#xff0c;需要实现精确…

比特币突然暴跌

作者&#xff1a;秦晋 周末愉快。 今天给大家分享两则比特币新闻&#xff0c;也是两个数据。一则是因为中东地缘政治升温&#xff0c;传统资本市场的风险情绪蔓延至加密市场&#xff0c;引发加密市场暴跌。比特币跌至66000美元下方。杠杆清算金额高达8.5亿美元。 二则是&#x…

Spring(24) Json序列化的三种方式(Jackson、FastJSON、Gson)史上最全!

目录 一、Jackson 方案&#xff08;SpringBoot默认支持&#xff09;1.1 Jackson 库的特点1.2 Jackson 的核心模块1.3 Maven依赖1.4 代码示例1.5 LocalDateTime 格式化1.6 统一配置1.7 常用注解1.8 自定义序列化和反序列化1.9 Jackson 工具类 二、FastJSON 方案2.1 FastJSON 的特…

Redis消息队列-基于PubSub的消息队列

7.3 Redis消息队列-基于PubSub的消息队列 PubSub&#xff08;发布订阅&#xff09;是Redis2.0版本引入的消息传递模型。顾名思义&#xff0c;消费者可以订阅一个或多个channel&#xff0c;生产者向对应channel发送消息后&#xff0c;所有订阅者都能收到相关消息。 SUBSCRIBE …

【练习】二分查找

1、704 &#xff08;1&#xff09;题目描述 &#xff08;2&#xff09;代码实现 package com.hh.practice.leetcode.array.demo_02;public class BinarySearch_704 {public int search(int[] nums, int target) {int i 0,j nums.length -1;while (i < j){int mid (ij) &…

【QT+QGIS跨平台编译】181:【QGIS+Qt跨平台编译】—【错误处理:找不到_DEBUGA】

点击查看专栏目录 文章目录 一、找不到_DEBUGA二、原因分析三、错误处理 一、找不到_DEBUGA 报错信息&#xff1a; 二、原因分析 采用了非UNICODE&#xff1a; DEFINES - UNICODE没法识别 _DEBUGA 但可以识别 _DEBUG 三、错误处理 修改 _DEBUGA 为 _DEBUG

C语言专项训练

道阻且长&#xff0c;接下来就要开始数据结构的学习&#xff0c;而学不可以不练&#xff0c;在接下来的学习中&#xff0c;数据结构学习的同时&#xff0c;c语言训练也要开始更新了&#xff5e; NO.1 函数 1.void函数声明 这道题一看就秒了(开玩笑)我们在知道答案的同时&#…

Linux多进程开发2 - 进程间通信

1、进程间通信的概念 进程是一个独立的资源分配单元&#xff0c;不同进程之间的资源是独立的&#xff0c;没有关联&#xff0c;不能在一个进程中直接访问另一个进程的资源。但是&#xff0c;进程不是孤立的&#xff0c;不同的进程需要进行信息的交换和状态的传递等&…

【YOLOV5 入门】——Pyside6/PyQt5可视化UI界面后端逻辑

声明&#xff1a;笔记是做项目时根据B站博主视频学习时自己编写&#xff0c;请勿随意转载&#xff01; 一、环境安装 VScode/Pycharm终端进入虚拟环境后&#xff0c;输入下面代码安装pyside6&#xff0c;若用的Pycharm作为集成开发环境&#xff0c;也下载个pyqt5&#xff1a; …

得物 Zookeeper SLA 也可以 99.99% | 得物技术

一、背景 ZooKeeper&#xff08;ZK&#xff09;是一个诞生于2007年的分布式应用程序协调服务。尽管出于一些特殊的历史原因&#xff0c;许多业务场景仍然不得不依赖它。比如&#xff0c;Kafka、任务调度等。特别是在 Flink 混合部署 ETCD 解耦 时&#xff0c;业务方曾要求绝对…

C语言之探秘:访问结构体空指针与结构体空指针的地址的区别(九十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

npm配置阿里镜像库

1、配置阿里云镜像源 #查看当前使用的镜像地址命令 npm config get registry#设置阿里镜像源 npm config set registry http://registry.npmmirror.com 这里要注意下&#xff0c;之前的镜像源地址 https://registry.npm.taobao.org/ 已经不能用了&#xff0c;这里要更改为新…

SpringCloud之LoadBalancer负载均衡器的简单使用

SpringCloud之LoadBalancer负载均衡器的简单使用 loadbalancer用于对提供服务的集群做一个节点的选取规则。 如图所示&#xff0c;load balancer集成在调用方 示例 创建loadbalance-base模块,并引入相关依赖 <dependencies><dependency><groupId>org.spr…

基于有序抖动块截断编码的水印嵌入和提取算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 噪声测试 旋转测试 压缩测试 2.算法运行软件版本 matlab2022a 3.部分核心程序 ............................................................…

STM32—外部中断_按键控制 LED

目录 1 、 电路构成及原理图 2 、编写实现代码 main.c exti.c 3、代码讲解 4、烧录到开发板调试、验证代码 5、检验效果 开发板介绍 相关笔记 此笔记基于朗峰 STM32F103 系列全集成开发板的记录 1 、 电路构成及原理图 外部中断---EXTI EXTI&#xff08;External…

codeforce #925 (div3) 题解

D. Divisible Pairs 给出数组 a a a&#xff0c;如果二元组 ( i , j ) (i,j) (i,j)满足 a i a j m o d x 0 & & a i − a j m o d y 0 a_i a_j mod x 0 \&\& a_i - a_j mod y 0 ai​aj​modx0&&ai​−aj​mody0&#xff0c;则beauty。其中 i &…

BEVFormer代码阅读

1. 代码地址 https://github.com/fundamentalvision/BEVFormer 2. 代码结构 个人理解&#xff0c;代码库中的代码与两篇论文都略有不同&#xff0c;总结起来&#xff0c;其结构如下。 3. BEVFormer 的 Pipeline 根据自己调试算法模型以及对论文的理解&#xff0c;我这里将 …

牛客网刷题 :BC50 你是天才吗

描述 据说智商140以上者称为天才&#xff0c;KiKi想知道他自己是不是天才&#xff0c;请帮他编程判断。输入一个整数表示一个人的智商&#xff0c;如果大于等于140&#xff0c;则表明他是一个天才&#xff0c;输出“Genius”。 输入描述&#xff1a; 多组输入&#xff0c;每…

(十)C++自制植物大战僵尸游戏设置功能实现

植物大战僵尸游戏开发教程专栏地址http://t.csdnimg.cn/m0EtD 游戏设置 游戏设置功能是一个允许玩家根据个人喜好和设备性能来调整游戏各项参数的重要工具。游戏设置功能是为了让玩家能够根据自己的需求和设备性能来调整游戏&#xff0c;以获得最佳的游戏体验。不同的游戏和平…

vite - WebAssembly入门

1. 初始化 vite 项目 1.1 安装 nvm&#xff08;可选&#xff09; brew update brew install nvm在 ~/.zshrc 添加 export NVM_DIR~/.nvm source $(brew --prefix nvm)/nvm.sh执行如下命令 source ~/.zshrc1.2 安装 node nvm install nodenvm ls -> …