【JVM系列】深入理解Java虚拟机(JVM)的核心技术:从加载到初始化的全过程解析(一、Java类加载器)

news2024/11/23 13:28:22

文章目录

  • 【JVM系列】深入理解Java虚拟机(JVM)的核心技术:从加载到初始化的全过程解析(一、Java类加载器)
    • 1. 类加载器加载的过程
    • 2. Class文件读取来源
    • 3. 类加载器的分类
    • 4. 那些操作会初始化类加载器
    • 5. 类加载器的双亲委派机制
    • 6. ClassLoader源码解读
      • 6.1 Launcher类源码解读
      • 6.2 自定义一个类加载器
    • 7. 根据类加载器手写热部署插件
    • 8. SPI机制
    • 9. 如何绕开双亲委派原则
    • 10. 常见的几款java虚拟机

【JVM系列】深入理解Java虚拟机(JVM)的核心技术:从加载到初始化的全过程解析(一、Java类加载器)

类加载器(Class Loader)是Java运行时环境中一个重要的组件,它负责在程序运行期间动态地加载类文件到Java虚拟机(JVM)中。类加载器不仅能够从本地文件系统加载类,也可以从网络或者其他自定义的存储位置加载类。这样的设计使得Java程序具有高度的灵活性和扩展性。

1. 类加载器加载的过程

类加载器在Java虚拟机(JVM)中负责加载类文件到内存中,并且进行相应的验证、准备和初始化工作。整个过程可以分为以下几个阶段:

1. 加载(Loading)

在这个阶段,类加载器会根据指定的类全名(包括包名)来查找并加载该类的二进制字节流。这个二进制字节流通常来自于.class文件,但也可以来自其他地方,例如数据库、网络资源等。加载完成后,类加载器会在内存中生成一个java.lang.Class对象,表示这个类。

2.验证(Verification)

验证阶段的主要目的是确保读入的字节流包含的信息符合JVM规范的要求,不会危害到虚拟机自身的安全。验证过程包括四个子阶段:文件格式验证、元数据验证、字节码验证和符号引用验证。

  • 文件格式验证:确保输入的字节流能正确地解析并符合Class文件格式的规范。
  • 元数据验证:对类的方法和字段根据Java语言规范进行语义验证。
  • 字节码验证:进行数据流和控制流分析,确保程序不会做出危害虚拟机安全的行为。
  • 符号引用验证:确保解析后的类的方法区的常量池里的各种符号引用可以成功地解析成具体的类、接口、字段和方法。

3.准备(Preparation)

准备阶段负责为类的静态变量分配内存,并设置类变量(即static修饰的变量)所需的初始值,这与程序代码无关,而是根据字段的数据类型和是否有显式初始化来决定的。例如所有的基本类型的字段会被初始化为零值(如0、0L、false、null等)。

4.解析(Resolution)

解析阶段是将类的二进制数据中的符号引用替换为直接引用的过程。符号引用就是符号名加上描述符组成的字符串,而直接引用则是可以直接定位到内存地址的指针、相对偏移量或是一个能够间接定位到目标的句柄。

5.初始化(Initialization)

初始化阶段是执行类构造器<clinit>()方法的过程。<clinit>()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序按照文本顺序排列。<clinit>()方法对于类或接口来说并不是必须的,如果一个类中没有静态语句块,也没有对类变量的赋值操作,那么编译器就可以不为这个类生成<clinit>()方法。

类的初始化还包括了其父类的初始化,如果类中有静态初始化块,则静态初始化块也会被执行。

需要注意的是,虽然类加载的过程通常按照上述顺序进行,但在某些情况下,例如在某些验证子阶段之间可能会有交叉行为,不是完全线性的。此外,类加载的具体实现细节可能因不同的JVM实现而有所不同。

2. Class文件读取来源

  1. 本地磁盘文件 java源代码编译的class文件

  2. 通过网络下载的class文件

  3. War、Jar解压的class文件

  4. 从专门的数据库中读取的class文件

  5. 使用java cglib、动态代理生成的代理类class文件

3. 类加载器的分类

  1. 启动(Bootstrap)类加载器:加载JVM自身工作需要的类,它由JVM自己实现。它会加载$JAVA_HOME/jre/lib下的文件 底层是C语言实现

  2. 扩展(Extension)类加载器:它是JVM的一部分,由sun.misc.LauncherExtClassLoader实现,他会加载ExtClassLoader实现,他会加载ExtClassLoader实现,他会加载JAVA_HOME/jre/lib/ext目录中的文件(或由System.getProperty(“java.ext.dirs”)所指定的文件)。 底层是Java实现

  3. (应用)AppClassLoader 类加载器:应用类加载器,我们工作中接触最多的也是这个类加载器,它由sun.misc.Launcher$AppClassLoader实现。他加载我们工程目录classpath下的classjar包,底层是java实现

  4. 自定义类加载器: 也就是用户自己定义的类加载器

image-20240912212021198
import java.util.Arrays;
import java.util.List;

public class JvmDemo01 {
    public static void main(String[] args) {
        // 应用类加载器
        bootstrapClassLoader();
        System.out.println("------------------------------");
        extClassLoader();
        System.out.println("------------------------------");
        appClassLoader();
    }

    /**
     * 启动类加载器的职责
     */
    public static void bootstrapClassLoader() {
        String property = System.getProperty("sun.boot.class.path");
        List<String> list = Arrays.asList(property.split(";"));
        list.forEach((t) -> {
            System.out.println("启动类加载器目录:" + t);
        });
    }


    /**
     * 扩展类加载器
     */
    public static void extClassLoader() {
        String property = System.getProperty("java.ext.dirs");
        List<String> list = Arrays.asList(property.split(";"));
        list.forEach((t) -> {
            System.out.println("扩展类加载器" + t);
        });
    }

    /**
     * app 类加载器
     */
    public static void appClassLoader() {
        String property = System.getProperty("java.class.path");
        List<String> list = Arrays.asList(property.split(";"));
        list.forEach((t) -> {
            System.out.println("应用类加载器" + t);
        });
    }
}

4. 那些操作会初始化类加载器

类的主动使用:

  1. 调用类的静态方法
  2. invokeStatic 调用静态方法
  3. Main
  4. new
  5. Class.formname
  6. 子类初始化一定会初始化父类

初始化一个类,那么一定会触发类加载器;但是类加载器加载了该类,但是该类不一定初始化。

5. 类加载器的双亲委派机制

首先在我们类加载器分为四种自定义类加载器、应用类加载器、扩展类加载器、启动类加载器。

当一个类加载器收到请求之后,首先会依次向上查找到最顶层类加载器(启动类加载器),依次向下加载class文件,如果已经加载到class文件,子加载器不会加继续加载该class文件。

**双亲委派机制机制的好处:**目的就是为了防御开发者为定义的类与jdk定义源码类产生冲突问题,保证该类在内存中的唯一性。

6. ClassLoader源码解读

6.1 Launcher类源码解读

public class JvmDemo02 {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("com.zhaoli.jvm.test01.JvmDemo01");
        Object o = aClass.newInstance();
        System.out.println(o.getClass().getClassLoader());
    }
}
public abstract class ClassLoader {
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {  // 确保类加载的线程安全性
            // 首先检查该类是否已经被加载过
            Class<?> 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异常,则说明父类加载器未能找到该类
                    // ClassNotFoundException thrown if class not found from the non-null parent class loader
                }

                if (c == null) {  // 如果此时类仍然未被加载
                    // 调用findClass方法来寻找该类
                    long t1 = System.nanoTime();  // 记录调用findClass的时间点
                    c = findClass(name);  // 这里是由当前类加载器自己去查找并加载类

                    // 当前类加载器是定义类的加载器;记录相关统计信息
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }

            if (resolve) {  // 如果需要解析类,则调用resolveClass方法来解析类
                resolveClass(c);
            }
            return c;  // 返回加载好的类
        }
    }

    private Class<?> findBootstrapClassOrNull(String name) {
        if (!checkName(name)) return null;  // 检查类名是否合法,如果不合法则返回null

        // 尝试通过启动类加载器来加载类
        return findBootstrapClass(name);
    }

    // 如果找不到指定的类,则返回null
    private native Class<?> findBootstrapClass(String name);
}

代码为Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("com.zhaoli.jvm.test01.JvmDemo01");

  1. 先进入loadClass()

    • 发现此类没有被加载过
    • 存在父类加载器继续调用loadClass()让父类加载器加载
    • 循环(发现此类没有被加载过)(自动向上依次检查是否已经被加载过)
    • 存在父类加载器继续调用loadClass()(让父类加载器加载)直到没有父类加载器
    • 调用findBootstrapClassOrNull()尝试加载核心类库中的类
    • 发现没有(if (c == null))则(自定向下依次加载)调用findClass()(此时是扩展类加载器)查找此类
    • 返回上一级的loadClass()方法,发现没有(if (c == null))则(自定向下依次加载)调用findClass()(此时是应用类加载器)查找此类
    • 最终找到此类(是应用类加载器)找到的
  2. 当我们将com.zhaoli.jvm.test01.JvmDemo01类对应的class类拷贝到D:\Java\jdk1.8\jre\目录下(注意并不是直接拷贝JvmDemo01.class而是拷贝整个文件夹target/classes/com/zhaoli/jvm/test01/JvmDemo01.class

    • 此时先进入loadClass()
    • 发现此类没有被加载过
    • 存在父类加载器继续调用loadClass()让父类加载器加载
    • 循环(发现此类没有被加载过)(自动向上依次检查是否已经被加载过)
    • 存在父类加载器继续调用loadClass()(让父类加载器加载)直到没有父类加载器
    • 调用findBootstrapClassOrNull()尝试加载核心类库中的类
    • 不进入if (c == null)(此时是启动类加载器)
    • 不进入if (c == null)(此时是应用类加载器)
    • 最终找到此类(是启动类加载器)找到的

代码为:Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("java.lang.Stirng");

  • 先进入loadClass()=>发现此类被加载过(Stirng提前已经加载过了)=>直接返回

6.2 自定义一个类加载器

public class TestClassLoader extends ClassLoader {

    private File fileObject;

    public TestClassLoader(File fileObject) {
        this.fileObject = fileObject;
    }

    public void setFileObject(File fileObject) {
        this.fileObject = fileObject;
    }

    public File getFileObject() {
        return fileObject;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = getClassFileBytes(this.fileObject);
            System.out.println("进入到了自定义类加载器(TestClassLoader)的 findClass() 来加载 class 类");
            return defineClass(name, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }

    }

    /**
     * 从文件中读取去class文件
     */
    private byte[] getClassFileBytes(File file) throws Exception {
        //采用NIO读取
        FileInputStream fis = new FileInputStream(file);
        FileChannel fileC = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel outC = Channels.newChannel(baos);
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        while (true) {
            int i = fileC.read(buffer);
            if (i == 0 || i == -1) {
                break;
            }
            buffer.flip();
            outC.write(buffer);
            buffer.clear();
        }
        fis.close();
        return baos.toByteArray();
    }
}
public class JvmDemo03 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        TestClassLoader testClassLoader = new TestClassLoader(new File("D:\\code\\Test01.class"));
        Class<?> aClass = testClassLoader.loadClass("com.zhaoli.jvm.test01.Test01");
        Object o = aClass.newInstance();
        System.out.println(o);
    }
}

7. 根据类加载器手写热部署插件

  1. 如何判断一个class 文件是否发生变化? MD5或者操作系统提供api 文件修改时间

  2. 判断该class文件修改日期是否有发生变化,如果有发生变化,则从新使用类加载器读取最新的class文件到内存中。

  3. 如何监听class文件是否有发生变化呢?单独线程

public class ClassFileEntity {

    /**
     * 类的名称
     */
    private String name;

    /**
     * class
     */
    private Class aClass;

    /**
     * 最后被更改的时间
     */
    private long lastModified;

    public ClassFileEntity(String name, long lastModified) {
        this.name = name;
        this.lastModified = lastModified;
    }

    public ClassFileEntity(String name, long lastModified, Class aClass) {
        this.name = name;
        this.lastModified = lastModified;
        this.aClass = aClass;
    }
	//此处省略get()和set()
}
@Slf4j
public class HotDeploymentPlug {
    //存放所有的class文件
    private Map<String, ClassFileEntity> mapClassFiles = new HashMap<>();
    private String path;
    /**
     * 包的名称
     */
    private String packageName = "com.zhaoli.demo.";

    public HotDeploymentPlug(String path) {
        this.path = path;
    }

    public void start() {
        listener();
    }

    /**
     * 监听方法
     */
    public void listener() {
        new Thread(() -> {
            while (true) {
                // 1.读取该文件下
                File files = new File(path);
                File[] tempList = files.listFiles();
                // 2.读取class文件 存入到 mapClassFiles
                for (File file : tempList) {
                    String name = file.getName();
                    if (StringUtils.isEmpty(name)) {
                        continue;
                    }
                    long l = file.lastModified();
                    // 使用类加载器读取该 class
                    String className = packageName + name.replace(".class", "");
                    if (mapClassFiles.containsKey(className)) {
                        // 则比对该class文件 是否被修改
                        ClassFileEntity mapClassFileEntity = mapClassFiles.get(className);
                        if (mapClassFileEntity.getLastModified() != l) {
                            try {
                                mapClassFileEntity.setLastModified(l);
                                TestClassLoader testClassLoader = new TestClassLoader(file);
                                Class<?> aClass = testClassLoader.loadClass(className);
                                Object o = aClass.newInstance();
                                log.info(className + "class文件发生了变化");
                            } catch (Exception e) {
                                log.error("e:{}", e);
                            }
                        }
                    } else {
                        ClassFileEntity newClassFileEntity = new ClassFileEntity(className, l);
                        // 如果不存在 则存入到mapClassFiles集合中
                        mapClassFiles.put(className, newClassFileEntity);
                    }
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        }).start();
    }
}
public static void main(String[] args) {
    HotDeploymentPlug hotDeploymentPlug = new HotDeploymentPlug("D:\\code\\com\\zhaoli\\demo");
    hotDeploymentPlug.listener();
}

8. SPI机制

SPI 机制(Service Provider Interface)是 Java 中的一种服务发现机制,用于在运行时动态加载和使用类库中的服务实现。SPI 机制允许开发者定义服务接口,并让不同的服务提供者提供自己的实现,而不必修改原始代码或重新编译。

SPI 机制的基本原理:

  1. 服务接口定义:开发者首先定义一个服务接口(通常是一个抽象类或接口),这个接口声明了服务应提供的方法。

  2. 服务实现:第三方开发者可以提供这个接口的具体实现类。这些实现类通常放在项目的类路径下的某个包内。

  3. 配置文件:实现类的信息记录在一个名为 services 目录下的文本文件中,文件名是服务接口的全限定名(包名+类名)。每个文件包含一行或多行,每行对应一个实现类的全限定名。

  4. 加载实现类:当应用程序启动时,JVM 会查找并加载这些配置文件,并根据配置文件中的信息实例化相应的服务实现类。

示例:

假设有一个服务接口 com.example.MyService,并且有两个实现类 com.example.impl.MyServiceImpl1com.example.impl.MyServiceImpl2

配置文件内容:

src/main/resources/META-INF/services/com.example.MyService 文件中,内容如下:

com.example.impl.MyServiceImpl1
com.example.impl.MyServiceImpl2

加载实现类:

当应用程序需要使用 MyService 接口的服务时,可以通过以下代码加载实现类:

import java.util.ServiceLoader;

public class ServiceProviderDemo {
    public static void main(String[] args) {
        ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class);
        for (MyService service : loader) {
            System.out.println("Loaded service implementation: " + service.getClass().getName());
            // 调用服务实现的方法
            service.doSomething();
        }
    }
}

SPI 机制的优点:

  1. 扩展性:无需修改源代码即可添加新的服务实现。

  2. 灵活性:可以在运行时动态加载不同的服务实现。

  3. 插件式架构:适合构建插件式的架构,允许第三方扩展核心功能。

SPI 机制的局限性:

  1. 静态加载:SPI 机制在类加载时就会加载所有实现类,无法在运行时动态注册或卸载服务。

  2. 单一实例:SPI 默认为每个服务提供者创建一个单例对象,如果服务实现依赖于外部状态或其他初始化参数,可能需要额外的处理。

  3. 线程安全:如果有多个实现类,SPI 机制需要保证线程安全,否则可能会出现并发问题。

9. 如何绕开双亲委派原则

双亲委派模型(Parent Delegation Model)是 Java 类加载器体系结构的核心组成部分。在这个模型中,每个类加载器负责先将其加载请求委托给父类加载器,只有当父类加载器无法完成加载请求时,才会尝试自己加载。这种机制有助于确保类的一致性和避免类加载冲突。

**绕开双亲委派模型的方法:**绕开双亲委派模型通常意味着你需要自定义类加载器,并且在某些情况下直接加载类而不经过标准的委托流程。以下是一些常见的方法:

1. 自定义类加载器

你可以创建一个自定义的类加载器,覆盖 findClass() 方法,并且在 loadClass() 方法中不调用 super.findClass(),而是直接加载类。这样就可以绕过标准的委托机制。

import java.io.InputStream;

public class CustomClassLoader extends ClassLoader {
    private final String name;
    private final byte[] classData;

    public CustomClassLoader(String name, byte[] classData) {
        this.name = name;
        this.classData = classData;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return defineClass(name, classData, 0, classData.length);
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (name.equals(this.name)) {
            return findClass(name);
        } else {
            return super.loadClass(name); // 这里仍然可以调用父类加载器
        }
    }
    
    // 其他必要的方法,如getResourceAsStream等
}

2. 使用 defineClass() 方法

你可以使用 ClassLoaderdefineClass() 方法来直接定义类,而不是通过标准的类加载流程。这种方式通常用于从字节数组中加载类,例如从网络、磁盘或者其他来源获取的字节码数据。

public class CustomClassLoader extends ClassLoader {
    public Class<?> defineClass(byte[] classData) {
        return defineClass("CustomClassName", classData, 0, classData.length);
    }
}

3. 使用 ClassLoader.defineClass(String name, byte[] b) 方法

这种方法直接使用 ClassLoader 类的静态方法来定义类,这种方式不依赖于类加载器的继承关系。

public class CustomClassLoader extends ClassLoader {
    public Class<?> defineClass(byte[] classData) {
        return defineClass("CustomClassName", classData, 0, classData.length);
    }
}

注意事项:

  1. 安全性:绕过双亲委派模型可能会导致安全性和一致性问题,特别是当涉及到核心类库的加载时。

  2. 兼容性:如果你的类加载逻辑过于复杂,可能会导致与其他框架或库的兼容性问题。

  3. 维护性:自定义类加载器增加了系统的复杂性,可能会使代码更难以维护。

10. 常见的几款java虚拟机

Java 虚拟机(JVM)是 Java 运行时环境的核心组件,它负责执行 Java 字节码。不同的 JVM 实现有不同的特性和适用场景。以下是几种常见的 Java 虚拟机:

  1. Oracle HotSpot VM:HotSpot 是最常用的 Java 虚拟机,由 Sun Microsystems 开发(后来被 Oracle 收购)。HotSpot VM 是 OpenJDK 和 Oracle JDK 的默认 JVM,它具有以下特点:

    • 性能优化:HotSpot 包含了许多性能优化技术,如即时编译(JIT)、垃圾回收(GC)算法等。

    • 开源:HotSpot 是 OpenJDK 项目的一部分,因此它是开源的。

    • 广泛支持:几乎所有的现代 Java 开发环境都支持 HotSpot。

  2. OpenJ9:OpenJ9 是 IBM 开发的一款高性能的 Java 虚拟机,现在由 Eclipse 基金会维护。OpenJ9 作为 IBM 的 Java SE 8 和 Java SE 11 发布版的基础,并且也被用于 Adoptium(之前称为 AdoptOpenJDK)项目中。OpenJ9 的特点包括:

    • 内存效率:OpenJ9 通常具有较低的内存使用。

    • 性能:在某些工作负载下,OpenJ9 表现出色,尤其是那些需要长时间运行的应用程序。

    • 可移植性:OpenJ9 支持多种操作系统和硬件平台。

  3. GraalVM:GraalVM 是 Oracle 开发的一款多语言虚拟机,支持 Java、JavaScript、Python、Ruby 等多种语言。GraalVM 的特点是:

    • 多语言支持:除了 Java,还支持其他动态语言。

    • AOT 编译:支持提前编译(Ahead-of-Time Compilation),可以将 Java 字节码转换为本地代码,提高启动速度和运行时性能。

    • 内存效率:GraalVM 通常具有较好的内存效率。

  4. Zing:Zing 是 Azul Systems 开发的一款企业级 Java 虚拟机,专为大规模、长时间运行的应用程序设计。Zing 的特点包括:

  5. Zulu:Zulu 是 Azul Systems 提供的一款开源 Java 虚拟机实现,基于 OpenJDK。Zulu 支持多种 Java 版本,并且是完全免费和开源的。

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

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

相关文章

Dolma:包含三万亿Token的语言模型预训练研究开放语料库

前言 原论文&#xff1a;Dolma: an Open Corpus of Three Trillion Tokens for Language Model Pretraining Research 摘要 关于训练当前最佳性能语言模型的预训练语料库的信息很少被讨论——商业模型很少详细说明它们的数据&#xff0c;即使是开源模型也往往在没有训练数据…

操作系统 | 学习笔记 | 王道 | 3.2 虚拟内存管理

3.2 虚拟内存管理 3.2.1 虚拟内存的基本概念 传统存储管理方式的特征 传统存储管理方式 连续分配 单一连续分配固定分区分配动态分区分配 非连续分配 基本分页存储管理基本分段存储管理基本段页式存储管理 特征&#xff1a; 一次性&#xff1a; 作业必须一次性全部装入内存后…

『网络游戏』制作提示弹窗UI【03】

将上一章的创建角色界面隐藏 创建一个空节点重命名为DynamicWnd 设置父物体为伸展 钉在中间创建一个Text文本组件 添加动画Animation组件 创建自定义动画Animation动画 点击创建 选择指定文件夹 拖拽至Animation 使用记录动画方式编辑动画首先点击红点录制 在第0帧设置文字透明…

最简单的示例:通过JDBC查询数据

引言 在现代企业级应用开发中&#xff0c;持久层框架&#xff08;如 MyBatis、Hibernate 等&#xff09;极大地简化了数据库操作&#xff0c;提高了开发效率和代码的可维护性。本文将通过一个最简单的示例&#xff0c;演示如何使用 JDBC 连接数据库、执行 SQL 语句以及处理结果…

LabVIEW技术难度最大的程序

在LabVIEW开发中&#xff0c;技术难度最大的程序通常涉及复杂的系统架构、高精度的控制要求、大量数据处理&#xff0c;以及跨平台或多硬件设备的集成。以下是几类具有高技术难度的LabVIEW程序&#xff1a; 1. 高精度实时控制系统 LabVIEW中涉及高精度实时控制的系统程序&…

金纳米星“融入”水凝胶,原位生长的奥秘,应用前景的探索

大家好&#xff01;今天来了解一项在三维水凝胶表面生长金纳米星的研究——《Growing Gold Nanostars on 3D Hydrogel Surfaces》发表于《Chemistry of Materials》。水凝胶在生物医学等诸多领域有着重要应用&#xff0c;而金纳米星具有独特的光学性质。这项研究通过原位合成的…

【含开题报告+文档+PPT+源码】基于SpringBoot的校园互助平台设计与实现【包运行成功】

开题报告 现代大学校园是一个多样化且充满活力的环境&#xff0c;拥有来自不同文化和地域的学生。然而&#xff0c;这种多样性也伴随着一系列挑战&#xff0c;包括学业压力、心理健康问题、社交挑战以及适应新环境的困难。面对这些挑战&#xff0c;学生常常感到信息获取困难和…

redis——哨兵机制

redis中提供了哨兵&#xff08;Sentinel&#xff09;机制来实现主从集群的自动故障恢复。 主从复制是实现redis高可用性的基石&#xff0c;从节点宕机时我们仍然可以将请求发送给主节点或者其他从节点&#xff0c;而当主节点宕机的时候&#xff0c;无法执行写操作&#xff0c;无…

Maven、Git

1. Maven 安装 &#xff08;2024.6.23&#xff09;最新版MAVEN的安装和配置教程&#xff08;超详细&#xff09;_maven安装-CSDN博客 2. 配置IDEA和Maven的关联 1. 单个关联 &#xff08;每新建一个项目都要配一次&#xff0c;不推荐&#xff09; 配置maven home path&#…

【Linux】文件IO系统[ 库函数 ]封装了[ 系统调用 ] +【区分文件结构体FILE和file与files_srtuct表】(读写接口盘点与介绍)

前言 大家好吖&#xff0c;欢迎来到 YY 滴Linux系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Lin…

Unity中实现预制体自动巡逻与攻击敌人的完整实现指南

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

C语言 | Leetcode C语言题解之第463题岛屿的周长

题目&#xff1a; 题解&#xff1a; const int dx[4] {0, 1, 0, -1}; const int dy[4] {1, 0, -1, 0};int dfs(int x, int y, int** grid, int n, int m) {if (x < 0 || x > n || y < 0 || y > m || grid[x][y] 0) {return 1;}if (grid[x][y] 2) {return 0;}g…

读数据工程之道:设计和构建健壮的数据系统02数据工程师

1. 背景和技能 1.1. 数据工程是一个快速发展的领域&#xff0c;关于如何成为一名数据工程师仍然存在很多问题 1.2. 进入数据工程领域的人在教育、职业和技能方面有着不同的背景 1.2.1. 每个进入该领域的人都应该投入大量的时间进行自学 1.3. 从一个邻近的领域转到数据工程是…

【STM32开发之寄存器版】(七)-PWM脉冲宽度调制

一、前言 PWM简介 PWM&#xff08;脉宽调制&#xff09;是一种通过调节信号的脉冲宽度来控制功率输出的技术。其基本原理是保持固定频率的信号&#xff0c;将其高电平和低电平的持续时间调整&#xff0c;达到控制平均功率的目的。应用方面&#xff0c;PWM广泛用于电机控制、LED…

LeetCode 3310. 移除可疑的方法

LeetCode 3310. 移除可疑的方法 你正在维护一个项目&#xff0c;该项目有 n 个方法&#xff0c;编号从 0 到 n - 1。 给你两个整数 n 和 k&#xff0c;以及一个二维整数数组 invocations&#xff0c;其中 invocations[i] [ai, bi] 表示方法 ai 调用了方法 bi。 已知如果方法 k…

Qt创建插件及使用

本文使用“Qt Creator 6.0.1”和“Qt 6.2.2”完成插件创建及使用&#xff0c;主要有如下步骤&#xff1a;&#xff08;1&#xff09;创建子目录项目MyProject&#xff1b;&#xff08;2&#xff09;在子目录项目中创建应用程序项目MyApp&#xff1b;&#xff08;3&#xff09;在…

python实现RC4加解密算法

RC4算法 一、算法介绍1.1 背景1.2 密钥调度算法(KSA)1.3 伪随机生成算法(PRGA) 二、代码实现三、演示效果 一、算法介绍 1.1 背景 RC4算法是由Ron Rivest在1987年为RSA数据安全公司设计的一种流密码算法&#xff0c;其安全性主要依赖于其密钥流的随机性和不可预测性。该算法因…

Spring Cloud 3.x 集成admin快速入门Demo

1.什么是Spring Boot Admin&#xff1f; Spring Boot Admin(SBA)是一个社区开源项目&#xff0c;用于管理和监视Spring Boot 应用程序&#xff0c;它提供详细的健康(Health)信息、内存信息、JVM 系统和环境属性、垃圾回收信息、日志设置和查看、定时任务查看、Spring Boot 缓存…

Steam Deck掌机可装“黑苹果” 开发者成功安装macOS 15 Sequoia

在Steam Deck掌机上运行Windows 11相对轻松&#xff0c;但要让其成功搭载“黑苹果”系统则颇具挑战性。近日&#xff0c;有博主勇于尝试&#xff0c;将macOS 15 Sequoia安装到了Steam Deck上。 开发者kaitlyn在X平台上分享道&#xff1a;“在朋友们的鼎力相助下&#xff0c;我…