类加载子系统之类的生命周期(待完善)

0、前言

文中大量图片来源于 B站 黑马程序员

0.1、类加载子系统在 JVM 中的位置

image-20240429141045222

类加载器负责的事情是:加载、链接、解析

0.2、与类的生命周期相关的虚拟机参数

参数描述
-XX:+TraceClassLoading打印出加载且初始化的类

1、类的生命周期

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

堆上的变量在分配空间的时候隐式设置默认初始值(广义0),其中类变量在准备阶段(Preparation)分配空间,成员变量在使用阶段(Using)分配空间

1.1、加载阶段(懒加载)

懒加载的含义是:并不会加载 jar 包中所有的字节码,使用到才会进行加载

加载阶段流程:

  1. 通过类的全限定名从某个源位置获取定义此类的二进制字节流(内存)
  2. 这个字节流被解析转换为方法区的数据结构(InstanceKlass)
  3. 在堆空间中生成一个代表这个类的 java.lang.Class 对象,java.lang.Class 对象 和 InstanceKlass 对象互相指向。作为方法区中这个类的各种操作的访问入口

static 静态字段在 JDK 8 之后和 java.lang.Class 对象存储在一起,即存放在堆空间中
在这里插入图片描述

什么是 InstanceKlass

InstanceKlass 是 Java 类在 JVM 中的一个快照,JVM 将从字节码文件中解析出来的常量池,类字段,类方法等信息存储到 InstanceKlass 中,这样 JVM 在运行期便能通过 InstanceKlass 来获取Java类的任意信息,能够对Java类的成员变量进行遍历,也能进行Java方法的调用。这也是Java反射机制的基础,不需要创建对象,就可以查看加载类中的方法,属性等等信息。

Class 对象由 class 字节码 + ClassLoader 共同决定,不同的 ClassLoader 加载同一个 class 字节码得到不同的 Class 对象,即 class 字节码不能够唯一确定 Class 对象。

1.2、链接阶段

子阶段描述
验证校验魔数、版本号等
准备为类变量(static)分配内存空间,并设置默认值(0)
解析将符号引用处理为直接引用

1.3、初始化阶段

判断一个自定义的类是否被初始化的方法:在

其余见下面的测试案例

1.4、使用阶段

分为主动使用和被动使用两大类,二者区别在于被动使用的情况下,类只会进行加载而不会进行初始化。
在这里插入图片描述

1.5、卸载阶段

和 GC 垃圾回收相关

2、用于理解类生命周期的测试案例

2.1、不考虑父子类继承的情况

案例一:认识 <clinit><init>

Java 源代码

public class ClassLifeCycleTest01 {

    public ClassLifeCycleTest01() {
        System.out.println("<init>...2");
    }


    {
        // 在字节码层面,这些非静态代码块最终被添加到构造函数的最前面
        System.out.println("<init>...1");
    }

    static {
        System.out.println("<clinit>...");
    }


    public static void main(String[] args) {
        System.out.println("ClassLifeCycleTest01 main...");
        new ClassLifeCycleTest01();
        new ClassLifeCycleTest01();
    }
}

字节码

<init> 方法的字节码

// 成员方法的第一个形参是this(从局部变量表可知),将this压入操作数栈
0 aload_0

// 调用父类(Object)的<init>方法,即构造器方法中隐藏在首行的super()
1 invokespecial #1 <java/lang/Object.<init> : ()V>

// System.out.println("<init>...1"),先执行构造器方法外面的代码
4 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
7 ldc #3 <<init>...1>
9 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

// System.out.println("<init>...2"),再执行构造器方法里面的代码
12 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
15 ldc #5 <<init>...2>
17 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

20 return

输出结果

<clinit>...
ClassLifeCycleTest01 main...
<init>...1
<init>...2
<init>...1
<init>...2

总结

  • <init> 方法(实例对象初始化)的逻辑:
    1. 构造器方法作为入口
    2. 先执行super()
    3. 再执行构造方法外部的代码逻辑(顺序拼接)
    4. 最后执行构造方法内部的代码逻辑
  • <clinit> 方法是存在线程安全问题的,但虚拟机会对这个过程加锁,不需要程序员处理

案例二:强化理解 <clinit><init> 的生成逻辑

Java 源代码

/**
 * 目的:通过一些类变量或成员变量的赋值,进一步理解类生命周期的过程

 * 1. 在变量声明之前的代码块中,该变量只可以作为右值表达式,而不能作为左值

 * 2. 等价形式为:
 * 2.1、将变量声明在类的最前面,初始化为0,
 * 2.2、然后按照再将显式赋值和代码块赋值按照出现顺序,整合为一个init方法或clinit方法
 */
public class ClassLifeCycleTest02 {

    // 变量classVar01定义在静态代码块之前
    static int classVar01;

    static {
    	System.out.println("ClassLifeCycleTest02 clinit ...");
        classVar01 = 20;
        // System.out.println(classVar01);//正常

        classVar02 = 10;
        // classVar02 = classVar01 + 1; //正常,classVar02可以作为右值
        // classVar02 = classVar02 + 1; //异常,classVar01不可以作为左值
        // System.out.println(classVar02);//异常
    }

    // 变量classVar02定义在静态代码块之后
    static int classVar02 = 100;


    public ClassLifeCycleTest02() {
        System.out.println("ClassLifeCycleTest02 constructor ...");
        instanceVar = 30;
    }

    {
        System.out.println("ClassLifeCycleTest02 init ...");
        instanceVar = 10;
        // instanceVar = instanceVar + 2;//异常
        // System.out.println(instanceVar);//异常
    }

    // instanceVar的值变化过程: 0->10->20->30
    private int instanceVar = 20;


    public static void main(String[] args) {
        int var01 = ClassLifeCycleTest02.classVar01;
        int var02 = ClassLifeCycleTest02.classVar02;
        System.out.println(var01);
        System.out.println(var02);


        ClassLifeCycleTest02 demo = new ClassLifeCycleTest02();
        int var = demo.instanceVar;
        System.out.println(var);
    }
}

字节码

<clinit> 方法的字节码

0 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
3 ldc #12 <ClassLifeCycleTest02 clinit ...>
5 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

//classVar01 = 20
8 bipush 20
10 putstatic #7 <org/example/lifecycle/ClassLifeCycleTest02.classVar01 : I>

//classVar02 = 10
13 bipush 10
15 putstatic #8 <org/example/lifecycle/ClassLifeCycleTest02.classVar02 : I>

// classVar02 = 100
18 bipush 100
20 putstatic #8 <org/example/lifecycle/ClassLifeCycleTest02.classVar02 : I>

23 return

<init> 方法的字节码

//1、将this压入操作数栈
 0 aload_0
 
 //2、调用父类的<init>方法,这里父类是Object
 1 invokespecial #1 <java/lang/Object.<init> : ()V>
 
//3、输出字符串
4 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
7 ldc #3 <ClassLifeCycleTest02 init ...>
9 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

//(成员变量在堆上分配空间时会设置默认初始值0,无法通过字节码体现出来)
//4、this.instanceVar = 10
12 aload_0
13 bipush 10
15 putfield #5 <org/example/lifecycle/ClassLifeCycleTest02.instanceVar : I>

//5、this.instanceVar = 20
18 aload_0
19 bipush 20
21 putfield #5 <org/example/lifecycle/ClassLifeCycleTest02.instanceVar : I>

//6、输出字符串
24 getstatic #2 <java/lang/System.out : Ljava/io/PrintStream;>
27 ldc #6 <ClassLifeCycleTest02 constructor ...>
29 invokevirtual #4 <java/io/PrintStream.println : (Ljava/lang/String;)V>

//7、this.instanceVar = 30
32 aload_0
33 bipush 30
35 putfield #5 <org/example/lifecycle/ClassLifeCycleTest02.instanceVar : I>

38 return

输出结果

ClassLifeCycleTest02 clinit ...
20
100
ClassLifeCycleTest02 init ...
ClassLifeCycleTest02 constructor ...
30

总结

  • super() 调用的不是父类的构造器,而是父类的 <init> 方法

  • <clinit> 方法和 <init> 方法的生成逻辑是相同的,区别在于前者针对类变量,后者针对成员变量

  • 在变量声明之前的代码块中,如果出现了该变量,那么该变量只能够作为右值表达式,而不能作为左值表达式,例如 classVar02instanceVar 变量

  • 针对下面的代码块,可以进行等价处理

    static{
    	classVar = 10;
    }
    static int classVar = 20;
    
    // 变量声明提前
    static int classVar = 0;
    static{
    	// 顺序添加原来代码块和显式赋值的过程
    	classVar = 10;
    	classVar = 20;
    }
    

案例三:验证 <clinit> 方法会被 JVM 加锁

思路:让多个线程并发创建对象,让获取到 <clinit> 方法执行锁的线程在 <clinit> 内部陷入死循环(阻塞),观察其它线程是否能够进入到 <clinit> 方法中

 <dependency>
     <groupId>org.slf4j</groupId>
     <artifactId>slf4j-api</artifactId>
     <version>2.0.7</version>
 </dependency>
 <dependency>
     <groupId>ch.qos.logback</groupId>
     <artifactId>logback-classic</artifactId>
     <version>1.3.5</version>
 </dependency>
 <dependency>
     <groupId>org.projectlombok</groupId>
     <artifactId>lombok</artifactId>
     <version>1.18.26</version>
     <exclusions>
         <exclusion>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </exclusion>
     </exclusions>
 </dependency>
/**
 * 验证 clinit 方法会被添加上锁
 */
@Slf4j
public class ClassLifeCycleTest07 {
    static class Demo {
        static {
            // 为了通过语法检查, 需要添加if(true){}
            if (true) {
            	// 使用log.error()方便区分
                log.error("<clinit>()...");
                
                // 为了将线程阻塞在<clinit>()类初始化过程
                // 对比实验就是分别观察while(true){}被注释和未被注释的情况
                while (true) {

                }
            }
        }
    }

    private static final int NUM = 100;

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            // 用来体现并发环境
            log.info("begin: " + System.currentTimeMillis());

            // 触发 Demo 类的初始化
            Demo demo = new Demo();

            // 用来证明并发环境下,一个线程的Demo的初始化没有完成,锁便不会释放,其它线程无法获取到锁也会被阻塞
            log.info("end: " + System.currentTimeMillis());
        };


        List<Thread> threadList = new ArrayList<>(NUM);
        for (int i = 0; i < NUM; i++) {
            threadList.add(new Thread(runnable, "thread_" + i));
        }

        // 启动并发线程
        for (int i = 0; i < NUM; i++) {
            threadList.get(i).start();
        }

        // 避免主线程退出 
        for (int i = 0; i < NUM; i++) {
            threadList.get(i).join();
        }
    }
}

测试结果

对比项实验结果
while(true){} 被注释所有线程执行完成,程序正常退出。仅有一个线程输出 <clinit> 方法中的内容
while(true){} 未被注释程序被阻塞,没有任何一个线程输出 end 时间,同样仅有一个线程输出 <clinit> 方法中的内容

在这里插入图片描述

在这里插入图片描述

2.2、考虑父子类继承的情况

案例四:隐藏的 super() 就是调用父类的 <init> 方法

/**
 * 特别事项:和Main进行对比,一种类的被动使用导致类没有执行clinit初始化
 */
public class ClassLifeCycleTest03 extends ClassLifeCycleTest02 {

    // 变量classVar01定义在静态代码块之前
    static int classVar03;

    static {
        System.out.println("ClassLifeCycleTest03 clinit ...");
        classVar03 = 20;
    }


    private int instanceVar = -20;

    {
        System.out.println("ClassLifeCycleTest03 init ...");
    }

    public ClassLifeCycleTest03() {
        System.out.println("ClassLifeCycleTest03 constructor ...");
        instanceVar = -30;
    }


    public static void main(String[] args) {
        int var01 = ClassLifeCycleTest03.classVar01;
        System.out.println(var01);

        int var02 = ClassLifeCycleTest03.classVar02;
        System.out.println(var02);

        ClassLifeCycleTest03 demo = new ClassLifeCycleTest03();
        int var = demo.instanceVar;
        System.out.println(var);
    }
}

字节码

<init> 的字节码

0 aload_0

// 这里可以清晰看到调用父类的<init>方法,其它部分在之前的案例中已经介绍
1 invokespecial #1 <org/example/lifecycle/ClassLifeCycleTest02.<init> : ()V>

4 aload_0
5 bipush -20
7 putfield #2 <org/example/lifecycle/ClassLifeCycleTest03.instanceVar : I>

10 aload_0
11 bipush -30
13 putfield #2 <org/example/lifecycle/ClassLifeCycleTest03.instanceVar : I>

16 return

main 的字节码

// 注意这里的类变量,是ClassLifeCycleTest03.classVar01
0 getstatic #3 <org/example/lifecycle/ClassLifeCycleTest03.classVar01 : I>
3 istore_1

4 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
7 iload_1
8 invokevirtual #5 <java/io/PrintStream.println : (I)V>

11 getstatic #6 <org/example/lifecycle/ClassLifeCycleTest03.classVar02 : I>
14 istore_2

15 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
18 iload_2
19 invokevirtual #5 <java/io/PrintStream.println : (I)V>

// ClassLifeCycleTest03 demo = new ClassLifeCycleTest03()的字节码
// new:分配对象空间,设置广义0值,并将对象地址压入操作数栈
// dup:复制栈顶元素
// invokespecial:调用父类的<init>方法(属于字节码层面的方法)
// 将栈顶元素赋值给demo局部变量
22 new #7 <org/example/lifecycle/ClassLifeCycleTest03>
25 dup
26 invokespecial #8 <org/example/lifecycle/ClassLifeCycleTest03.<init> : ()V>
29 astore_3

30 aload_3
31 getfield #2 <org/example/lifecycle/ClassLifeCycleTest03.instanceVar : I>
34 istore 4

36 getstatic #4 <java/lang/System.out : Ljava/io/PrintStream;>
39 iload 4
41 invokevirtual #5 <java/io/PrintStream.println : (I)V>

44 return

输出结果

ClassLifeCycleTest02 clinit ...
ClassLifeCycleTest03 clinit ...
20
100
ClassLifeCycleTest02 init ...
ClassLifeCycleTest02 constructor ...
ClassLifeCycleTest03 init ...
ClassLifeCycleTest03 constructor ...
-30

总结

  • 父类优先于子类(初始化、类加载)

案例五:类的被动使用(调用父类的静态变量)

在案例三中,我们直接在 ClassLifeCycleTest03 这个类中的 main 方法进行测试,而 main 方法被调用会默认去加载当前类,因此会丢失掉一些现象。因此,我们额外定义一个 Main 类来作为测试入口

import org.junit.jupiter.api.Test;

public class Main {

    /**
     * 和ClassLifeCycleTest03类中的main()方法进行对比
     */
    @Test
    public void compareClassLifeCycleTest03Test01() {
        int var01 = ClassLifeCycleTest03.classVar01;
        System.out.println(var01);

        int var02 = ClassLifeCycleTest03.classVar02;
        System.out.println(var02);
    }


    @Test
    public void compareClassLifeCycleTest03Test02() {
        int var03 = ClassLifeCycleTest03.classVar03;
        System.out.println(var03);

    }

}

输出结果

ClassLifeCycleTest02 clinit ...
20
100
ClassLifeCycleTest02 clinit ...
ClassLifeCycleTest03 clinit ...
20

总结

类(Class)类变量(static)调用示例类加载(Loading)类初始化(Initialization)
子类(ClassLifeCycleTest03)子类(classVar03)ClassLifeCycleTest03.classVar03父类、子类父类、子类
子类(ClassLifeCycleTest03)父类(classVar02)ClassLifeCycleTest03.classVar02父类、子类父类
父类(ClassLifeCycleTest02)父类(classVar02)ClassLifeCycleTest01.classVar02父类父类

调用静态方法同上

注:可以通过添加虚拟机参数 -XX:+TraceClassLoading 查看已经加载的类,再通过 Ctrl + f 来搜索某个类是否被加载

2.3、考虑常量的编译期优化

代码中所有对常量的引用,都会在编译后直接被替换为相应的字面量

在 Java 中什么是常量?

  • 从字节码角度来看,含有 ConstantValue 信息的字段是常量
  • 从 Java 代码角度来看,使用 static final 修饰,且右侧表达式中只包含字面量(1、1.0、“hello” 等)或常量
    // 常量:static final修饰,右侧只包含字面量
    static final int NUM_1 = 100;
    static final int NUM_2 = 200;
    
    // 常量:static final修饰,右侧只包含常量
    static final int SUM = NUM_1 + NUM_2;
    
    // 字符串同理
    static final String S_1 = "HELLO";
    static final String S_2 = "WORLD";
    static final String S_3 = S_1 + S_2;
    
    // 不是常量,右侧出现new对象,这就是static final修饰的变量不一定是常量的原因。
    // 其它类型的引用变量必定是new出来的对象,而String类型却有两种赋值方式
    static final String S_4 = new String(S_1 + S_2);
    

Java 源代码

/**
 * 常量的编译期优化
 * 
 * 可以通过反编译看出Demo.VAR被替换为字面量"Hello World",因此不会触发 Demo 的加载和初始化
 */
public class ClassLifeCycleTest04 {

    static class Demo {
        private static final String VAR = "Hello World!";

        static {
            System.out.println("Demo clinit ...");
        }
    }

    public static void main(String[] args) {
        // 在编译期便完成对常量的替换,所以不会加载 Demo.class,更不会初始化。
        // 注意这里 main方法并不是 Demo 类的方法
        System.out.println(Demo.VAR);
    }
}

反编译后的 Java 代码

public class ClassLifeCycleTest04 {
    public ClassLifeCycleTest04() {
    }

    public static void main(String[] args) {
    	// 可以得出结论,在编译后的字节码文件中,Demo.VAR直接被替换为"Hello World"字面量
        System.out.println("Hello World!");
    }

    static class Demo {
        private static final String VAR = "Hello World!";

        Demo() {
        }

        static {
            System.out.println("Demo clinit ...");
        }
    }
}

2.4、验证类变量(static)在准备阶段(Preparation)设置默认值

思路:对类变量只进行声明,而不显式赋值。观察字节码中是否有 <clinit> 方法。

public class ClassLifeCycleTest06 {
    static int num;

    public static void main(String[] args) {
        System.out.println(num);
    }
}

2.5、使用 HSDB 工具来判断 static 变量的存储位置

(TODO:添加过程细节)

注意:inspect 找到的是 InstanceKlass 对象
在这里插入图片描述

在这里插入图片描述

3、补充

  1. 静态变量(static)的存放位置
    • JDK 7 及之前:方法区(InstanceKlass)
    • JDK 8 及之后:堆(java.lang.Class)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LiveCD镜像文件的定制化

最近想要定制化一款属于自己的LiveCD镜像文件&#xff0c;并且里边封装好所需要的软件程序&#xff0c;本文将会记录具体的操作步骤&#xff0c;供有需要的人参考借鉴。 环境说明&#xff1a; 环境配置说明配置参数编码环境Centos7.9LiveCD文件CentOS-7-livecd-x86_64.iso 附…

基础安全:CSRF攻击原理与防范

CSRF的概念 CSRF(Cross-Site Request Forgery)中文名为“跨站请求伪造”。这是一种常见的网络攻击手段,攻击者通过构造恶意请求,诱骗已登录的合法用户在不知情的情况下执行非本意的操作。这种攻击方式利用了Web应用程序中用户身份验证的漏洞,即浏览器在用户完成登录后会自…

2024 华东杯高校数学建模邀请赛(B题)| 自动驾驶转弯问题 | 建模秘籍文章代码思路大全

铛铛&#xff01;小秘籍来咯&#xff01; 小秘籍团队独辟蹊径&#xff0c;以偏微分方程&#xff0c;中心驱动等强大工具&#xff0c;构建了这一题的详细解答哦&#xff01; 为大家量身打造创新解决方案。小秘籍团队&#xff0c;始终引领着建模问题求解的风潮。 抓紧小秘籍&…

计算机毕业设计python_django宠物领养系统z6rfy

本宠物领养系统主要包括两大功能模块&#xff0c;即管理员模块、用户模块。下面将对这两个大功能进行具体功能需求分析。 &#xff08;1&#xff09;管理员&#xff1a;管理员登录后主要功能包括个人中心、用户管理、送养宠物管理、地区类型管理、失信黑名单管理、申请领养管理…

第12章 软件测试基础(第二部分)

四、测试策略 &#xff08;一&#xff09;概念 测试策略指在一定软件测试标准、测试规范的指导下&#xff0c;依据测试项目的特定环境而规定测试原则和方法的一个集合。 &#xff08;二&#xff09;方法 基于分析的策略基于模型的策略基于标准规范的策略基于自动化的回归测…

2023蓝桥杯学习与刷题建议

前两天天给你们组了队&#xff0c;经过两天发现各位都有这样的问题&#xff1a; 不愿意交流。小组不会规划刷题计划。可能是因为没有人带头和具体刷题计划&#xff0c;所以都处于迷茫&#xff0c;不交流、不刷题。还有可能是大家都不认识&#xff0c;都比较羞涩。同时也有我个…

冯喜运:5.1国际黄金今日行情还会跌吗?黄金原油独家行情分析

【黄金消息面分析】&#xff1a;周三(5月1日)亚市盘中&#xff0c;现货黄金在昨日暴跌近50美元后继续承压&#xff0c;金价目前位于2285美元/盎司附近。FXStreet首席分析师Valeria Bednarik撰文&#xff0c;对黄金技术前景进行分析。Bednarik写道&#xff0c;现货黄金周二面临强…

主营产品需求旺盛,三清互联业绩稳定提升

在全球能源结构转型的大背景下&#xff0c;作为技术创新型的国内知名电力物联网关键技术、核心智能设备和服务提供商&#xff0c;三清互联以卓越技术在业内取得了显著的成绩。公司的主营业务收入主要集中在智能配电终端、智能中高压电气设备和低压电气成套设备三大类产品上&…

Linux内核之虚拟内存区域页分配:alloc_pages_vma用法实例(六十四)

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

ESP32 和 Arduino 之间建立蓝牙连接

ESP32 和 Arduino Uno 可以通过蓝牙进行通信。为此&#xff0c;您必须使用与 Arduino 兼容的蓝牙模块。我们将使用 HC-05&#xff08;06&#xff09; 蓝牙模块。 连接Arduino Uno和HC-05蓝牙模块 将 HC-05 蓝牙模块连接到 Arduino 板。将模块的VCC、GND、RX、TX引脚连接到Ard…

1.4 Java全栈开发前端+后端(全栈工程师进阶之路)-前置课程java基础语法、java面向对象编程

Java核心语法&#xff1a; 对象&#xff1a;对象是类的一个实例&#xff08;对象不是找个女朋友&#xff09;&#xff0c;有状态和行为。例如&#xff0c;一条狗是一个对象&#xff0c;它的状态有&#xff1a;颜色、名字、品种&#xff1b;行为有&#xff1a;摇尾巴、叫、吃等。…

目前全球各类遥感卫星详细介绍

一、高分一号 高分一号&#xff08;GF-1&#xff09;是中国高分辨率对地观测系统重大专项&#xff08;简称高分专项&#xff09;的第一颗卫星。“高分专项”于2010年5月全面启动&#xff0c;计划到2020年建成中国自主的陆地、大气和海洋观测系统。 高分一号&#xff08;GF-1&…

Open Life Science AI (OLSA)

文章目录 关于 Open Life Science ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/16d93b633c8442cc93ee6433ceea38e9.png 500x)关于 Open Life Science AI 关于 Open Life Science Open Life Science 是一个非营利组织。 官网&#xff1a; https://openlifesci…

实习面试算法准备之图论

这里写目录标题 1 基础内容1.1 图的表示1.2图的遍历 2 例题2.1 所有可能的路径2.2 课程表&#xff08;环检测算法&#xff09;2.2.1 环检测算法 DFS版2.2.2 环检测算法 BFS版 2.3 课程表 II &#xff08;拓扑排序算法&#xff09;2.3.1 拓扑排序 DFS版 1 基础内容 图没啥高深的…

【分布式通信】NPKit,NCCL的Profiling工具

NPKit介绍 NPKit (Networking Profiling Kit) is a profiling framework designed for popular collective communication libraries (CCLs), including Microsoft MSCCL, NVIDIA NCCL and AMD RCCL. It enables users to insert customized profiling events into different C…

Java项目:88 springboot104学生网上请假系统设计与实现

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本学生网上请假系统管理员&#xff0c;教师&#xff0c;学生。 管理员功能有个人中心&#xff0c;学生管理&#xff0c;教师管理&#xff0c;班级信息…

程序员与土地的关系

目录 一、土地对人类的重要性 二、程序员与土地的关系 二、程序员如何利用GIS技术改变土地管理效率&#xff1f; 四、GIS技术有哪些运用&#xff1f; 五、shapely库计算多边形面积的例子 一、土地对人类的重要性 土地资源对人类是至关重要的。土地是人类赖…

力扣HOT100 - 131. 分割回文串

解题思路&#xff1a; class Solution {List<List<String>> res new ArrayList<>();List<String> pathnew ArrayList<>();public List<List<String>> partition(String s) {backtrack(s,0);return res;}public void backtrack(Str…

Windows下面源码安装PostgreSQL

目录 一、环境&#xff1a; 二、安装MSYS2 三、安装PG 四、初始化数据库 五、启停数据库 六、调试PG 平时我们在LINUX下&#xff0c;使用源码安装PG的比较多&#xff0c;但在WINDOWS下安装&#xff0c;一般是使用二机制安装包来安装&#xff0c;能否使用源码来安装呢&…

力扣82-链表、迭代 的思考

题目解读 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 两个示范 思考 返回链表&#xff1a;返回更新链表后的头结点&#xff1b; 更新链表&#xff1a;判断重复元素&#xff0c;改变指针…