JVM的内容

0、Java基础考点

请添加图片描述

1、谈谈你对Java的理解

  • 平台无关性(一次编译,到处运行)
  • GC(垃圾清理)
  • 语言特性(泛型、反射)
  • 面向对象(封装、继承、多态)
  • 类库
  • 异常处理

2、Java是如何实现平台无关性的(一处编译,到处运行)

  • 编译时(语法和语义进行检测)
  • 运行时

javac将源码编译为.class字节码文件。

javap java自带的反编译工具
javac test.java
javap -c test

请添加图片描述

Java源码首先被编译成字节码,再由不同平台的JVM进行解析,Java语言在不同的平台上运行时不需要进行重新编译,Java虚拟机在执行字节码的时候,把字节码转换成具体平台上的机器指令。

为什么JVM不直接将源码解析成机器码去执行?

  • 准备工作:每次执行都需要各种检查。(编译时先进行语法和语义检查)
  • 兼容性:也可以将别的语言解析成字节码。(别的语言也可以编译为.class字节码文件)
  • 平台无关性:可以实现平台无关性。一次编译,到处执行。

3、JVM如何加载 .class文件

Java虚拟机

JVM屏蔽底层操作系统的不同,减少基于原生语言开发的复杂性。

  • 内存结构模型
  • GC

请添加图片描述

  • Class loader:依据特定格式,加载dass文件到內存。
  • Execution Engine:对命令进行解析。
  • Native Interface:融合不同开发语言的原生库为Java所用。
  • RuntimeDataArea:JM内存空间结构模型。

JVM主要有Class Loader、RuntimData Area 、Execution Engine、Native Interface四部分组成,主要通过Class Loader将符合格式要求的文件,加载到内存中。并通过Execution Engine解析内部字节码,提交给操作系统执行。

4、谈谈反射

  • JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;
  • 对于任意一个对象,都能够调用它的任意方法和属性;
  • 这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

待调用方法:

public class Robot {

    private String name;

    //    公共的方法
    public void sayHi(String hello) {
        System.out.println(hello + ":" + name);
    }

    //    私有化的方法
    private String throwHello(String tag) {
        return "Hello :" + tag;
    }
}

调用:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Test {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class robot = Class.forName("com.lydms.IO.Robot");
        Robot r = (Robot) robot.newInstance();
        System.out.println("Class name is" + robot.getName());


//        1、执行private方法throwHello,并接受返回值
        Method throwHello = robot.getDeclaredMethod("throwHello", String.class);
//        将私有化方法设置为可见
        throwHello.setAccessible(true);
//        执行方法,并传入参数,获取返回值
        Object str = throwHello.invoke(r, "Bob");
        System.out.println("Result is " + str);


//        2、执行publi方法sayHi,并指定name的值
        Method sayHi = robot.getMethod("sayHi", String.class);
        sayHi.invoke(r, "Welcome");


//        3、执行publi方法sayHi,并指定name的值
//        获取私有变量的对象
        Field name = robot.getDeclaredField("name");
//        给私有变量设置可见
        name.setAccessible(true);
        name.set(r, "Alice");
        sayHi.invoke(r, "Welcome");
    }
}

类从编译到执行的过程

  • 编译器将 Robot java源文件编译为 Robot.class字节码文件
  • Classloader将字节码转换为JVM中的 Class< Robot>对象
  • JVM利用Class< Robot>对象实例化为 Robot对象

5、谈谈ClassLoader

Classloader 在 Java 中有着非常重要的作用,它主要工作在 Class装载的加载阶段,其主要作用是从系统外部获得 Class二进制数据流。它是Java的核心组件,所有的Class都是由 Classloader进行加载的,Classloader负责通过将 Class文件里的二进制数据流装载进系统,然后交绐Java虚拟机进行连接、初始化等操作。

ClassLoader的种类

  • BootstrapClassLoader:C++编写,加载核心库java.*。
  • Extclassloader:Java编写,加载扩展库 Javax.*。
  • AppClassLoader:Java编写,加载程序所在目录方法。
  • 自定义 Classloader:Java编写,定制化加载。

Extclassloader:(扩展库 Javax.*)

String property = System.getProperty("java.ext.dirs");

AppClassLoader:(加载程序所在目录)

String property = System.getProperty("java.class.path");

自定义ClassLoader的实现

关键函数

寻找class文件,读取二进制文件

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

定义类(将字节流转为class)

protected final Class<?> defineClass(byte[] b, int off, int len)throws ClassFormatError{
    return defineClass(null, b, off, len, null);
}

根据名称/位置加载Class文件,调用defineClass去解析字节流

待执行文件:

public class Wali{
    static{
         System.out.println("Hello Wali");
        
    }
}

自定义类加载器

import java.io.*;
public class MyClassLoader extends ClassLoader {
    private String path;
    private String classLoaderName;


    public MyClassLoader(String path, String classLoaderName) {
        this.path = path;
        this.classLoaderName = classLoaderName;
    }


//    用于寻找类文件
    @Override
    public Class findClass(String name) {
        byte[] b = loadClassData(name);
        return defineClass(name, b, 0, b.length);
    }


//    用于加载类文件
    private byte[] loadClassData(String name) {
        name = path + name + ".class";
        InputStream in = null;
        ByteArrayOutputStream out = null;
        try {
            in = new FileInputStream(new File(name));
            out = new ByteArrayOutputStream();
            int i = 0;
            while ((i = in.read()) != -1) {
                out.write(i);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return out.toByteArray();
    }
}

调用类加载器

 public class ClassLoaderChecker {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        MyClassLoader m = new MyClassLoader("D:\\", "myClass");
        Class<?> c = m.loadClass("Wali");
//        会调用自定义的findClass( )
        ClassLoader classLoader = c.getClassLoader();
        System.out.println(classLoader);
//        执行加载文件里的方法
        c.newInstance();
    }
}

类加载器的双亲委派机制

请添加图片描述

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1、首先,检查是否已经加载的类
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                     2、没有的话,代用父类的加载器
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // 如果类没有找到抛出ClassNotFoundException 没有父类加载
            }
            if (c == null) {
                // 如果仍然没有找到,然后调用findClass去寻找class
                long t1 = System.nanoTime();
                c = findClass(name);
                  这是定义的类装入器;记录数据;
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

为什么要使用双亲委派机制去加载类?

  • 避免对分同样字节码的加载

没必要保存相同的类对象class。不使用委托机制,则每个类加载一次Class文件,内存中会有多份class文件。使用委托机制,自定义classLoader开始逐层向上查询是否装载,如果有则不再装载。保证内存中只有一份class文件。

6、类的加载方式

  • 隐式加载:new
  • 显示加载:loadClass,forName等

类的装载过程

请添加图片描述

loadClass和forName的区别

  • Class.forName得到的 class 是已经初始化完成的(完成加载、链接、初始化)
  • Classloder.loadClass得到的 class 是还没有链接的(完成加载)
package com.lydms.IO;
public class Robot {
    static {
        System.out.println("Hello Robot");
    }
}

采用classLoader方式加载(未执行static方法)

public static void main(String[] args) throws ClassNotFoundException {
    ClassLoader classLoader = Robot.class.getClassLoader();
}

采用Class.forName( )方式加载(执行static中方法)

public static void main(String[] args) throws ClassNotFoundException {
    Class.forName("com.lydms.IO.Robot");
}

7、Java的内存模型

在程序执行的过程中,需要不断将内存的逻辑地址与物理地址进行映射,找到相关的指令以及数据去执行,java运行时面对着与其它进程完全相同的内存限制即受限于操作系统架构提供的可寻址地址空间,操作系统架构提供的可寻址地址空间由操作系统的位数决定,32位处理器提供了2^32的可寻址范围即4GB.

内核空间:内核是主要的操作系统程序和C运行时的空间,包含用于连接计算机硬件,调度程序,提供联网和虚拟内存等服务的逻辑和基于C的进程。

用户空间:用户空间是java进程实际运行时使用的内存空间,32位系统用户最多可以访问3GB,内核可以访问所有物理内存,而64位系统用户进程最大可以访问超过512gb,内核代码同样可以访问所有物理空间。

内存简介

请添加图片描述

  • 32位处理器:2^32的可寻址范围
  • 64位处理器:2^64的可寻址范围

地址空间的划分

  • 内核空间(主要操作系统程序和C运行位置)
  • 用户空间(Java程序运行位置)

请添加图片描述

Java内存模型(Runtime Data Area)

请添加图片描述

JVM内存模型—JDK8

请添加图片描述

程序计数器(Program Counter Register)

  • 当前线程所执行的字节码行号指示器(逻辑)。
  • 改变计数器的值来选取下一条需要执行的字节码指令。
  • 和线程是一对一的关系即“线程私有”。
  • 对Java方法计数,如果是Native方法则计数器值为 Undefined
  • 不会发生内存泄露。

Java虚拟机栈(Stack)

  • Java方法执行的内存模型
  • 包含多个栈针

请添加图片描述

局部变量表和操作数栈

  • 局部变量表:包含方法执行过程中的所有变量。
  • 操作数栈:入栈、出栈、复制、交换、产生消费变量。
public class ByteCodeSample{
    public static int add(int a, int b){
        int c=0;
        c = a + b;
        return c;
    }
}

javap 反编译后

请添加图片描述

执行 add(1,2)

请添加图片描述

0:iconst_0:0压入操作数栈,1/2放入局部变量表

  • 1: istore_2:将栈中元素弹出,放到局部变量表中2位置。
  • 2: iload_0:将局部变量中第0个元素,压入栈中。
  • 3: iload_1:将局部变量中第1个元素,压入栈中。
  • 4: iadd:将栈中0和1进行计算,并将结果压入栈顶。
  • 5: istore_2:将栈中元素弹出,放到局部变量2位置
  • 6: iload_2:将局部变量2中元素,压入栈中。
  • 7: ireturn:将栈中元素返回

递归为什么会引发 java.lang StackOverflow Error 异常

当线程执行一个方法时,就创建一个栈针,并将创建的栈针压入虚拟机栈中,当方法执行完毕时,将栈针出栈。一直自己调用自己,不能释放栈针,递归过深,栈帧数超过虚拟栈深度,抛出StackOverflow Error异常。

递归过深,栈帧数超出虚拟栈深度。

虚拟机栈过多会引发 java.lang.OutofMemory Error(内存溢出)异常

栈内存不需要通过GC回收,就能够自动释放内存。

本地方法栈(Native Method Stack)

与虚拟机栈相似,主要作用于标注了 native的方法

8、元空间(MetaSpace)与永久代(PermGen)的区别

元空间:JDK1.8后,类的元数据放在本地堆内存中,叫做元空间(MetaSpace)。这一块区域在JDK1.7以前属于永久代,元空间与永久代都是用来存储class的相关信息,包括class的method与field等,元空间与永久代都是方法区的实现,只是实现方法有所不同.所以说方法区只是JVM的规范,原先位于方法区的JVM常量池已被移动到堆中,并且在jdk8以后用元空间替代了永久代。

,

  • 元空间使用本地内存,而永久代使用的是JVM的内存。
  • 此异常不存在了: java.lang.OutOfMemoryError:PermGen space

MetaSpace相比 Permgen的优势

  • 字符串常量池存在永久代中,容易出现性能问题和内存溢出。
  • 类和方法的信息大小难易确定,给永久代的大小指定带来困难。
  • 永久代会为GC带来不必要的复杂性。
  • 方便 Hotspot与其他JVM如 Jrockit 的集成。

Java堆(Heap)

  • 对象实例的分配区域。
  • GC管理的主要区域。

java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,此内存区域的唯一目的就是存放对象实例,所有的对象实例都在这里分配内存.也是GC管理的主要区域。

32位处理器java进程的内存布局:

请添加图片描述

现在收集器基本都采用分代收集算法,所以java堆还可以分为新生代和老年代:

请添加图片描述

8、Java内存模型之常考习题

JVM三大性能调优参数-Xms-Xmx-Xss的含义

  • -Xms:堆的初始值。
  • -Xmx:堆能达到的最大值。
  • -Xss:规定了每个线程虚拟机栈(堆栈)的大小
  • -Xmn10m:设置新生代大小为20m。

通常将-Xms与-Xmx写成一样的,避免内存抖动,不必再为扩展内存空间而消耗性能;

Java内存模型中堆和栈的区别->内存分配策略

程序运行时有三种内存分配策略,静态的,栈式的,堆式的。

  • 静态存储:编译时确定每个数据目标在运行时的存储空间需求。(静态存储是指在编译时能确定每个数据目标在运行时的存储空间需求,因而在编译时就能分配给它们固定的内存空间,这种程序分配策略要求代码中不能有可变数据集,以及嵌套,递归的结构出现)
  • 栈式存储:数据区需求在编译时未知,运行时模块入口前确定。(该程序可被动态的存储分配,程序对数据区的要求是编译时是完全未知的,运行时才能知道,但是规定在运行到数据模块时必须知道该程序所需内存的大小以分配其内存)
  • 堆式存储:编译时或运行时模块入口都无法确定,动态分配.

Java内存模型中堆和栈的区别

  • 联系:引用对象、数组时,栈里定义变量保存堆中目标的首地址

请添加图片描述

区别:

  • 管理方式:栈自动释放,堆需要GC。
  • 空间大小:栈比堆小。
  • 碎片相关:栈产生的碎片远小于堆。
  • 分配方式:栈支持静态和动态分配,而堆仅支持动态分配。
  • 效率:栈的效率比堆高。

元空间、堆、线程独占部分间的联系—内存角度

public class HelloWorld {
    private String name;


    public void getName() {
        System.out.println("Hello" + name);
    }


    public void setName(String name) {
        this.name = name;
    }


    public static void main(String[] args) {
        int a = 1;
        HelloWorld hw = new HelloWorld();
        hw.setName("Test");
        hw.getName();
    }
}

请添加图片描述

不同版本之间的intern( )方法的区别—JDK6 VS JDK6+

String s = new String( "a")
s .intern( );
  • JDK6:当调用 intern方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,将此字符串对象添加到字符串常量池中,并且返回该字符串对象的引用。
  • JDK6+:当调用 intern方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用。否则,如果该字符串对象已经存在于Java堆中,则将堆中对此对象的引用添加到字符串常量池中,并且返回该引用;如果堆中不存在,则在池中创建该字符串并返回其引用。

请添加图片描述

10、字符串常量池

JVM中的常量池从永久代中,移动到了堆中。

public class PermGenErrTest {
   public static void main(String[] args) {
       for (int i = 0; i < 1000; i++) {
//            将返回的随机字符串添加到字符串常量池中
           getRandomString(100000).intern();
       }
       System.out.println("Creat End");
   }


   //    返回指定长度的随机字符串
   private static String getRandomString(int length) {
//        字符串源
       String str = "asdfasdfiouwerwelsdfkjsafjiasfnsnfsdafsfs";
       Random random = new Random();
       StringBuffer sb = new StringBuffer();
       for (int i = 0; i < length; i++) {
           int number = random.nextInt(20);
           sb.append(str.charAt(number));
       }
       return sb.toString();
   }
}

设置永久代的大小

-XX: MaxPermSize=6M    永久代最大为6M
-XX: PermSize=6M        初始时永久代大小
-XX:MaxDirectMemorySize 可以设置java堆外内存的峰值

JDK6(报出异常—永久代内存溢出)

请添加图片描述

JDK7+(正常执行)

请添加图片描述

public class Test {
    public static void main(String[] args) {
//        存在堆中
        String s = new String("a");
//        想放到常量池中,但是其中已经存在,放失败
        s.intern();
//        常量池中
        String s2="a";
        System.out.println("s==s2: "+(s==s2));

//        拼接为"aa",在进行比较
        String s3 = new String("a") + new String("a");
        s3.intern();
        String s4="aa";
        System.out.println("s3==s4: "+(s3==s4));
    }
}

输出结果:
JDK6中:

s==s2: false
s3==s4: false

JDK6+中:

s==s2: false
s3==s4: true

解析:

是在常量池中创建"a’,再在堆中创建"a"的空间。也就是占用2份内存空间。

String s = new String("a");

在堆中创建出"aa",并将"aa"放置到常量池中

String s3 = new String("a") + new String("a");
s3.intern();

s==s2: false

String s = new String("a");在堆中创建内存空间,s指的是堆内存地址。
String s2="a";引用的是常量池中的地址。

s3==s4: true

JDK6:中副本,指向的是常量池和堆中
JDK6+:常量池中可以放置引用。
生成的s3值"aa",存在于堆中,将其引用放在常量池中。
s4创建的时候,查看常量池中有"aa"的引用(指向堆中),则使用该引用。则S3==S4(true)
堆中"aa",常量池中有"aa"的引用。

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

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

相关文章

c#网编实验五--WCF和TCP消息通信实验

分别编写服务端和客户端程序&#xff0c;利用基于WCF的TCP技术&#xff0c;实现在线聊天功能&#xff0c;完成在线用户列表管理&#xff0c;消息发送、接收的功能。 在同一个解决方案中&#xff0c;分别编写服务端程序和客户端程序&#xff0c;利用TCP实现简单的群聊功能。 具…

校园wifi网页认证登录入口

很多校园wifi网页认证登录入口是1.1.1.1 连上校园网在浏览器写上http://1.1.1.1就进入了校园网 使 用 说 明 一、帐户余额 < 0.00元时&#xff0c;帐号被禁用&#xff0c;需追加网费。 二、在计算中心机房上机的用户&#xff0c;登录时请选择新建帐号时给您指定的NT域&…

TOGAF10®标准中文版--(阶段C —数据架构阶段B )方法

6.5 方法 6.5.1 数据结构 数据架构应该能够处理&#xff1a; 静态数据——存储中的数据动态数据——事务或服务/API 中的数据使用中的数据——应用边界的数据&#xff08;例如&#xff0c;GUI&#xff09;开放数据——组织提供给公众使用并且自愿或合法要求提供的数据 将添…

Linux主分区,扩展分区,逻辑分区的联系和区别

基本概念 硬盘分区有三种&#xff0c; 主磁盘分区、扩展 磁盘分区、 逻辑分区。 一个 硬盘 主分区至少有1个&#xff0c;最多4个&#xff0c;扩展分区可以没有&#xff0c;最多1个。且 主分区扩展分区总共不能超过4个。 逻辑分区可以有若干个。 在windows下激活的 主分区是 …

MySQL 被 PG 干翻了。。

出品 | OSC开源社区&#xff08;ID&#xff1a;oschina2013) Stack Overflow 发布了 2023 年开发者调查报告&#xff0c;据称共计超过 9 万名开发者参与了此次调查。 完整报告包含了受访开发者画像&#xff0c;以及关于开发技术、AI、职业、社区等方面的内容。本文主要介绍关于…

stm32读取BH1750光照传感器

stm32读取BH1750光照传感器 一.序言二.BH1750指令三.IIC协议四.代码实例4.1 bh1750.c源文件4.2 bh1750.h头文件 一.序言 BH1750是用IIC协议进行数据传输的。有SCL,SDA&#xff0c;VCC,GND四根线。下图是原理图 二.BH1750指令 我们先看芯片手册的操作指令&#xff08;下图&a…

2023年网络安全竞赛——网页渗透

网页渗透 任务环境说明:  服务器场景:Server2120  服务器场景操作系统:未知(封闭靶机)  用户名:未知 密码:未知 访问服务器的网站主页,猜测后台数据库中本网页中应用的库名称长度,将长度作为flag提交; 通过扫描发现靶机开放80端口,直接访问80 尝试输入一个1,…

【设计模式】工厂模式(简单工厂模式、工厂方法模式、抽象工厂模式)详记

注&#xff1a;本文仅供学习参考&#xff0c;如有错漏还请指正&#xff01; 参考文献/文章地址&#xff1a; https://zh.wikipedia.org/wiki/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%8F%AF%E5%A4%8D%E7%94%A8%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%BD%AF%E4%BB%B…

el-table合计行单元格合并、单元格样式修改

1、目标效果 源码放在下面&#xff0c;复制粘贴即可 &#xff08;1&#xff09;合计行放在头部&#xff0c;且字体颜色变粗、合计行背景色变粗 &#xff08;2&#xff09;合计行年龄算平均值且字体颜色为绿色&#xff0c;财产算总数且字体颜色为红色 2、原理 2.1、el-table中s…

WPF 零基础入门笔记(1):WPF静态页面,布局+样式+触发器

文章目录 官方文档往期回顾零基础笔记项目实战&#xff08;已完结&#xff09; WPF项目创建为什么选net core版本 WPF 静态页面WPF 页面布局WPF样式Style样式行内样式行外样式如果是简单样式&#xff0c;可以这么写如果是复杂样式 WPF样式继承WPF触发器单条件触发器多条件触发 …

【性能测试一】性能测试概述

目录 &#x1f31f;一、性能测试的基础概念 &#x1f308;1、生活中软件相关的性能问题&#xff1f; &#x1f308;2、性能测试的概念 &#x1f308;3、性能测试与功能测试的区别&#xff1f; &#x1f308;4、什么样的软件属于性能好&#xff1f;什么样的软件属于性能不好…

网络协议TCP/IP 协议学习笔记一

T C P / I P通常被认 为是一个四层协议系统&#xff0c;每一层负责不同的功能&#xff1a; 1) 链路层&#xff0c;有时也称作数据链路层或网络接口层&#xff0c; 通常包括操作系统中的设备驱动程序和计算机 中对应的网络接口卡。它们一起处理与电缆&#xff08;或其他任何传输…

黑客常用cmd命令(window版)

1、ping命令 ping命令是一个常用的网络工具&#xff0c;用来测试和诊断网络连接状况。通过发送ICMP&#xff08;Internet控制消息协议&#xff09;数据包到目标主机&#xff0c;并接收回复的数据包&#xff0c;可以测量目标主机的可达性、平均响应时间等指标。 在Windows操作…

【C++】哈希的应用

文章目录 一、位图1. 位图的引入2. 位图的实现3. 位图的应用4. 哈希切割 二、布隆过滤器1. 布隆过滤器的引入2. 布隆过滤器的实现3. 布隆过滤器的应用4. 布隆过滤器的总结 一、位图 1. 位图的引入 我们先来看一道面试题&#xff1a; 给40亿个不重复的无符号整数&#xff0c;没…

Spring Boot 如何使用 @RequestParam 进行数据校验

Spring Boot 如何使用 RequestParam 进行数据校验 在 Web 应用程序中&#xff0c;用户提交的数据通常以请求参数的形式传递。在 Spring Boot 中&#xff0c;可以使用 RequestParam 注解来获取请求参数。但是&#xff0c;如何确保这些请求参数的有效性呢&#xff1f;在本文中&a…

APP测试面试题快问快答(五)

21. App自动化你用的什么工具&#xff1f; 框架&#xff1a;Appium 编译环境和工具&#xff1a;python3.7和PyCharm 环境&#xff1a;Android sdk 第三方模拟器&#xff1a;夜神、蓝叠等模拟器 定位工具&#xff1a;uiautomatorviewer 实时日志查看&#xff1a;ddms 22.…

智慧加油站卸油作业行为分析算法 opencv

智慧加油站卸油作业行为分析系统通过opencvpython网络模型技术&#xff0c;智慧加油站卸油作业行为分析算法实现对卸油作业过程的实时监测。当现场出现卸油作业时人员离岗&#xff0c;打电话人员抽烟等违规行为&#xff0c;灭火器未正确摆放&#xff0c;明火和烟雾等异常状态&a…

TypeScript零基础入门之背景介绍和环境安装

一、什么是TypeScript TypeScript是一种由微软开发和维护的开源编程语言。它是JavaScript的超集&#xff0c;意味着任何JavaScript程序都是一种有效的TypeScript程序。TypeScript添加了静态类型、类、接口、枚举和命名空间等概念&#xff0c;同时支持ES6特性。TypeScript被视为…

Kubernetes入门实战课-初始容器

Kubernetes入门实战课-初始容器 文章目录 Kubernetes入门实战课-初始容器课前准备初始容器Docker 的形态Docker 的安装Docker 的架构 容器的本质隔离原因与虚拟机区别隔离是怎么实现的 镜像创建容器镜像&#xff1a;如何编写正确、高效的Dockerfile镜像的内部机制是什么Dockerf…

Spring介绍

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;JavaEE、Spring 目录 1、Spring简介2、轻量级和非侵入性3、IoC容器4、AOP支持5、声明式事务管理6、数据访问支持…