JVM之类的初始化与类加载机制

news2024/11/29 20:38:49

类的初始化

clinit

  • 初始化阶段就是执行类构造器方法clinit的过程。
  • 此方法不需定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。
  • 构造器方法中指令按语句在源文件中出现的顺序执行。
  • clinit不同于类的构造器。(关联:构造器是虚拟机视角下的init
  • 若该类具有父类,JVM会保证子类的clinit执行前,父类的clinit已经执行完毕。
  • 虚拟机必须保证一个类的clinit方法在多线程下被同步加锁。

只有给类中的static的变量显示赋值或者静态代码块中赋值了,才会生成此方法.

哪些类不会生成clinit方法
  • 一个类中没有静态变量和静态代码块
  • 有静态变量,但是没有静态变量的显示赋值以及静态代码来执行初始化操作
  • 直接采用的 static final修饰的基本数据类型字段,这些字段在链接阶段的准备环节就已经初始化了
  • 不是显示常量赋值的话 而是调用方法去赋值还是会生成clinit方法的(在初始化阶段给他赋值)
clinit方法会死锁吗

虚拟机会保证一个类的clinit方法在多线程环境下被正确的加锁,同步(所以他多线程下是线程安全的),这同样会导致如果clinit方法中有耗时过场的操作,就可能导致线程阻塞而死锁,并且非常难以排查.

类加载的时机(什么情况会触发类的加载)

  • 当创建一个类的实例时,比如new关键字,或者是反射,克隆,反序列化等
  • 当调用类的静态方法时,即当使用了字节码invokestatic指令
  • 当使用类,接口的静态字段时(final修饰特殊考虑),比如使用getstatic或者putstatic指令
  • 当使用java.lang.reflect包中的方法反射类的方法时.比如Class.forname(“aa.bb.Test”)
  • 当初始化子类时,如果发现其父类还没有初始化,则需要先去触发父类的初始化
  • 如果一个接口定义了default方法,那么直接实现或者间接实现该接口的类初始化,该接口要在其之前被初始化
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个主类
  • 当初次调用MethodHandle实例时,初始化该MethodHandle指向方法所在的类

当Java虚拟机初始化一个类时,要求他所有的父类都已经被初始化,这条规则不适用于接口 \

  • 在初始化一个类时,并不会先初始化他所实现的接口
  • 在初始化一个接口时,并不会初始化他的父接口
    因此一个父接口并不会因为他的子接口或者实现类初始化而初始化,只有当程序首次使用特定接口的静态字段时,才会导致该接口初始化

被动使用不会触发类的初始化]

  • 通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类或接口的初始化,因为常量已经在链接准备阶段被显示赋值了
  • 调用ClassLoader类的loadClass()加载一个类,并不是对类的主动使用,不会导致类的初始化

被动的使用,意味着不需要执行初始化环节,意味着没有clinit的调用.

类的卸载

除非是你自定义的类加载器直接把这个类加载器也卸载掉(包括他下面的类),否则其他的已有的类加载器是不允许被卸载的,因为他们与类是双向绑定的关系.

类加载代码demo

public class T {

    public static int k = 0;
    public static T t1 = new T("t1");
    public static T t2 = new T("t2");
    public static int i = print("i");
    public static int n = 99;

    static {
        print("静态块");
    }

    public int j = print("j");

    {
        print("构造块");
    }

    public T(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + "   n=" + n);
        ++n;
        ++i;
    }

    public static int print(String str) {
        System.out.println((++k) + ":" + str + " i=" + i + "   n=" + n);
        ++n;
        return ++i;
    }

    public static void main(String[] args) {

    }
}

运行结果

1:j i=0   n=0
2:构造块 i=1   n=1
3:t1 i=2   n=2
4:j i=3   n=3
5:构造块 i=4   n=4
6:t2 i=5   n=5
7:i i=6   n=6
8:静态块 i=7   n=99

类的加载器

类的加载分类

  • 显示加载:调用ClassLoader类中的方法去显示的加载某个类
  • 隐式加载:是由jvm自动加载到内存中的,用到哪些加载哪些

类的唯一性

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间,比较这两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义

命名空间
  • 每个类加载器都有自己的命名空间,由该加载器及其所有的父类加载器所加载的类组成
  • 在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
  • 在不同的命名空间,有可能出现类的完整名字(包括包名)相同的情况

在大型应用中,可以借助这个特性,来运行一个类的不同版本

类加载的三个基本特征

  • 双亲委派模型: 也有不用这个机制的 ,比如上下文加载器
  • 可见性: 子类加载器可以访问父类加载器的类型,但是反过来不被允许.
  • 单一性: 由于父类加载器对于子加载器是可见的,所以父类加载过的类,在子类中不会重复加载.

类加载的分类

JVM支持两种类型的类加载器,分别为引导类加载器(BootStrap ClassLoader)和自定义类加载器(User-Defined ClassLoader).
从概念上讲,自定义加载器应该是程序中由开发人员自定义的加载器,但是Java的虚拟机规范并没有如此定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义加载器,而引导类加载器是由C++源码是实现的,在Java环境中也根本获取不到

启动类加载器(BootStrap ClassLoader )
  • 这个类加载使用C/C++语言实现,嵌套在JVM内部
  • 他用来加载Java的核心库,用于提供JVM自身所需要的类
  • 不继承自ClassLoader,没有父加载器
  • 出于安全考虑,只加载包名为java,javax,sun等开头的类
  • 加载扩展类和应用程序类加载器,并指定为他们的父类加载器
扩展类加载器(Extension ClassLoader)
  • Java语言编写,由ExtClassLoader实现
  • 继承自ClassLoader类
  • 父类加载器为启动类加载器
  • 从java.ext.dirs系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext子目录下加载类库,如果用户创建的jar放在此目录下,也会由扩展类加载器加载
应用程序加载器(系统类加载器 AppClassLoader)
  • Java语言编写,由AppClassLoader实现
  • 继承自ClassLoader类
  • 父类加载器为启动类加载器
  • 他负责加载环境变量classPath或系统属性java.class.path指定路径下的类库
  • 应用程序中的类加载器默认是系统类加载器
  • 他是用户自定义类加载器的默认父加载器
  • 通过ClassLoader的getSystemClassLoader()可以获取到该类加载器
用户自定义类加载器

通过类加载器可以实现非常绝妙的插件机制.自定义的类加载器能够实现应用隔离,tomcat和spring都在内部实现了自定义类加载器,这样的机制比c/c++要优秀太多,自定义的类加载器通常都需要继承自抽象类ClassLoader

ClassLoader源码分析

什么是双亲委派机制

在加载器加载类的时候,如果加载器有父类(一般是组合定义为父类),先让父类去加载,一层层往上找,如果父类都没有再由自己加载,否则由某一个父类加载进来.

关键方法源码分析
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 {
	//在这里 Java源码判断了如果父类不为null,就去调用父类的加载方法,一直递归到最高的父类
                    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;
        }
protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
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;
    }
private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }
  • loadClass属于一个模板方法,不需要去重写他 否则有可能会破坏双亲委派机制
  • 子类可以去重写findClass方法,到这里是需要子类加载器自己去加载类方法了(有些像spring的感觉 新建和获取都是通过getSingletonBean去获取单例bean的)
  • defineClass是获取类的字节码流信息,并组装成class对象(这个方法也都会用到)
  • preDefineClass 中判断必须以java.开头,避免双亲委派机制遭到破坏时系统受到威胁(又加了一层判断确保安全性)
自定义类加载器
public class UserDefineClassLoader extends ClassLoader {

    private final String rootPath;

    public UserDefineClassLoader(String rootPath) {
        this.rootPath = rootPath;
    }

    @Override
    protected Class<?> findClass(String name) {
        //转换为以文件路径表示的文件
        String filePath = classToFilePath(name);
        //获取指定路径的class文件对应的二进制流数据
        byte[] data = getBytesFromPath(filePath);
        //自定义ClassLoader 内部需要调用defineClass() 把二进制流还原为Class实例
        return defineClass(name, data, 0, data.length);
    }

    private byte[] getBytesFromPath(String filePath) {
        FileInputStream fis = null;
        ByteArrayOutputStream baos = null;
        try {
            fis = new FileInputStream(filePath);
            baos = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, len);
            }
            return baos.toByteArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    private String classToFilePath(String name) {
        return rootPath + "\\" + name.replace(".", "\\") + ".class";
    }

    public static void main(String[] args) throws ClassNotFoundException {
        UserDefineClassLoader loader = new UserDefineClassLoader("D:\\code\\test");
        Class<?> aClass = loader.findClass("com.test.User");
        System.out.println(aClass);
		  System.out.println(aClass.getClassLoader());
    }

}

双亲委派类加载机制

优势与好处
  • 保护程序安全,防止核心的API类库被随意篡改
  • 避免类的重复加载,确保一个类的全局唯一性(当父类已经加载完成后,子类不会再去加载)
弊端
  • 下层的加载器可以访问上层父类的加载器都有什么类,但是上层是没有办法得知下层类加载器都有什么类,他是单向的
破坏双亲委派机制
  • 线程上下文加载器:(Thread.currentThread().getContextClassLoader()) jdbc等场景就是这么采用的
  • 热部署 (每一个程序模块都有一个自己的类加载器,当需要替换一个bundle时,就把bundle连同类加载器一起换掉以实现代码的热替换,这时候类加载处于一种更加复杂的网状结构)

tomcat的类加载机制

  • tomcat8可以配置<Loader delegate="true"/>表示遵循双亲委派机制
    类的结构图
    类的结构图
加载流程

当tomcat启动时,会创建几种类加载器
{fwtab}
{fwh}
{fwthead target=“1”} Bootstrap引导类加载器 {/fwthead}
{fwthead target=“2”} system系统类加载器 {/fwthead}
{fwthead target=“3”} tomcat自定义类加载器 {/fwthead}
{fwthead target=“4”} 加载顺序 {/fwthead}
{/fwh}
{fwb}
{fwtbody target=“1”}
加载jvm启动所需的类,以及标准扩展类(位于jre/lib/ext下)
{/fwtbody}
{fwtbody target=“2”}
加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定,位于CATALINA_HOME/bin下
{/fwtbody}
{fwtbody target=“3”}
Common/Catalina/Shared/WebappClassLoader
这些是tomcat自己定义的类加载器,它们分别加载’/common/,/server/,/shared/*'(在tomcat6以后已经合并到了根目录的lib目录下)和/WebApp/WEB-INF/*中的Java类库,其中WebApp类加载器和jsp类加载器通常会存在多个实例,每一个web应用程序对应一个WebApp类加载器,每一个jsp文件对应一个jsp类加载器

  • CommonClassLoader,Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个WebApp访问(加载CATALINA_HOME/lib下的结构,比如servlet-api.jar)
  • CatalinaClassLoader,Tomcat容器私有的类加载器,加载路径中的class对于WebApp不可见
  • SharedClassLoader,各个WebApp共享的类加载器,加载路径中的class对于所有WebApp可见,但是对于Tomcat容器不可见
    {/fwtbody}
    {fwtbody target=“4”}
    当应用需要某个类时,则会按照下面的顺序进行类加载
  • 使用bootstrap引导类加载器加载
  • 使用system系统类加载器加载
  • 使用应用类加载器在WEB-INF/classes中加载
  • 使用应用类加载器在WEB-INF/lib中加载
  • 使用common类加载器在CATALINA_HOME/lib中加载
    {/fwtbody}
    {/fwb}
    {/fwtab}

从图中的委派关系可以看出:
CommonClassLoader能加载的类都可以被CatalinaClassLoader和SharedClassLoader使用,从而实现了公有类库的共用,而
CatalinaClassLoader和SharedClassLoader自己能加载到类则与对方相互隔离.
WebAPPClassLoader可以使用SharedClassLoader加载到的类,但是每个WebAPPClassLoader实例之间互相隔离.
而JsperLoader的加载范围仅仅是这个jsp文件所编译出来的一个.class文件,他出现的目的就是为了被丢弃,当web容器检测到jsp文件被修改时,会替换掉目前的JsperLoader实例再去重建一个新的,以实现jsp文件的热修改功能

tomcat不遵循双亲委派机制会有风险吗

tomcat不遵循双亲委派机制,只是自定义的ClassLoader加载顺序不同,没有去严格的遵循双亲委派机制(我个人觉得这个设计是为了给每个WebApp更好的设置隔离性,以避免互相干扰),但是核心的jdk的api也是遵循双亲委派去由顶层的加载器加载的(他自己的核心api也是有自己的加载器去专门加载,也没有恶意篡改的风险)

tomcat作为web容器,要解决的问题是什么
  • 一个web容器可能需要部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本,因此要保证每个应用程序的类库是独立的,保证相互的隔离性
  • 部署在用一个web容器中相同的类库相同的版本可以共享,否则就会出现多个相同的类加载进入虚拟机,显然这是要去尽力避免的
  • web容器自身也需要有一个自己的类库支持,他不能与应用的类库所混淆,基于安全上的考虑,应该让容器的类库和程序的类库相互隔离开
  • 支持jsp的热修改(那时候的jsp属于主流技术,还是非常流行的 现在都是动静分离,前后分离了)
如果Tomcat的CommonClassLoader想加载WebAppClassLoader中的类怎么办

用线程上下文加载器

为什么Java文件放在eclipse/idea中的src文件夹下会优先于jar包中的class

tomcat破坏双亲委派机制,自己指定的加载顺序 先使用应用类加载器在WEB-INF/classes中加载,再使用应用类加载器在WEB-INF/lib中加载

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

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

相关文章

C++-stack题型->最小栈,栈的压入与弹出,逆波兰表达式

目录 最小栈 栈的压入与弹出 逆波兰表达式 最小栈 155. 最小栈 - 力扣&#xff08;Leetcode&#xff09; 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时间内检索到最小元素的栈。 实现 MinStack 类: MinStack() 初始化堆栈对象。void …

App 测试流程及资料合集

/1 / 测试理论知识:跟其他 web 等测试一样; /2 / 整体测试流程:同 web 类似 具体差异化见下方内容 /3 / App 独特测试点: 客户端兼容性测试:系统版本、不同深度定制的 rom、屏幕分辨率、中断测试、 安装、卸载、升级、对其他程序的干扰等 需要的一些工具: appnium / lr / …

测试工程师转型开发?还是继续磨练测试技能?

测试五年&#xff0c;没有积累编程脚本能力和自动化经验&#xff0c;找工作时都要求语言能力&#xff0c;自动化框架。 感觉开发同事积累的经历容易找工作。 下一步&#xff0c;想办法转开发岗还是继续测试&#xff1f;&#xff1f;&#xff1f; 正常情况下&#xff0c;有了四年…

pdf怎么在线阅读?一键查阅并不难

PDF格式的文件已经成为现代生活中不可或缺的一部分&#xff0c;无论是学术论文、电子书、工作文件还是表格&#xff0c;都有可能以PDF格式出现。然而&#xff0c;为了读取这些文件&#xff0c;我们需要安装PDF阅读器&#xff0c;这在某些情况下可能会带来不便。因此&#xff0c…

如何修复缺失的mfplat.dll文件,多种修复mfplat.dl分享

当你在使用电脑时&#xff0c;突然遇到了缺失了mfplat.dll的错误提示&#xff0c;你可能会感到非常烦恼。不要担心&#xff0c;这是一个常见的问题。在本文中&#xff0c;我们将指导你如何修复缺失的mfplat.dll文件。 一.什么是mfplat.dll MFPLAT.DLL是Microsoft Windows操作系…

广和通发布5G RedCap模组FG132-NA,助力5G商用规模化

5月30日&#xff0c;全球领先的无线通信模组和解决方案提供商广和通发布5G RedCap模组FG132-NA&#xff0c;加速5G技术在更多物联网场景广泛应用。 FG132-NA符合3GPP Release17演进标准&#xff0c;为物联网终端带来卓越5G体验的同时&#xff0c;全面优化产品尺寸、功耗以及成本…

spring boot与spring cloud版本兼容问题解决(附版本兼容表)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【Matter】使用chip tool在ESP32-C3上进行matter开发

文章目录 使用chip tool在ESP32-C3上进行matter开发前提准备编译 chip-tool1.激活esp-matter环境2.编译matter所需环境3.构建CHIP TOOL chip-tool client 调试设备说明1.基于 BLE 调试2.通过IP与设备配对3.Trust store4.忘记当前委托的设备 使用chip-tool点灯1.matter环境激活2…

简单解决八皇后问题与n皇后问题

努力是为了不平庸~ 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。 目录 一、问题描述 二、问题解决思路 1. 建立数据结构&#xff1a; 2. 约束条件的实现&#xff1a; 3. 结果展示&#xff1a; 4. 拓展至n皇…

(STL之string)string类的用法详解

string类成员函数PART1 成员函数(构造函数拷贝构造函数)&#xff1a;string 函数原型&#xff1a; string(); string (const string& str); string (const string& str, size_t pos, size_t len npos); string (const char* s); string (const char* s, size_t n)…

项目中使用es(一):使用springboot操作elasticsearch

使用springboot操作es 写在前面搭建项目环境和选择合适版本具体的代码实现&#xff08;1&#xff09;继承ProductInfoRepository具体的代码实现&#xff08;2&#xff09;使用ElasticsearchRestTemplate操作问题总结最后放个demo 写在前面 对于elasticsearch的搭建&#xff0c…

【Top10】天津高性价比Web前端培训机构(Web前端需要掌握什么技能)

Web前端开发已经成为了一门备受瞩目的技能&#xff0c;对于一些初学者或者转行的人来说&#xff0c;也是非常友好的&#xff0c;当然越火也越会存在争议&#xff0c;大部分没有经验的人会选择参加培训来学习Web前端技术&#xff0c;也有不少人对于参加Web前端培训的必要性存在疑…

项目管理系统的设计与实现(ASP.NET,SQL)

开发环境&#xff1a;Microsoft Visual Studio 数据库&#xff1a;Microsoft SQL Server 程序语言&#xff1a;asp.NET(C#)语言本系统的开发使各大公司所的项目管理更加方便快捷&#xff0c;同时也促使项目的管理变的更加系统化、有序化。系统界面较友好&#xff0c;易于操作。…

AIGC下的低代码赛道,你我皆是拓荒人

今年年初&#xff0c;ChatGPT的现象级爆发&#xff0c;让其底层技术AIGC的承载方OpenAI备受关注。几重buff叠加&#xff0c;打工人的命运可以说是跌宕起伏&#xff0c;命途多舛了。在国内&#xff0c;AIGC的长期价值已逐渐被挖掘&#xff0c;正在重构人们的办公、娱乐乃至生活方…

启真医学大模型

启真医学大模型 QiZhenGPT: An Open Source Chinese Medical Large Language Model 本项目利用启真医学知识库构建的中文医学指令数据集&#xff0c;并基于此在LLaMA-7B模型上进行指令精调&#xff0c;大幅提高了模型在中文医疗场景下效果&#xff0c;首先针对药品知识问答发…

事务隔离级别-浅析

事务隔离级别是指在并发操作下&#xff0c;不同的事务之间互相隔离的程度。常见的事务隔离级别有以下四种&#xff1a; 读未提交&#xff08;Read Uncommitted&#xff09;&#xff1a;一个事务可以读取另一个未提交事务的数据。这样可能会导致脏读、不可重复读和幻读等问题。…

是德KEYSIGHT N9918A、N9917A 手持式射频和微波组合分析仪

是德&#xff08;KEYSIGHT) N9917A,N9918A 手持式射频和微波组合分析仪 Keysight N9918A FieldFox 组合分析仪能够处理日常维护、深度故障排除以及介于两者之间的任何事情。Keysight N9918A (Agilent) FieldFox 可随时随地为您提供高质量测量。将 FieldFox N9918A 添加到您的故…

面试官:你来说一下分布式锁的设计与实现

今天跟大家探讨一下分布式锁的设计与实现。希望对大家有帮助&#xff0c;如果有不正确的地方&#xff0c;欢迎指出&#xff0c;一起学习&#xff0c;一起进步哈~ 分布式锁概述 数据库分布式锁 Redis分布式锁 Zookeeper分布式锁 三种分布式锁对比 1. 分布式锁概述 我们的…

低代码制造ERP管理系统:降低开发成本,提高生产效率

随着制造业的快速发展&#xff0c;ERP管理系统成为了现代制造业中不可或缺的一部分。ERP管理系统可以帮助企业更好地管理生产流程、库存和供应链等方面&#xff0c;从而提高企业的生产效率和竞争力。然而&#xff0c;传统的ERP管理系统往往需要大量的编程工作和长周期的开发过程…

Kali渗透Windows服务器

这个实验主要让我们学习漏洞扫描技术基本原理&#xff0c;了解其在网络攻防中的作用&#xff0c;掌握使用Kali中的Metasploit对目标主机渗透&#xff0c;并根据报告做出相应的防护措施。 本次实战环境&#xff1a;Kali渗透Windows服务器 实战步骤一 本实验通过利用kali进行漏…