学习笔记-JAVAJVM-JVM的基本结构及概念

news2024/9/20 8:11:46

申明:文章内容是本人学习极客时间课程所写,文字和图片基本来源于课程资料,在某些地方会插入一点自己的理解,未用于商业用途,侵删。
原资料地址:课程资料

什么是JVM

原文连接:
原文连接
JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相关的信息,使得Java程序只需要生成在Java虚拟机上运行的目标代码(字节码),就可在多种平台上不加修改的运行,这也是Java能够“一次编译,到处运行的”原因。

在这里插入图片描述

JVM的主要板块

类加载子系统

类加载器的定义(深入理解JVM原话):
通过一个类的全限定名称来描述此类的二进制字节流,将这个动作放到Java虚拟机外部去实现,以便让应用程序自己据欸的那个如何区获取所需要的类,实现这个动作的代码模快称为类加载器。
1 JVM 的类加载是通过ClassLoader及子类来完成的,通常来说有下面几种类加载器:

  • 启动类加载器(Bootstrap ClassLoader)
    负责加载JAVA_ HOME\lib目录的或通过-Xbootclasspath参数指定路径中的且被虚拟机认可(rt.jar) 的类库
  • 扩展类加载器(Extension ClassLoader)
    负责加载JAVA_ _HOME\lib\ext目录或通过java.ext.dirs系统变量指定路径中的类库
  • 应用程序类加载器(Application ClassLoader)
    负责加载用户路径classpath上的类库
  • 自定义类加载器
    加载应用之外的类文件

2 类加载执行顺序
检查顺序是自底向上:加载过程中会先检查类是否被已加载,从Custom到BootStrap逐层检查,只要某个类加载器已加载就视为此类已加载,保证此类所有ClassLoader只加载一 次.
在这里插入图片描述
3 加载时机(检查时自底向上,加载时自顶向下)

1-遇到new、getStatic、 putStatic、 invokeStatic四条指令时。
2-使用java.lang.reflect包方法时,对类进行反射调用。
3-初始化-个类时,发现其父类还没初始化,要先初始化其父类。
4-当虚拟机启动时,用户需要指定–个主类Main,需要先将主类加载。

4 一个类的一生
在这里插入图片描述
5 类加载所做的事情
在类加载的过程中,做了如下几件事情:
1 根据全限定名称加载二进制字节流。
2 将字节流转换为数据结构
3 创建字节码class的对象

6 类加载途径
➢01-jar/war
➢02-jsp生成的class
➢03-数据库中的二进制字节流
➢04-网络中的二进制字节流
➢05-动态代理生成的二进制字节流

在这里插入图片描述
自定义类加载器案例heiloworld

public class CustomClassLoader extends ClassLoader {
    private final String classPath;
    public CustomClassLoader(String classPath) {
        this.classPath = classPath;
    }
    public static void main(String[] args) {
        CustomClassLoader customClassLoader = new CustomClassLoader("E:\\lesson-one\\lesson-one\\src\\lib");
        try {
            Class<?> c = customClassLoader.loadClass("com.learn.lessonone.dto.Test");
            if (c != null) {
                Object o = c.newInstance();
                Method say = c.getMethod("say", null);
                say.invoke(o, null);
                System.out.println(c.getClassLoader().toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        try {
            byte[] calsssDate = getData(name);
            if (calsssDate != null) {
                return defineClass(name, calsssDate, 0, calsssDate.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return super.loadClass(name, resolve);
    }
    @Override
    protected Object getClassLoadingLock(String className) {
        return super.getClassLoadingLock(className);
    }
    public byte[] getData(String className) {
        String path = classPath + File.separator + className.replace(".", File.separator) + ".class";
        try (InputStream in = new FileInputStream(path);
             ByteArrayOutputStream out = new ByteArrayOutputStream()
        ) {
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = in.read(buffer)) != -1) {
                out.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

类加载机制双亲委派

1-什么是双亲委派?
当一个类加载器收到类加载任务,会先交给其父类加载器去完成,因此最终加载任务都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载任务时,才会尝试执行加载任务。

2-为什么需要双亲委派呢?
双亲委派其实是一种规范,它一定程度上能够保证安全性。就比如我们尝试用的Object,String类,如果我们没有委托父类进行加载,每个子类进行加载,如果这个时候我们自己写了一个类的全限定名称和系统的一模一样,这个时候它加载的就是我们写的类,这样就会导致我们使用的不是Java给我门头提供的Object类,从而程序完全乱套。
为什么双亲委派能够解决这个问题呢,因为我们会一直委托父类去加载,加载Object这种类最终都是由BootstrapClassLoader来加载,它保证了加载的一定是Java提供给我们的Object类,因为BootstrapClassLoader就是负责加载这类内置类的。

3-为什么还需要破坏双亲委派?
在实际应用中,双亲委派解决了Java 基础类统一加载的问题,但是却存在着缺陷。JDK中的基础类作为典型的API被用户调用,但是也存在API调用用户代码的情况,JNDI,SPI,这种情况就需要打破双亲委派模式。
例如:数据库驱动DriverManager。以Driver接口为例,Driver接口定义在]DK中,其实现由各个数据库的服务商来提供由系统类加载器加载。这个时候就需要启动类加载器来委托子类来加载Driver实现,这就破坏了双亲委派。

4-如何破坏双亲委派?
方式一:重写ClassLoader的loadClass方法
方式二:SPl,类委托自类加载器加载Class,以数据库驱动DriverManager为例

自己的理解:
自定义的类始最终都是由ApplicationClassLoader或自定义类加载器加载比如我写了一个CustomObject 继承了Object这个类 并定义了这个类字段结构。在执行加载的时候,CustomObject一直向上委托,最后发现BootStrapClassLoaer加载不了,然后又自顶向下回溯ApplicationClassLoader来加载CustomObject这个类,但是Object BootStrapClassLoaer是能加载的,在回到这里之前Oject已将被加载过了。
类加载的源码 可以类的加载时委托自己的父亲进行加载

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 {
                       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 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;
       }
   }

运行时数据区概述

按照线程使用情况和职责分成两大类:
1)线程独享(程序执行区)
虚拟机栈,本地方法栈,程序计数器
不需要垃圾回收

2)线程共享(数据存储区域)
堆和方法区
存储类的静态数据和对象数据
需要垃圾回收
在这里插入图片描述

1)堆
是虚拟机所管理的内存中最大的一块,在虚拟器启动时创建,被所有线程共享。此区域的唯一目的就是存放对象实例,一般来说对象实例都是在这里分配内存,包括现对象、数组与运行时常量。
2)堆内存划分依据:
1.弱分代假说:大多数对象存活时间短。
2.强分代假说:熬过越多次的垃圾回收,就越难以被回收。
3.跨代引用假说:跨代引用的对象占少数。
划分情况如下图,1.6:
新生代和老年代,Eden空间,From Survivor, To Survivor。
在这里插入图片描述
·3)划分变迁
在这里插入图片描述
Young 年轻区 :主要保存年轻对象,分为三部分,Eden区、两个Survivor区。
Tenured 年老区 :主要保存年长对象,当对象在Young复制转移一定的次数后,对象就会被转移到Tenured区。
Perm 永久区 :主要保存class、method、filed对象,这部份的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到OOM :
PermGen space 的错误。
Virtual区: 最大内存和初始内存的差值,就是Virtual区。

在这里插入图片描述
由2部分组成,新生代(Eden + 2*Survivor ) + 年老代(OldGen )
JDK1.8中变化最大是,的Perm永久区用Metaspace进行了替换
注意:Metaspace所占用的内存空间不是在虚拟机内部,而是在本地内存空间中。区别于JDK1.7
在这里插入图片描述
取消新生代、老年代的物理划分
将堆划分为若干个区域(Region),这些区域中包含了有逻辑上的新生代、老年代区域

下面我们来验证区分划分:
1 我们需要先将不同版本的jdk下载到本地
2 接着借助jdk自带的工具来看JVM的相关信息
在这里插入图片描述
打开这个exe,然后按安装插件
插件下载地址
在这里插入图片描述
然后1.6版本我们可以看到:
在这里插入图片描述
同时我们使用命令

#可以查看堆内存结构,需要Java\bin目录下或者配置环境变量
jmap -heap 10420

在这里插入图片描述
4)虚拟机栈
栈帧(Stack Frame)是用于支持虚拟机进行方法执行的数据结构。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。每一个方法从调用至执行
完成的过程,都对应着一个栈帧在虚拟机栈里从入栈到出栈的过程。
栈内存为线程私有的空间,每个线程都会创建私有的栈内存,生命周期与线程相同,每个Java方法在执
行的时候都会创建一个栈帧(Stack Frame)。栈内存大小决定了方法调用的深度,栈内存过小则会导
致方法调用的深度较小,如递归调用的次数较少。

虚拟机栈可能出现的两种异常:
如果线程请求的栈深度大于虚拟机所允许的深度(Xss默认1m),会抛出StackOverflowError异常.
下面这个例子中,我们大约递归到2完多次就会堆栈溢出。

public class DemoError {
    private static int cnt = 0;
    private  void call() {
        cnt ++;
        try {
            call();
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println(cnt);
        }
    }
    public static void main(String[] args) {
        DemoError demoError = new DemoError();
        demoError.call();
    }
}
#空间进一步缩小,递归的深度会进一步缩小
java -Xss256k DemoError 

如果在创建新的线程时,没有足够的内存去创建对应的虚拟机栈,会抛出OutOfMemoryError异常。
5)本地方法栈
本地方法栈和虚拟机栈相似,区别就是虚拟机栈为虚拟机执行Java服务(字节码服务),而本地方法栈为虚拟机使用到的Native方法(比如C++方法)服务。

6)方法区
方法区(Method Area)是可供各个线程共享的运行时内存区域,方法区本质上是Java语言编译后代码存储区域,它存储每一个类的结构信息,例如:运行时常量池、成员变量、方法数据、构造方法和普通方法的字节码指令等内容。很多语言都有类似区域。
方法区的具体实现有两种:永久代(PermGen)、元空间(Metaspace)
方法区存储下面这几种数据类型:
第一:Class

  1. 类型信息,比如Class(com.hero.User类)
  2. 方法信息,比如Method(方法名称、方法参数列表、方法返回值信息)
  3. 字段信息,比如Field(字段类型,字段名称需要特殊设置才能保存的住)
  4. 类变量(静态变量):JDK1.7之后,转移到堆中存储
  5. 方法表(方法调用的时候) 在A类的main方法中去调用B类的method1方法,是根据B类的方
    法表去查找合适的方法,进行调用的。
    第二:运行时常量池(字符串常量池):从class中的常量池加载而来,JDK1.7之后,转移到堆中存

    字面量类型
    引用类型–>内存地址
    第三:JIT编译器编译之后的代码缓存

永久代和元空间的区别是什么?
1)JDK1.8之前使用的方法区实现是永久代,JDK1.8及以后使用的方法区实现是元空间。
2)存储位置不同:
永久代所使用的内存区域是JVM进程所使用的区域,它的大小受整个JVM的大小所限制。
元空间所使用的内存区域是物理内存区域。那么元空间的使用大小只会受物理内存大小的限
制。
3)存储内容不同:
永久代存储的信息基本上就是上面方法区存储内容中的数据。
元空间只存储类的元信息,而静态变量和运行时常量池都挪到堆中。
3)为什么要使用元空间来替换永久代?
字符串存在永久代中,容易出现性能问题和永久代内存溢出。

  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代
  • 溢出,太大则容易导致老年代溢出。
  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
  • Oracle 计划将HotSpot 与 JRockit 合二为一。
  • 在这里插入图片描述
    下面来看几个例子搞明白这个变迁:
import java.util.ArrayList;
import java.util.List;
public class StringDemo {
    public static void main(String[] args) {
        String base = "";
        List<String> strings = new ArrayList<String>();
        for (int i = 0; i < Integer.MAX_VALUE; i ++) {
            String str =  base  += "abcdefg";
            strings.add(str.intern());
        }
    }
}

java -XX:PermSize=8m -XX:MaxPermSize=8m StringDemo

在1.6的运行结果:
在这里插入图片描述
1.7运行结果:
永久带还保留但是字符串常量已经存到了堆中去,因为报的是堆内存溢出
在这里插入图片描述

1.8的运行结果:
可以看到1.8永久带彻底被移除,字符串常量存储到了堆内存中。
在这里插入图片描述

  1. 运行时常量

三种常量池的比较

class常量池:一个class文件只有一个class常量池
字面量:数值型(int、float、long、double)、双引号引起来的字符串值等
符号引用:Class、Method、Field等

运行时常量池:一个class对象有一个运行时常量池
字面量:数值型(int、float、long、double)、双引号引起来的字符串值等
符号引用:Class、Method、Field等

字符串常量池:全局只有一个字符串常量池
双引号引起来的字符串值

我们定义字符串的时候如果直接用""进行引用则是存储在字符串常量池中,如果用new String()则是存储在堆中。字符串常量池底层是一个拉链哈希表,用来存储和索引,所以字符串常量池不会存储重复的字符串。
看下面这几个例子来理解:
例子1

String a = "abc";
String b = "abc";
// true 我们知道 == 比较的是存储地址,所以我们可以看到这两个字符串都是存储在字符串常量池中的,并且引用的是同一个地址,那么肯定是只存储了一份。
System.out.println(a == b);

例子2

String a = "abc";
String b =  new String("abc");
// false b 这个对象是通过new出来的,他不会放到字符串常量池中
System.out.println(a == b);

例子3

String a = "abc";
String c = "def";
String d = a + c;
String f = "abcdef";
// false 所以只有"" 的字符串在在字符串常量池,涉及到任何其它操作都不会存储在字符串常量池中
System.out.println(f == d);
// intern将字符串移动到字符串常量池中
// 因为我们调用函数intern d 的引用移动了字符串常量池,他们的地址也就一样了
System.out.println(f == d.intern());

例子4 map的底层实现和字符串常量池基本一样

Map<String, Integer> map = new HashMap<>();
map.put("通话", 51);
map.put("重地", 55);
// 两个字符串的hashcode一模一样
System.out.println("通话".hashCode());
System.out.println("重地".hashCode());
// hashcode 相同的时候会进行拉链,也就是先比较hashcode如果相同在比较值,最后放到这个槽的链表上
System.out.println(map.size());

8)程序计数器
程序计数器(Program Counter Register),也叫PC寄存器,是一块较小的内存空间,它可以看作是当
前线程所执行的字节码指令的行号指示器。字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。分支,循环,跳转,异常处理,线程回复等都需要依赖这个计数器来完
成。
为什么需要程序计数器?
由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定的时刻,一个处理器(针对多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线
程切换(系统上下文切换)后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
存储的什么数据?
如果一个线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是一个Native方法,这个计数器的值则为空。
异常:此内存区域是唯一一个在Java的虚拟机规范中没有规定任何OutOfMemoryError异常情况的区
域。
9)直接内存
直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,
它可以使用native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer 对象
作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
本机直接内存的分配不会受到Java 堆大小的限制,受到本机总内存大小限制。

直接内存(堆外内存)与堆内存比较
直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显
直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显
下面这个例子可以比较它们读写之前的差异:

public class ByteBufferCompare {
    public static void main(String[] args) {
        //allocateCompare(); //分配比较
        operateCompare(); //读写比较
    }
    /**
     * 直接内存 和 堆内存的 分配空间比较
     * 结论: 在数据量提升时,直接内存相比非直接内的申请,有很严重的性能问题
     */
    public static void allocateCompare() {
        int time = 1000 * 10000; //操作次数,1千万
        long st = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
             //ByteBuffer.allocate(int capacity) 分配一个新的字节缓冲区。
            //非直接内存分配申请
            ByteBuffer buffer = ByteBuffer.allocate(2);
        }
        long et = System.currentTimeMillis();
        System.out.println("在进行" + time + "次分配操作时,堆内存 分配耗时:" +
                (et - st) + "ms");
        long st_heap = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
            //ByteBuffer.allocateDirect(int capacity) 分配新的直接字节缓冲区。
            //直接内存分配申请
            ByteBuffer buffer = ByteBuffer.allocateDirect(2);

        }
        long et_direct = System.currentTimeMillis();
        System.out.println("在进行" + time + "次分配操作时,直接内存 分配耗时:" +
                (et_direct - st_heap) + "ms");
    }

    /**
     * 直接内存 和 堆内存的 读写性能比较
     * 结论:直接内存在直接的IO 操作上,在频繁的读写时 会有显著的性能提升
     */
    public static void operateCompare() {
        int time = 10 * 10000 * 10000; //操作次数,10亿
        ByteBuffer buffer = ByteBuffer.allocate(2 * time);
        long st = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
        // putChar(char value) 用来写入 char 值的相对 put 方法
            buffer.putChar('a');
        }
        buffer.flip();
        for (int i = 0; i < time; i++) {
            buffer.getChar();
        }
        long et = System.currentTimeMillis();
        System.out.println("在进行" + time + "次读写操作时,非直接内存读写耗时:" +
                (et - st) + "ms");
        ByteBuffer buffer_d = ByteBuffer.allocateDirect(2 * time);
        long st_direct = System.currentTimeMillis();
        for (int i = 0; i < time; i++) {
        // putChar(char value) 用来写入 char 值的相对 put 方法
            buffer_d.putChar('a');
        }
        buffer_d.flip();
        for (int i = 0; i < time; i++) {
            buffer_d.getChar();
        }
        long et_direct = System.currentTimeMillis();
        System.out.println("在进行" + time + "次读写操作时,直接内存读写耗时:" +
                (et_direct - st_direct) + "ms");
    }
}

从数据流的角度,来看:

非直接内存作用链:本地IO –>直接内存–>非直接内存–>直接内存–>本地IO
直接内存作用链:本地IO–>直接内存–>本地IO
直接内存的使用场景:
有很大的数据需要存储,它的生命周期很长
适合频繁的IO操作,例如:网络并发场景

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

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

相关文章

即将发布的 Kibana 版本可运行 Node.js 18

作者&#xff1a;Thomas Watson Kibana 构建在 Node.js 框架之上。 为了确保每个 Kibana 版本的稳定性和使用寿命&#xff0c;我们始终将捆绑的 Node.js 二进制文件保持为最新的最新长期支持 (LTS) 版本。 当 Node.js 版本 18 升级到 LTS 时&#xff0c;我们开始将 Kibana 升级…

【WebRTC---源码篇】(二十三)JitterBuffer

PacketBuffer packetbuffer类中重要的一些变量 // buffer_.size() and max_size_ must always be a power of two.const size_t max_size_;//能存储的最大元素个数// The fist sequence number currently in the buffer.uint16_t first_seq_num_ RTC_GUARDED_BY(crit_);//这个…

Canmv K210开发板训练自己的模型

文章目录 前言​一、为什么选择Canmv开发板&#xff1f;二、利用Maix Hub在线训练三、配置开发环境1.MaixPy IDE2.刷指定固件 四、部署在Canmv开发板上五、测试模型 前言​ 关于Canmv开发板的介绍&#xff1a; CanMV K210 AI开发板全网首发&#xff0c;项目代码完全开源&#x…

C++数据结构之平衡二叉搜索树(一)——AVL的实现(zig与zag/左右双旋/3+4重构)

本文目录 00.BBST——平衡二叉搜索树01.AVL树02.AVL的插入2.1单旋——zig 与 zag2.2插入节点后的单旋实例2.3手玩小样例2.4双旋实例2.5小结 03.AVL的删除3.1单旋删除3.2双旋删除3.3小结 04.34重构05.综合评价AVL5.1优点5.2缺点 06.代码注意插入算法删除算法完整代码&#xff1a…

Spring Boot3.0(一):入门篇

什么是 Spring Boot Spring Boot 是由 Pivotal 团队提供的全新框架&#xff0c;其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。 用我的话来理解&#xff0c;就是 Spring…

Python自动化实战之使用Pytest进行API测试详解

概要 每次手动测试API都需要重复输入相同的数据&#xff0c;而且还需要跑多个测试用例&#xff0c;十分繁琐和无聊。那么&#xff0c;有没有一种方法可以让你更高效地测试API呢&#xff1f;Pytest自动化测试&#xff01;今天&#xff0c;小编将向你介绍如何使用Pytest进行API自…

K8s持久化存储(nfs网络存储)

数据卷 emptydir&#xff0c;是本地存储&#xff0c;pod重启&#xff0c;数据就不存在了&#xff0c;需要对数据持久化存储 1.nfs&#xff0c;网络存储 &#xff0c;pod重启&#xff0c;数据还存在的

32.SpringMVC配置

SpringMVC配置 在pom.xml里面将之前的"jar"打包方式更改为"war" 因为之前在JavaWeb创建Maven时就是按web工程来创建的&#xff08;详细可参考5.IDEA里面使用Maven.md博客&#xff09;&#xff0c;所以不需要再创建webapp目录了&#xff0c;完整目录如下&a…

20天学会rust(二)rust的基础语法篇

在第一节&#xff08;20天学rust&#xff08;一&#xff09;和rust say hi&#xff09;我们配置好了rust的环境&#xff0c;并且运行了一个简单的demo——practice-01&#xff0c;接下来我们将从示例入手&#xff0c;学习rust的基础语法。 首先来看下项目结构&#xff1a; 项目…

云原生Kubernetes:阿里云托管k8s集群ACK创建和使用

目录 一、理论 1.容器服务Kubernetes版 2.ACK Pro版集群概述 3.CKA版本说明 二、实验 1.创建专有版Kubernetes集群 三、问题 1.依赖检查未通过 一、理论 1.容器服务Kubernetes版 &#xff08;1&#xff09;概念 阿里云容器服务Kubernetes版&#xff08;Alibaba Cloud…

ChatGPT实战:创业咨询,少走弯路,少踩坑

用九死一生形容创业再适合不过&#xff0c;不过一旦成功回报也很诱人&#xff0c;这也是为什么那么多人下场创业。纸上得来终觉浅&#xff0c;绝知此事要躬行&#xff0c;创过业的人都知道其中的心酸&#xff0c;而他们也建议你去创业&#xff0c;因为那真不是一般人能干的事。…

采油平台状态监测:确保海上能源供应的可靠性与安全性

在全球范围内&#xff0c;保障可靠的能源供应对于每个工业化国家至关重要。某采油平台通过设备数字化平台的相关技术&#xff0c;实现了对海上平台的结构完整性的可靠监测和预测。本文将探讨海上平台在极端环境中的挑战&#xff0c;以及通过设备数字化平台数据分析如何确保采油…

Apache Arrow Acero执行引擎

Apache Arrow流执行引擎 对于许多复杂的计算&#xff0c;在内存或计算时间内&#xff0c;连续的计算函数的直接调用都是不可行的。为了更加有效的提高资源使用率、促进多批数据的消费&#xff0c;Arrow提供了一套流式执行引擎&#xff0c;称为Acero。 目前支持算子有&#xff1…

jvm-程序计数器

1、是什么 4 学习路线 类加载器 内存结构方法区 类堆 对象虚拟机栈程序计数器本地方法栈 执行引擎解释器编译器 热点代码 5 程序计数器–作用 java源代码编译蛏二进制字节码 jvm指令。 对所有平台保持一致性。记住下一条jvm指令的执行地址。寄存器&#xff0c;cpu中读取速度…

CI/CD—Docker中深入学习

1 容器数据卷 什么是容器数据卷&#xff1a; 将应用和环境打包成一个镜像&#xff01;数据&#xff1f;如果数据都在容器中&#xff0c;那么我们容器删除&#xff0c;数据就会丢失&#xff01;需求&#xff1a;数据可以持久 化。MySQL容器删除了&#xff0c;删容器跑路&#…

网络框架重构之路plain2.0(c++23 without module) 环境

开发环境 主开发环境 1、系统 plain因为支持跨平台&#xff0c;所以主要的两个操作系统是linux和windows&#xff0c;而linux中我选择了中小企业中常用的centos 7&#xff08;centos 8 已经停止支持了&#xff0c;但是7还有一段时间才会停&#xff0c;估计之后大家可能会被迫使…

C++QT教程2——创建QT项目

文章目录 2 创建Qt项目2.1 使用向导创建2.2 手动创建2.3 .pro文件2.4 一个最简单的Qt应用程序main入口函数中&#xff08;main.cpp&#xff09;arnold_widget.h函数arnold_widget.cpp 参考文章 2 创建Qt项目 2.1 使用向导创建 打开Qt Creator 界面选择 New Project或者选择菜…

CTF Crypto --- 七八月份比赛杂题记录

文章目录 前言第一届交通运输行业网络安全大赛决赛---CryptoeasyRSAMypow baby_RSAEasyRSA你懂RSA吗 前言 哥们终于想起账号密码了(尊嘟忘了)。 鸽了快两个星期辣&#xff0c;下次一定不鸽(x)。 第一届交通运输行业网络安全大赛决赛—Crypto easyRSA 题目&#xff1a; f…

APP外包开发的开发语言对比

在开发iOS APP时有两种语言可以选择&#xff0c;Swift&#xff08;Swift Programming Language&#xff09;和 Objective-C&#xff08;Objective-C Programming Language&#xff09;&#xff0c;它们是两种不同的编程语言&#xff0c;都被用于iOS和macOS等苹果平台的软件开发…