【JVM基础篇】打破双亲委派机制

文章目录

    • 打破双亲委派机制
      • 自定义类加载器
        • 双亲委派机制核心代码
        • 打破双亲委派机制
        • 自定义类加载器父类怎么是AppClassLoader呢?
        • 两个自定义类加载器加载相同限定名的类,不会冲突吗?
        • 拓展类加载器功能
      • 线程上下文类加载器
        • JDBC案例
          • 那么问题来了,DriverManager怎么知道jar包中要加载的驱动在哪儿?
          • Jar包中的驱动是哪个加载器加载的
          • DriverManager本身是通过启动类加载器加载的,那SPI是如何获取应用程序类加载器的呢?
          • 总结
        • JDBC案例中真的打破了双亲委派机制吗?
      • Osgi框架的类加载器(简单了解)
      • 使用阿里arthas不停机解决线上问题
    • 文章说明

打破双亲委派机制

双亲委派机制虽然很有用,但有时候需要打破双亲委派机制才可以实现一些功能

打破双亲委派机制历史上出现了三种方式,但本质上只有第一种算是真正的打破了双亲委派机制

  • ※ 自定义类加载器并且重写loadClass方法。如Tomcat通过这种方式实现应用之间类隔离,《面试篇》中分享它的做法。
  • ※ 线程上下文类加载器。使用SPI机制、利用上下文类加载器加载类,比如JDBC和JNDI等。
  • Osgi框架的类加载器。Osgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载,目前很少使用。

自定义类加载器

  • 一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类,Tomcat要保证这两个类都能加载并且它们应该是不同的类。
  • 如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了。

在这里插入图片描述

Tomcat使用了自定义类加载器来实现应用之间类的隔离。 每一个应用会有一个独立的类加载器加载对应的类。

在这里插入图片描述

双亲委派机制核心代码

那么自定义加载器是如何能做到的呢?首先我们需要先了解,双亲委派机制的代码到底在哪里,接下来只需要把这段代码消除即可。

ClassLoader中包含了4个核心方法,双亲委派机制的核心代码就位于loadClass方法中。

// 类加载的入口,提供了双亲委派机制。内部会调用findClass   重要
public Class<?> loadClass(String name)// 由类加载器子类实现,获取二进制数据调用defineClass ,
// 比如URLClassLoader会根据文件路径去获取类文件中的二进制数据。重要
// 这个方法ClassLoader是没有实现的,由子类来实现
protected Class<?> findClass(String name)// 做一些类名的校验,然后调用虚拟机底层的方法(C++实现)将字节码信息加载到虚拟机内存中
protected final Class<?> defineClass(String name, byte[] b, int off, int len)// 执行类生命周期中的连接阶段
protected final void resolveClass(Class<?> c)

1、入口方法:

调用重载方法,传递参数不执行连接阶段

在这里插入图片描述

添加测试B类,该类会在连接阶段会打印信息

在这里插入图片描述

尝试加载,发现没有任何输出,说明连接阶段和初始化阶段都没有执行

在这里插入图片描述

如果使用Class.forName,还是会有连接阶段

在这里插入图片描述

2、看看ClassLoader类里面的实现

【ClassLoader】

/*** Loads the class with the specified <a href="#name">binary name</a>.  The* default implementation of this method searches for classes in the* following order:** <ol>**   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class*   has already been loaded.  </p></li>**   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method*   on the parent class loader.  If the parent is <tt>null</tt> the class*   loader built-in to the virtual machine is used, instead.  </p></li>**   <li><p> Invoke the {@link #findClass(String)} method to find the*   class.  </p></li>** </ol>** <p> If the class was found using the above steps, and the* <tt>resolve</tt> flag is true, this method will then invoke the {@link* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.** <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link* #findClass(String)}, rather than this method.  </p>** <p> Unless overridden, this method synchronizes on the result of* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method* during the entire class loading process.** @param  name*         The <a href="#name">binary name</a> of the class** @param  resolve*         If <tt>true</tt> then resolve the class** @return  The resulting <tt>Class</tt> object** @throws  ClassNotFoundException*          If the class could not be found*/
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException
{// 通过加锁,让多线程下只有一个线程加载类synchronized (getClassLoadingLock(name)) {// First, check if the class has already been loaded// 判断当前加载器是否加载过这个类,如果加载过,返回这个类对象Class<?> c = findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (parent != null) {// 向上委派c = parent.loadClass(name, false);} else {// parent为空,通过启动类加载器进行加载(调用C++代码)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.// c还是空,说明父类加载器都没有加载成功,换成当前加载器进行加载long t1 = System.nanoTime();// ClassLoader并没有实现当前方法,交给子类进行实现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;}
}

在这里插入图片描述

如果查找都失败,进入加载阶段,首先会由启动类加载器加载,这段代码在findBootstrapClassOrNull中。如果失败会抛出异常,接下来执行下面这段代码:

在这里插入图片描述

父类加载器加载失败就会抛出异常,回到子类加载器的这段代码,这样就实现了加载并向下传递。

可以参考URLClassLoader中的实现

【URLClassLoader】

/*** Finds and loads the class with the specified name from the URL search* path. Any URLs referring to JAR files are loaded and opened as needed* until the class is found.** @param name the name of the class* @return the resulting class* @exception ClassNotFoundException if the class could not be found,*            or if the loader is closed.* @exception NullPointerException if {@code name} is {@code null}.*/
protected Class<?> findClass(final String name)throws ClassNotFoundException
{final Class<?> result;try {result = AccessController.doPrivileged(new PrivilegedExceptionAction<Class<?>>() {public Class<?> run() throws ClassNotFoundException {// 获取path目录下的字节码文件String path = name.replace('.', '/').concat(".class");// 获取文件对象,供下面获取二进制数据Resource res = ucp.getResource(path, false);if (res != null) {try {return defineClass(name, res);} catch (IOException e) {throw new ClassNotFoundException(name, e);} catch (ClassFormatError e2) {if (res.getDataError() != null) {e2.addSuppressed(res.getDataError());}throw e2;}} else {return null;}}}, acc);} catch (java.security.PrivilegedActionException pae) {throw (ClassNotFoundException) pae.getException();}if (result == null) {throw new ClassNotFoundException(name);}return result;
}/** Defines a Class using the class bytes obtained from the specified* Resource. The resulting Class must be resolved before it can be* used.*/
private Class<?> defineClass(String name, Resource res) throws IOException {long t0 = System.nanoTime();int i = name.lastIndexOf('.');URL url = res.getCodeSourceURL();if (i != -1) {String pkgname = name.substring(0, i);// Check if package already loaded.Manifest man = res.getManifest();definePackageInternal(pkgname, man, url);}// Now read the class bytes and define the classjava.nio.ByteBuffer bb = res.getByteBuffer();if (bb != null) {// Use (direct) ByteBuffer:CodeSigner[] signers = res.getCodeSigners();CodeSource cs = new CodeSource(url, signers);sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);return defineClass(name, bb, cs);} else {// 获取文件的二进制数组byte[] b = res.getBytes();// must read certificates AFTER reading bytes.CodeSigner[] signers = res.getCodeSigners();CodeSource cs = new CodeSource(url, signers);sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);// 将二进制数据传给父类ClassLoaderreturn defineClass(name, b, 0, b.length, cs);}
}

【ClassLoader】

/*** Converts an array of bytes into an instance of class <tt>Class</tt>,* with an optional <tt>ProtectionDomain</tt>.  If the domain is* <tt>null</tt>, then a default domain will be assigned to the class as* specified in the documentation for {@link #defineClass(String, byte[],* int, int)}.  Before the class can be used it must be resolved.** <p> The first class defined in a package determines the exact set of* certificates that all subsequent classes defined in that package must* contain.  The set of certificates for a class is obtained from the* {@link java.security.CodeSource <tt>CodeSource</tt>} within the* <tt>ProtectionDomain</tt> of the class.  Any classes added to that* package must contain the same set of certificates or a* <tt>SecurityException</tt> will be thrown.  Note that if* <tt>name</tt> is <tt>null</tt>, this check is not performed.* You should always pass in the <a href="#name">binary name</a> of the* class you are defining as well as the bytes.  This ensures that the* class you are defining is indeed the class you think it is.** <p> The specified <tt>name</tt> cannot begin with "<tt>java.</tt>", since* all classes in the "<tt>java.*</tt> packages can only be defined by the* bootstrap class loader.  If <tt>name</tt> is not <tt>null</tt>, it* must be equal to the <a href="#name">binary name</a> of the class* specified by the byte array "<tt>b</tt>", otherwise a {@link* NoClassDefFoundError <tt>NoClassDefFoundError</tt>} will be thrown. </p>** @param  name*         The expected <a href="#name">binary name</a> of the class, or*         <tt>null</tt> if not known** @param  b*         The bytes that make up the class data. The bytes in positions*         <tt>off</tt> through <tt>off+len-1</tt> should have the format*         of a valid class file as defined by*         <cite>The Java&trade; Virtual Machine Specification</cite>.** @param  off*         The start offset in <tt>b</tt> of the class data** @param  len*         The length of the class data** @param  protectionDomain*         The ProtectionDomain of the class** @return  The <tt>Class</tt> object created from the data,*          and optional <tt>ProtectionDomain</tt>.** @throws  ClassFormatError*          If the data did not contain a valid class** @throws  NoClassDefFoundError*          If <tt>name</tt> is not equal to the <a href="#name">binary*          name</a> of the class specified by <tt>b</tt>** @throws  IndexOutOfBoundsException*          If either <tt>off</tt> or <tt>len</tt> is negative, or if*          <tt>off+len</tt> is greater than <tt>b.length</tt>.** @throws  SecurityException*          If an attempt is made to add this class to a package that*          contains classes that were signed by a different set of*          certificates than this class, or if <tt>name</tt> begins with*          "<tt>java.</tt>".*/
protected final Class<?> defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain)throws ClassFormatError
{protectionDomain = preDefineClass(name, protectionDomain);String source = defineClassSourceLocation(protectionDomain);Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);postDefineClass(c, protectionDomain);return c;
}

在这里插入图片描述

3、最后根据传入的参数判断是否进入连接阶段:

在这里插入图片描述

打破双亲委派机制

接下来实现打破双亲委派机制,其实就是重写双亲委派机制的部分代码

package classloader.broken;//package com.itheima.jvm.chapter02.classloader.broken;import org.apache.commons.io.IOUtils;import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.ProtectionDomain;
import java.util.regex.Matcher;/*** 打破双亲委派机制 - 自定义类加载器*/
public class BreakClassLoader1 extends ClassLoader {private String basePath;private final static String FILE_EXT = ".class";//设置加载目录public void setBasePath(String basePath) {this.basePath = basePath;}//使用commons io 从指定目录下加载文件private byte[] loadClassData(String name)  {try {String tempName = name.replaceAll("\\.", Matcher.quoteReplacement(File.separator));FileInputStream fis = new FileInputStream(basePath + tempName + FILE_EXT);try {return IOUtils.toByteArray(fis);} finally {IOUtils.closeQuietly(fis);}} catch (Exception e) {System.out.println("自定义类加载器加载失败,错误原因:" + e.getMessage());return null;}}//重写loadClass方法@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 如果是java包下,还是走双亲委派机制if(name.startsWith("java.")){// 交给父类来加载return super.loadClass(name);}// 通过类名从磁盘中指定目录下找到字节码文件并加载二进制数据到内存中byte[] data = loadClassData(name);// 调用虚拟机底层方法,根据二进制数据,在方法区和堆区创建对象return defineClass(name, data, 0, data.length);}// 使用自定义加载器来加载类public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {//第一个自定义类加载器对象BreakClassLoader1 classLoader1 = new BreakClassLoader1();classLoader1.setBasePath("D:\\lib\\");Class<?> clazz1 = classLoader1.loadClass("com.itheima.my.A");//第二个自定义类加载器对象BreakClassLoader1 classLoader2 = new BreakClassLoader1();classLoader2.setBasePath("D:\\lib\\");Class<?> clazz2 = classLoader2.loadClass("com.itheima.my.A");System.out.println(clazz1 == clazz2);Thread.currentThread().setContextClassLoader(classLoader1);System.out.println(Thread.currentThread().getContextClassLoader());System.in.read();}
}
自定义类加载器父类怎么是AppClassLoader呢?

在这里插入图片描述

默认情况下自定义类加载器的父类加载器是应用程序类加载器,除非进行手动设置:

在这里插入图片描述

以Jdk8为例,ClassLoader类中提供了构造方法设置parent的内容:

在这里插入图片描述

这个构造方法由另外一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的是AppClassLoader。

在这里插入图片描述

两个自定义类加载器加载相同限定名的类,不会冲突吗?

不会冲突,在同一个Java虚拟机中,只有相同类加载器+相同的类限定名才会被认为是同一个类。不同的加载器对象就算加载同一个类,也会认为是不同的类。

在Arthas中使用sc –d 类名的方式查看具体的情况。

如下代码:

 public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IOException {// 第一个自定义类加载器对象BreakClassLoader1 classLoader1 = new BreakClassLoader1();classLoader1.setBasePath("D:\\lib\\");Class<?> clazz1 = classLoader1.loadClass("com.itheima.my.A");// 第二个自定义类加载器对象BreakClassLoader1 classLoader2 = new BreakClassLoader1();classLoader2.setBasePath("D:\\lib\\");Class<?> clazz2 = classLoader2.loadClass("com.itheima.my.A");System.out.println(clazz1 == clazz2);}

打印的应该是false,因为两个类加载器对象不同,尽管加载的是同一个类名,最终Class对象也不是相同的。可以通过Arthas看详细信息:

在这里插入图片描述

也会出现两个不同的A类。

拓展类加载器功能

有的时候并不希望打破双亲委派机制,只是想拓展一些功能,例如从数据库中加载字节数据,这时候应该重写findClass方法

线程上下文类加载器

利用上下文类加载器加载类,大量用于java自己的技术中,比如JDBC和JNDI等。

JDBC案例

我们来看下JDBC的案例:

  • JDBC不希望出现某个特定数据库的语法,希望可以提高代码的泛化性,这样对接任何数据库都比较容易,JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql驱动、oracle驱动,DriverManager会去加载制定数据库的驱动,这样可以连接使用相应数据库了。DriverManager在加载驱动的时候,是打破了双亲委派机制的
package classloader.broken;//package com.itheima.jvm.chapter02.classloader.broken;import com.mysql.cj.jdbc.Driver;import java.sql.*;/*** 打破双亲委派机制 - JDBC案例*/
public class JDBCExample {// JDBC driver name and database URLstatic final String JDBC_DRIVER = "com.mysql.cj.jdbc.Driver";static final String DB_URL = "jdbc:mysql:///bank1";//  Database credentialsstatic final String USER = "root";static final String PASS = "123456";public static void main(String[] args) {Connection conn = null;Statement stmt = null;try {conn = DriverManager.getConnection(DB_URL, USER, PASS);stmt = conn.createStatement();String sql;sql = "SELECT id, account_name FROM account_info";ResultSet rs = stmt.executeQuery(sql);//STEP 4: Extract data from result setwhile (rs.next()) {//Retrieve by column nameint id = rs.getInt("id");String name = rs.getString("account_name");//Display valuesSystem.out.print("ID: " + id);System.out.print(", Name: " + name + "\n");}//STEP 5: Clean-up environmentrs.close();stmt.close();conn.close();} catch (SQLException se) {//Handle errors for JDBCse.printStackTrace();} catch (Exception e) {//Handle errors for Class.forNamee.printStackTrace();} finally {//finally block used to close resourcestry {if (stmt != null)stmt.close();} catch (SQLException se2) {}// nothing we can dotry {if (conn != null)conn.close();} catch (SQLException se) {se.printStackTrace();}//end finally try}//end try}//end main
}//end FirstExample

2、DriverManager类位于rt.jar包中,由启动类加载器加载。

在这里插入图片描述

3、依赖中的mysql驱动对应的类,由应用程序类加载器来加载。

在这里插入图片描述

在类中有初始化代码:

在这里插入图片描述

DriverManager属于rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制(一般是低层将加载任务委派给高层,这里确实启动类加载器将加载任务委派给应用程序类加载器)。(这点存疑,一会儿再讨论)

在这里插入图片描述

那么问题来了,DriverManager怎么知道jar包中要加载的驱动在哪儿?

1、在类的初始化代码中有这么一个方法LoadInitialDrivers

在这里插入图片描述

2、这里使用了SPI机制,去加载所有jar包中实现了Driver接口的实现类。

在这里插入图片描述

3、SPI机制就是在这个位置下存放了一个文件,文件名是接口的名字,文件里存储了实现类的类名。这样SPI机制就可以通过扫描这个文件里面的内容找到实现类了。DriverManager拿到实现类的类名之后,就可以加载驱动了

在这里插入图片描述

在这里插入图片描述

‘’’在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

通过迭代器遍历所有实现类,即所有驱动

在这里插入图片描述

Jar包中的驱动是哪个加载器加载的

在这里插入图片描述

DriverManager本身是通过启动类加载器加载的,那SPI是如何获取应用程序类加载器的呢?

SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。

在这里插入图片描述

在这里插入图片描述

执行一下看看

在这里插入图片描述

一个创建之后,虚拟机会将应用程序加载器保存的线程的上下文中

在这里插入图片描述

所以创建线程时,线程上下文的类加载器就是应用程序加载器,如果想获取应用程序加载器,可以直接使用Thread.currentThread().getContextClassLoader()方法来获取

当然也是可以修改线程上下文中的类加载器的

在这里插入图片描述

总结

在这里插入图片描述

JDBC案例中真的打破了双亲委派机制吗?
  • 说法一,打破了双亲委派机制:最早这个论点提出是在周志明《深入理解Java虚拟机》中,他认为打破了双亲委派机制,这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,所以打破了双亲委派机制。
  • 说法二(正确),没有打破:我们分别从DriverManager以及驱动类的加载流程上分析,JDBC只是在DriverManager加载完之后,通过初始化阶段触发了驱动类的加载,类的加载依然遵循双亲委派机制。双亲委派机制描述的是一个类的加载过程,这里实际上是加载了两种类,一个是DriverManager,另外一种是Jar包的驱动类。DriverManager的加载是满足双亲委派机制的,Jar包驱动类的加载也是满足双亲委派机制的,因为都是调用虚拟机自动的类加载器,并没有重写loadClass方法。所以我认为这里没有打破双亲委派机制,只是用一种巧妙的方法让启动类加载器加载的类去引发的其他类的加载。

Osgi框架的类加载器(简单了解)

历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载(如下图的加载器1和2)。OSGi还使用类加载器实现了热部署的功能。热部署指的是在服务不停止的情况下,动态地更新字节码文件到内存中。JDK9之后不再使用OSGi,现在可以使用arthas解决热部署问题。

在这里插入图片描述

由于这种机制使用已经不多,所以不再过多讨论OSGi,着重来看下热部署在实际项目中的应用。

使用阿里arthas不停机解决线上问题

背景:

小李的团队将代码上线之后,发现存在一个小bug,但是用户急着使用,如果重新打包再发布需要一个多小时的时间,所以希望能使用arthas尽快的将这个问题修复。

思路:

只需要将修改的类的字节码上传,然后让类加载器重新加载,即可实现热部署

  1. 在出问题的服务器上部署并启动一个 arthas。
  2. 使用jad命令jad --source-only 类全限定名 > 目录/文件名.java 将源代码反编译并保存到指定目录中,然后可以用其它编译器,比如 vim 来修改源码
  3. 使用mc 命令mc –c 类加载器的hashcode 目录/文件名.java -d 输出目录用来编译修改过的代码
  4. 用 retransform 命令retransform class文件所在目录/xxx.class加载新的字节码,将其更新到内存中

详细流程演示:

1、这段代码编写有误,在枚举中的类型判断上使用了== 而不是equals

在这里插入图片描述

2、枚举中是这样定义的,1001是普通用户,1002是VIP用户:

在这里插入图片描述

3、由于代码有误,导致传递1001参数时,返回的是收费用户的内容。

在这里插入图片描述

4、jad --source-only 类全限定名 > 目录/文件名.java 使用 jad 命令反编译,然后可以用其它编译器,比如 vim 来修改源码
在这里插入图片描述

这里直接双击文件使用finalShell编辑:

在这里插入图片描述

5、mc –c 类加载器的hashcode 目录/文件名.java -d 输出目录 使用mc 命令用来编译修改过的代码
在这里插入图片描述

可以使用sc -d查看指定类的类加载器hashcode
在这里插入图片描述

在这里插入图片描述

6、retransform class文件所在目录/xxx.class 用 retransform 命令加载新的字节码

在这里插入图片描述

7、测试:

在这里插入图片描述

注意事项:

1、程序重启之后,字节码文件会恢复(因为retransform只是将字节码信息更新到内存中),除非将class文件放入jar包中进行更新。

2、使用retransform不能添加方法或者字段,也不能更新正在执行中的方法。

文章说明

该文章是本人学习 黑马程序员 的学习笔记,文章中大部分内容来源于 黑马程序员 的视频黑马程序员JVM虚拟机入门到实战全套视频教程,java大厂面试必会的jvm一套搞定(丰富的实战案例及最热面试题),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对 黑马程序员 的优质课程表示感谢。

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

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

相关文章

博客管理系统

文章目录 博客管理系统一、项目演示二、项目介绍三、系统部分功能截图四、部分代码展示五、底部获取项目&#xff08;9.9&#xffe5;带走&#xff09; 博客管理系统 一、项目演示 博客系统 二、项目介绍 三个角色&#xff1a;游客 用户 管理员 游客可以浏览文章&#xff0c…

[Kubernetes] sealos 部署 K8s v1.25.0 集群

文章目录 1.sealos 介绍2.操作系统基础配置3.安装部署 K8s4.验证 K8s 集群5.部署测试资源 1.sealos 介绍 Sealos 是一个基于 Kubernetes 内核的云操作系统发行版。它采用云原生方式&#xff0c;摒弃传统的云计算架构&#xff0c;转向以 Kubernetes 为云内核的新架构。这使得企…

FileZilla一款免费开源的FTP软件,中文正式版 v3.67.0

01 软件介绍 FileZilla 客户端是一个高效且可信的跨平台应用程序&#xff0c;支持 FTP、 FTPS 和 SFTP 协议&#xff0c;其设计宗旨在于为用户提供一个功能丰富且直观的图形界面。此客户端的核心特性包括一个站点管理器&#xff0c;该管理器能有效地存储和管理用户连接详情及登…

tauri2.0bate版本支持移动端开发了,ios和android开发有福了

Tauri 是一个开源框架&#xff0c;用于构建轻量级、高性能的桌面应用程序。它是使用 Web 技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;来创建用户界面&#xff0c;同时利用 Rust 语言提供的api功能。Tauri 的目标是提供一种更高效、更安全的方式来开发跨平台的桌面…

OpenAI 或将推出多模态人工智能数字助理;研究发现部分 AI 系统已学会「说谎」丨 RTE 开发者日报 Vol.203

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

黄啤:醇厚与经典的传承

啤酒的历史悠久&#xff0c;种类繁多&#xff0c;而黄啤作为其中的一种经典风格&#xff0c;一直备受人们的喜爱。Fendi club黄啤作为精酿啤酒的代表&#xff0c;完善地传承了黄啤的醇厚与经典&#xff0c;为消费者带来了与众不同的口感体验。 Fendi club黄啤在酿造过程中&…

转移插槽笔记

4.3.4.转移插槽 我们要将num存储到7004节点&#xff0c;因此需要先看看num的插槽是多少&#xff1a; 如上图所示&#xff0c;num的插槽为2765. 我们可以将0~3000的插槽从7001转移到7004&#xff0c;命令格式如下&#xff1a; 具体命令如下&#xff1a; 建立连接&#xff1a;…

【C++ 】红黑树

1.1 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff…

R语言:肿瘤突变负荷分析

> merge_maf <- function(metadata, path){ #通过合并path,还有sample sheet前两列得到每一个文件的完整路径 filenames <- file.path(path, metadata$file_id, metadata$file_name, fsep .Platform$file.sep) message (##############…

Vulnhub-wp 获取vulnhub靶机wp搜索工具

项目地址:https://github.com/MartinxMax/vulnhub-wp 简介 搜索Vulnhub平台的解题文章,之过滤返回出正确可访问的页面 使用 $ python3 vulnhubwp.py 支持模糊搜索 [] Query: kiop 进入选项4,获取wp地址 [] Choice options: 4

DML之操作数据表

1. 插入数据 (1). 前言 前文我们实现了如果创建表&#xff0c;接下来我们将学习如何向数据表中插入数据.插入有两种方式. (2). 方式1 : 情况1 : 使用该语法一次只能向表中插入一条记录.为表中的任意字段按默认的顺序插入数据.值列表中需要为表的每一个字段指定值.并且值…

网络库-libevent介绍

1.简介 libevent是一个事件驱动的网络库&#xff0c;主要用于构建可扩展的网络服务器。它提供了跨平台的API&#xff0c;支持多种事件通知机制&#xff0c;如select、poll、epoll、kqueue等。 主要组件 event: 表示一个具体的事件&#xff0c;包括事件类型、事件回调等。eve…

【网络安全入门】你必须要有的学习工具(附安装包)零基础入门到进阶,看这一篇就够了!

工欲善其事必先利其器 在新入门网络安全的小伙伴而言。这些工具你必须要有所了解。本文我们简单说说这些网络安全工具吧&#xff01; Web安全类 Web类工具主要是通过各种扫描工具&#xff0c;发现web站点存在的各种漏洞如sql注入、xss等。从而获取系统权限&#xff0c;常用的…

iZotope RX 11 for Mac:音频修复的终极利器

在音频制作的浩瀚星海中&#xff0c;每一份声音都是珍贵的宝石&#xff0c;但往往被各种噪音、杂音所掩盖。此刻&#xff0c;iZotope RX 11 for Mac犹如一位专业的匠人&#xff0c;以其精湛的技术&#xff0c;将每一份声音雕琢至完美。 iZotope RX 11 for Mac&#xff0c;这是…

创新点!CNN与LSTM结合,实现更准预测、更快效率、更高性能!

推荐一个能发表高质量论文的好方向&#xff1a;LSTM结合CNN。 LSTM擅长捕捉序列数据中的长期依赖关系&#xff0c;而CNN则擅长提取图像数据的局部特征。通过结合两者的优势&#xff0c;我们可以让模型同时考虑到数据的时序信息和空间信息&#xff0c;减少参数降低过拟合风险&a…

MySQL—子查询

目录 ▐ 子查询概述 ▐ 准备工作 ▐ 标量子查询 ▐ 列子查询 ▐ 表子查询 ▐ 多信息嵌套 ▐ 子查询概述 • 子查询也称嵌套查询&#xff0c;即在一个查询语句中又出现了查询语句 • 子查询可以出现在from 后面 或where后面 • 出现在 from 后称表子查询&#xff0c;结…

远程终端协议TELNET

一、概述 TELNET是一个简单的远程终端协议&#xff0c;是互联网正式标准。 实现在本地对远程计算机进行操作&#xff1b;在本地键盘输入的字符通过应用层TELNET协议传输到远程服务器上&#xff0c;同时远程服务器把字符传送过来显示在本地的显示器上&#xff1b;TELNET协议采…

智能制造装备业项目数字化管理之项目模板管理

智能制造装备&#xff0c;作为工业4.0的核心组成部分&#xff0c;正日益受到全球制造业的关注。这类装备融合了信息技术和制造技术&#xff0c;旨在提高生产效率、降低成本并增强产品的个性化。然而&#xff0c;随着智能制造装备行业的飞速发展&#xff0c;项目管理复杂性也在不…

软件文档-总体测试计划书(Word原件2024)

软件资料清单列表部分文档&#xff1a; 工作安排任务书&#xff0c;可行性分析报告&#xff0c;立项申请审批表&#xff0c;产品需求规格说明书&#xff0c;需求调研计划&#xff0c;用户需求调查单&#xff0c;用户需求说明书&#xff0c;概要设计说明书&#xff0c;技术解决…

Spring事件分析以及多种使用方式实践 使用场景 附可执行demo

我们先说说它的特点&#xff0c;优缺点&#xff0c;以及使用场景&#xff0c;然后再说具体是怎么做的 Spring事件驱动的优点 松耦合 事件驱动模型通过发布-订阅机制促进组件间的解耦&#xff0c;发送者和接收者不需要直接知道对方的存在&#xff0c;只需关注事件本身&#xff…