[转帖]【全网首发】一些可以显著提高 Java 启动速度方法原创

https://heapdump.cn/article/4136322?from=pc

 

我们线上的业务 jar 包基本上普遍比较庞大,动不动一个 jar 包上百 M,启动时间在分钟级,拖慢了我们在故障时快速扩容的响应。于是做了一些分析,看看 Java 程序启动慢到底慢在哪里,如何去优化,目前的效果是大部分大型应用启动时间可以缩短 30%~50%

主要有下面这些内容

  • 修改 async-profiler 源码,只抓取启动阶段 main 线程的 wall 时间火焰图(✅)
  • 重新实现 JarIndex(✅)
  • 结合 JarIndex 重新自定义类加载器,启动提速 30%+(✅)
  • SpringBean 加载耗时 timeline 可视化分析(✅)
  • SpringBean 的可视化依赖分析(✅)
  • 基于依赖拓扑的 SpringBean 的异步加载(❌)

无观测不优化

秉承着无观测不优化的想法,首先我们要知道启动慢到底慢在了哪里。我之前分享过很多次关于火焰图的使用,结果很多人遇到问题就开始考虑火焰图,但是一个启动慢其实是一个时序问题,不是一个 hot  CPU 热点问题。很多时候慢,不一定是 cpu 占用过高,很有可能是等锁、等 IO 或者傻傻的 sleep。

在 Linux 中有一个杀手级的工具 bootchart 来分析 linux 内核启动的问题,它把启动过程中所有的 IO、CPU 占用情况都做了详细的划分,我们可以很清楚的看到各个时间段,时间耗在了哪里,基于这个 chart,你就可以看看哪些过程可以延后处理、异步处理等。

在 Java 中,暂时没有类似的工具,但是又想知道时间到底耗在了哪里要怎么做呢,至少大概知道耗在了什么地方。在生成热点调用火焰图的时候,我们通过 arthas 的几个简单的命令就可以生成,它底层用的是 async-profiler 这个开源项目,它的作者 apangin 做过一系列关于 jvm profiling 相关的分享,感兴趣的同学可以去看看。

async-profiler 底层原理简介

async-profiler 是一个非常强大的工具,使用 jvmti 技术来实现。它的 NB 之处在于它利用了 libjvm.so 中 JVM 内部的 API AsyncGetCallTrace 来获取 Java 函数堆栈,精简后的伪代码如下:

static bool vm_init(JavaVM *vm) {
    std::cout << "vm_init" << std::endl;
    
    // 从 libjvm.so 中获取 AsyncGetCallTrace 的函数指针句柄
    void *libjvm = dlopen("libjvm.so", RTLD_LAZY);
    _asyncGetCallTrace = (AsyncGetCallTrace) dlsym(libjvm, "AsyncGetCallTrace");
}

// 事件回调
void recordSample(void *ucontext, uint64_t counter, jint event_type, Event *event) {
    std::cout << "Profiler::recordSample: " << std::endl;

    ASGCT_CallFrame frames[maxFramesToCapture];

    ASGCT_CallTrace trace;
    trace.frames = frames;
    trace.env = getJNIEnv(g_jvm);

    // 调用 AsyncGetCallTrace 获取堆栈
    _asyncGetCallTrace(&trace, maxFramesToCapture, ucontext);
}

你可能要说获取个堆栈还需要搞这么复杂,jstack 等工具不是实现的很好了吗?其实不然。

jstack 等工具获取函数堆栈需要 jvm 进入到 safepoint,对于采样非常频繁的场景,会严重的影响 jvm 的性能,具体的原理不是本次内容的重点这里先不展开。

async-profiler 除了可以生成热点调用的火焰图,它还提供了 Wall-clock profiling 的功能,这个功能其实就是固定时间采样所有的线程(不管线程当前是 Running、Sleeping 还是 Blocked),它在文档中也提到了,这种方式的 profiling 适合用来分析应用的启动过程,我们姑且用这个不太精确的方式来粗略测量启动阶段耗时在了哪些函数里。

但是这个工具会抓取所有的线程的堆栈,按这样的方式抓取的 wall-clock 火焰图没法看,不信你看。

就算你找到了 main 线程,在函数耗时算占比的时候也不太方便,我们关心的其实只是 main 线程(也就是加载 jar 包,执行 spring 初始化的线程),于是我做了一些简单的修改,让 async-profiler 只取抓取 main 线程的堆栈。

重新编译运行

java 
-agentpath:/path/to/libasyncProfiler.so=start,event=wall,interval=1ms,threads,file=profile.html
-jar xxx.jar

这样生成的火焰图就清爽多了,这样就知道时间耗在了什么函数上。

接下来就是分析这个 wall-clock 的火焰图,点开几个调用栈仔细分析,发现很多时间花费在类和资源文件查找和加载(挺失望的,java 连这部分都做不好)

继续分析代码看看类加载在做什么。

Java 垃圾般实现的类查找加载

Java 的类加载不出意外最终都走到了 java.net.URLClassLoader#findClass 这里。

这里的 ucp 指的是 URLClassPath,也就是 classpath 路径的集合。对于 SpringBoot 的应用来说,classpath 已经在 META-INF 里写清楚了。

Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/

此次测试的程序 BOOT-INF/lib/ 有 300 多个依赖的 jar 包,当加载某个类时,除了 BOOT-INF/classes/ 之外 Java 居然要遍历那 300 个 jar 包去查看这些 jar 包中是否包含某个类。

我在 loader.getResource 上注入了一下打印,看看这些函数调用了多少次。

可以看到太丧心病狂了,加载一个类,居然要调用 loader.getResource 去 jar 包中尝试几百次。我就按二分之一 150 来算,如果加载一万个类,要调用这个函数 150W 次。

请忽略源码中的 LookupCache 特性,这个特性看起来是为了加速 jar 包查找的,但是这个特性看源码是一个 oracle 商业版的才有的特性,在目前的 jdk 中是无法启用的。(推测,如果理解不对请告知我)

于是有了一些粗浅的想法,为何不告诉 java 这个类在那个 jar 里?做索引这么天然的想法为什么不实现。

以下面为例,项目依赖三个 jar 包,foo.jar、bar.jar、baz.jar,其中分别包含了特定包名的类,理想情况下我们可以生成一个索引文件,如下所示。

foo.jar
com/foo1
com/foo2


bar.jar
com/bar
com/bar/barbar

baz.jar
com/baz

这就是我们接下来要介绍的 JarIndex 技术。

JarIndex 技术

其实 Jar 在文件格式上是支持索引技术的,称为 JarIndex,通过 jar -i 就可以在 META-INF/ 目录下生成 INDEX.LIST 文件。别高兴的太早,这个 JarIndex 目前无法真正起到作用,有下面几个原因:

  • INDEX.LIST 文件生成不正确,尤其是目前最流行的 fatjar 中包含 jar 列表的情况
  • classloader 不支持(那不是白忙活吗)

首先来看 INDEX.LIST 文件生成不正确的问题,随便拿一个 jar 文件,使用 jar -i 生成一下试试。

JarIndex-Version: 1.0

encloud-api_origin.jar
BOOT-INF
BOOT-INF/classes
BOOT-INF/classes/com
BOOT-INF/classes/com/encloud
....
META-INF
META-INF/maven
META-INF/maven/com.encloud
META-INF/maven/com.encloud/encloud-api
BOOT-INF/lib
org
org/springframework
org/springframework/boot
org/springframework/boot/loader
org/springframework/boot/loader/jar
org/springframework/boot/loader/data
org/springframework/boot/loader/archive
org/springframework/boot/loader/util

可以看到在 BOOT-INF/lib 目录中的类索引并没有在这里生成,这里面可是有 300 多个 jar 包。

同时生成不对的地方还有,org 目录下只有文件夹并没有 class 文件,org 这一行不应该在 INDEX.LIST 文件中。

第二个缺陷才是最致命的,目前的 classloader 不支持  JarIndex 这个特性。

所以我们要做两个事情,生成正确的 JarIndex,同时修改 SpringBoot 的 classloader 让其支持 JarIndex。

生成正确的 JarIndex

这个简单,就是遍历 jar 包里的类,将其所在的包名抽取出来。SpringBoot 应用有三个地方存放了 class:

  • BOOT-INF/classes
  • BOOT-INF/lib
  • jar 包根目录下 org/springframework/boot/loader

生成的时候需要考虑到上面的情况,剩下的就简单了。遍历这些目录,将所有的包含 class 文件的包名过滤过来就行。

大概生成的结果是:

JarIndex-Version: 1.0

encloud-api.jar

/BOOT-INF/classes
com/encloud
com/encloud/app/controller
com/encloud/app/controller/v2

/
org/springframework/boot/loader
org/springframework/boot/loader/archive
org/springframework/boot/loader/data
org/springframework/boot/loader/jar
org/springframework/boot/loader/util

/BOOT-INF/lib/spring-core-4.3.9.RELEASE.jar
org/springframework/a**
org/springframework/cglib
org/springframework/cglib/beans
org/springframework/cglib/core

/BOOT-INF/lib/guava-19.0.jar
com/google/common/annotations
com/google/common/base
com/google/common/base/internal
com/google/common/cache

... other jar ...

除了加载类需要查找,其实还有不少资源文件需要查找,比如 spi 等扫描过程中需要,顺带把资源文件的索引也生成一下写入到 RES_INDEX.LIST 中,原理类似,这里展开。

自定义 classloder

生成了 INDEX.LIST 文件,接下来就是要实现了一个 classloader 能支持一步到位通过索引文件去对应的 jar 包中去加载 class,核心的代码如下:

public class JarIndexLaunchedURLClassLoader extends URLClassLoader {
    public JarIndexLaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, ClassLoader parent) {
        super(urls, parent);
        initJarIndex(urls); // 根据 INDEX.LIST 创建包名到 jar 文件的映射关系
    }
    
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        Class<?> loadedClass = findLoadedClass(name);
        if (loadedClass != null) return loadedClass;

        // 如果是 loader 相关的类,则直接加载,不用找了,就在 jar 包的根目录下
        if (name.startsWith("org.springframework.boot.loader.") || name.startsWith("com.seewo.psd.bootx.loader.")) {
            Class<?> result = loadClassInLaunchedClassLoader(name);
            if (resolve) {
                resolveClass(result);
            }
            return result;
    
        }
        // skip java.*, org.w3c.dom.* com.sun.* ,这些包交给 java 默认的 classloader 去处理
        if (!name.startsWith("java") && !name.contains("org.w3c.dom.") 
                && !name.contains("xml") && !name.startsWith("com.sun")) {
            int lastDot = name.lastIndexOf('.');
            if (lastDot >= 0) {
                String packageName = name.substring(0, lastDot);
                String packageEntryName = packageName.replace('.', '/');
                String path = name.replace('.', '/').concat(".class");

                // 通过 packageName 找到对应的 jar 包
                List<JarFileResourceLoader> loaders = package2LoaderMap.get(packageEntryName);
                if (loaders != null) {
                    for (JarFileResourceLoader loader : loaders) {
                        ClassSpec classSpec = loader.getClassSpec(path); // 从 jar 包中读取文件
                        if (classSpec == null) {
                            continue;
                        }
                        // 文件存在,则加载这个 class
                        Class<?> definedClass = defineClass(name, classSpec.getBytes(), 0, classSpec.getBytes().length, classSpec.getCodeSource());
                        definePackageIfNecessary(name);
                        return definedClass;
                    }
                }
            }
        }
        // 执行到这里,说明需要父类加载器来加载类(兜底)
        definePackageIfNecessary(name);
        return super.loadClass(name, resolve);
    }
}      

到这里我们基本上就实现了一个支持 JarIndex 的类加载器,这里的改动经实测效果已经效果非常明显。

除此之外,我还发现查找一个已加载的类是一个非常高频执行的操作,于是可以在 JarIndexLaunchedURLClassLoader 之前再加一层缓存(思想来自 sofa-boot)

public class CachedLaunchedURLClassLoader extends JarIndexLaunchedURLClassLoader {
    private final Map<String, LoadClassResult> classCache = new ConcurrentHashMap<>(3000); 
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        return loadClassWithCache(name, resolve);
    }
    private Class<?> loadClassWithCache(String name, boolean resolve) throws ClassNotFoundException {
        LoadClassResult result = classCache.get(name);
        if (result != null) {
            if (result.getEx() != null) {
                throw result.getEx();
            }
            return result.getClazz();
        }

        try {
            Class<?> clazz = super.findLoadedClass(name);
            if (clazz == null) {
                clazz = super.loadClass(name, resolve);
            }
            if (clazz == null) {
                classCache.put(name, LoadClassResult.NOT_FOUND);
            }
            return clazz;
        } catch (ClassNotFoundException exception) {
            classCache.put(name, new LoadClassResult(exception));
            throw exception;
    }
}

注意:这里为了简单示例直接用 ConcurrentHashMap 来缓存 class,更好的做法是用 guava-cache 等可以带过期淘汰的 map,避免类被永久缓存。

如何不动 SpringBoot 的代码实现 classloader 的替换

接下的一个问题是如何不修改 SpringBoot 的情况下,把 SpringBoot 的 Classloader 替换为我们写的呢?

大家都知道,SpringBoot 的 jar 包启动类其实并不是我们项目中写的 main 函数,其实是

org.springframework.boot.loader.JarLauncher,这个类才是真正的 jar 包的入口。

package org.springframework.boot.loader;

public class JarLauncher extends ExecutableArchiveLauncher {

 public static void main(String[] args) throws Exception {
  new JarLauncher().launch(args);
 }

那我们只要替换这个入口类就可以接管后面的流程了。如果只是替换那很简单,修改生成好的 jar 包就可以了,但是这样后面维护的成本比较高,如果在打包的时候就替换就好了。SpringBoot 的打包是用 spring-boot-maven-plugin 插件

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>    

最终生成的 META-INF/MANIFEST.MF 文件如下

$ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Implementation-Title: encloud-api
Implementation-Version: 2.0.0-SNAPSHOT
Archiver-Version: Plexus Archiver
Built-By: arthur
Implementation-Vendor-Id: com.encloud
Spring-Boot-Version: 1.5.4.RELEASE
Implementation-Vendor: Pivotal Software, Inc.
Main-Class: org.springframework.boot.loader.JarLauncher
Start-Class: com.encloud.APIBoot
Spring-Boot-Classes: BOOT-INF/classes/
Spring-Boot-Lib: BOOT-INF/lib/
Created-By: Apache Maven 3.8.5
Build-Jdk: 1.8.0_332
Implementation-URL: http://projects.spring.io/spring-boot/parent/enclo
 ud-api/

为了实现我们的需求,就要看 spring-boot-maven-plugin 这个插件到底是如何写入 Main-Class 这个类的,经过漫长的 maven 插件源码的调试,发现这个插件居然提供了扩展点,可以支持修改 Main-Class,它提供了一个 layoutFactory 可以自定义

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
            <configuration>
                <layoutFactory implementation="com.seewo.psd.bootx.loader.tools.MyLayoutFactory"/>
            </configuration>
        </execution>
    </executions>
    <dependencies>
        <dependency>
            <groupId>com.seewo.psd.bootx</groupId>
            <artifactId>bootx-loader-tools</artifactId>
            <version>0.1.1</version>
        </dependency>
    </dependencies>
</plugin>

实现这个

package com.seewo.psd.bootx.loader.tools;

import org.springframework.boot.loader.tools.*;

import java.io.File;
import java.io.IOException;
import java.util.Locale;

public class MyLayoutFactory implements LayoutFactory {
    private static final String NESTED_LOADER_JAR = "META-INF/loader/spring-boot-loader.jar";
    private static final String NESTED_LOADER_JAR_BOOTX = "META-INF/loader/bootx-loader.jar";
    
    public static class Jar implements RepackagingLayout, CustomLoaderLayout {
        @Override
        public void writeLoadedClasses(LoaderClassesWriter writer) throws IOException {
            // 拷贝 springboot loader 相关的文件到 jar 根目录
            writer.writeLoaderClasses(NESTED_LOADER_JAR);
            // 拷贝 bootx loader 相关的文件到 jar 根目录
            writer.writeLoaderClasses(NESTED_LOADER_JAR_BOOTX); 
        }

        @Override
        public String getLauncherClassName() {
            // 替换为我们自己的 JarLauncher
            return "com.seewo.psd.bootx.loader.JarLauncher";
        }
    }
}

接下来实现我们自己的 JarLauncher

package com.seewo.psd.bootx.loader;

import java.net.URL;

public class JarLauncher extends org.springframework.boot.loader.JarLauncher {

    @Override
    protected ClassLoader createClassLoader(URL[] urls) throws Exception {
        return new CachedLaunchedURLClassLoader(urls, getClass().getClassLoader());
    }

    public static void main(String[] args) throws Exception {
        new JarLauncher().launch(args);
    }
}

重新编译就可以实现替换

$ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
...
Main-Class: com.seewo.psd.bootx.loader.JarLauncher
...

到这里,我们就基本完成所有的工作,不用改一行业务代码,只用改几行 maven 打包脚本,就可以实现支持 JarIndex 的类加载实现。

优化效果

我们来看下实际的效果,项目 1 稍微小型一点,启动耗时从 70s 降低到 46s

第二个 jar 包更大一点,效果更明显,启动耗时从 220s 减少到 123s

未完待续

其实优化到这里,还远远没有达到我想要的目标,为什么启动需要这么长时间,解决了类查找的问题,那我们来深挖一下 Spring 的初始化。

Spring bean 的初始化是串行进行的,于是我先来做一个可视化 timeline,看看到底是哪些 Bean 耗时很长。

Spring Bean 初始化时序可视化

因为不会写前端,这里偷一下懒,利用 APM 的工具,把数据上报到 jaeger,这样我们就可以得到一个包含调用关系的timeline 的界面了。jaeger 的网址在这里:https://www.jaegertracing.io/

首先我们继承 DefaultListableBeanFactory 来对 createBean 的过程做记录。

public class BeanLoadTimeCostBeanFactory extends DefaultListableBeanFactory {

    private static ThreadLocal<Stack<BeanCreateResult>> parentStackThreadLocal = new ThreadLocal<>();


    @Override
    protected Object createBean(String beanName, RootBeanDefinition rbd, Object[] args) throws BeanCreationException {
        // 记录 bean 初始化开始
        Object object = super.createBean(beanName, rbd, args);
        // 记录 bean 初始化结束
        return object;
    }

接下来我们实现 ApplicationContextInitializer,在 initialize 方法中替换 beanFactory 为我们自己写的。

public class BeanLoadTimeCostApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
    public BeanLoadCostApplicationContextInitializer() {
        System.out.println("in BeanLoadCostApplicationContextInitializer()");
    }

    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        if (applicationContext instanceof GenericApplicationContext) {
            System.out.println("BeanLoadCostApplicationContextInitializer run");
            BeanLoadTimeCostBeanFactory beanFactory = new BeanLoadTimeCostBeanFactory();
            Field field = GenericApplicationContext.class.getDeclaredField("beanFactory");
            field.setAccessible(true);
            field.set(applicationContext, beanFactory);
        }
    }
}

接下来将记录的状态上报到 jaeger 中,实现可视化堆栈显示。

public void reportBeanCreateResult(BeanCreateResult beanCreateResult) {
        Span span = GlobalTracer.get().buildSpan(beanCreateResult.getBeanClassName()).withStartTimestamp(beanCreateResult.getBeanStartTime() * 1000).start();

        try (Scope ignore = GlobalTracer.get().scopeManager().activate(span)) {
            for (BeanCreateResult item : beanCreateResult.getChildren()) {
                Span childSpan = GlobalTracer.get().buildSpan(item.getBeanClassName()).withStartTimestamp(item.getBeanStartTime() * 1000).start();

                try (Scope ignore2 = GlobalTracer.get().scopeManager().activate(childSpan)) {
                    printBeanStat(item);
                } finally {
                    childSpan.finish(item.getBeanEndTime() * 1000);
                }
            }
        } finally {
            span.finish(beanCreateResult.getBeanEndTime() * 1000);
        }
}

通过这种方式,我们可以很轻松的看到 spring 启动阶段 bean 加载的 timeline,生成的图如下所示。

这对我们进一步优化 bean 的加载提供了思路,可以看到 bean 的依赖关系和加载耗时具体耗在了哪个 bean。通过这种方式可以在 SpringBean 串行加载的前提下,把 bean 的加载尽可能的优化。

SpringBean 的依赖分析

更好一点的方案是基于 SpringBean 的依赖关系做并行加载。这个特性 2011 年前就有人提给了 Spring,具体看这个 issue:https://github.com/spring-projects/spring-framework/issues/13410

就在去年,还有人去这个 issue 下去恭祝这个 issue 10 周年快乐。

做并行加载确实有一些难度,真实项目的 Spring Bean 依赖关系非常复杂,我把 Spring Bean 的依赖关系导入到 neo4j 图数据库,然后进行查询

MATCH (n)
RETURN n;

得到的图如下所示。一方面 Bean 的数量特别多,还有复杂的依赖关系,以及循环依赖。

基于此依赖关系,我们是有机会去做 SpringBean 的并行加载的,这部分还没实现,希望后面有机会可以完整的实现这块的逻辑,个人感觉可以做到 10s 内启动完一个超大的项目。

Java 启动优化的其它技术

Java 启动的其它技术还有 Heap Archive、CDS,以及 GraalVM 的 AOT 编译,不过这几个技术目前都有各自的缺陷,还无法完全解决目前我们遇到的问题。

后记

这篇文章中用到的技术只是目前比较粗浅的尝试,如果大家有更好的优化,可以跟我交流,非常感谢。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

博客园美化:canvas炫酷背景

话不多说,先上效果图:yysy,这个当背景确实酷炫!!!😄 页脚HTML代码:1 <script src="https://files.cnblogs.com/files/zhonglinke/vendors.js"></script>2 <script type="text/javascript" language="javascript">3 4…

APC挂靠

5.APC挂靠 用户态apc 和上一课的内核apc几乎一致,唯一的变动就是这个 //插入当前线程KeInitializeApc(pKapc, eThread, OriginalApcEnvironment, KernelAPCRoutineFunc, NULL, 0x4011d0, UserMode, NULL);改成了UserMode函数地址改成了进程的地址0x4011d0 完整代码 Driver-mai…

Java(7)-Maven抽取公共模块构建jar包

前提假设:项目中有两个Moudle,分别是Moudle1和Moudle2,它们有些代码是相同的,比如相同的类和API,重复书写当然可以正常运行,但是我们可以用maven打包成jar包,其他Moudle直接引用即可。 步骤 1.新建一个Module-common pox.xml 中配置 Module1 和 Moudle2 同样使用的依赖:…

KPCR进程概念

1.KPCR进程概念 KPCR 介绍 KPCR 是CPU的控制结构 FS段寄存器在R0(FS=0x30)的时候指向KPCR结构 FS 段寄存器在R3(FS=0x3b)的时候指向 当前线程的TEB(线程) 线程结构是运行在CPU上面,所以线程结构是放在CPU上的 kd> dt _KPCR ntdll!_KPCR+0x000 NtTib : _NT_TIB…

驱动断链

03驱动断链获取驱动信息的途径是从Driver和FileSystem两个目录下获得的正常情况下我们自己做驱动遍历只能便利Driver下的所有驱动,也就是驱动链表里的驱动 分析 这里我们F12进去找一下DRIVER_OBJECT的结构 typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT; WinDbg里dt一下 kd&…

蓝屏分析

04蓝屏分析 直接上蓝屏代码 #include <ntifs.h>VOID Unload(PDRIVER_OBJECT pDriver) {DbgPrint("Driver Unload\r\n"); }NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {pDriver->DriverUnload = Unload;//DbgBreakPoint();DbgPrin…

Qt学习第二篇(基本小组件的使用)

Qt_2 小部件是 GUI 的基本元素。 它也称为UI 控件。 它接受来自底层平台的不同用户事件,如鼠标和键盘事件(以及其他事件)。 我们使用不同的小部件创建 UI。 曾经有一段时间,所有的 GUI 控件都是从头开始编写的。 Qt 小部件通过开发具有现成的 GUI 控件的桌面 GUI 来缩短时间,…

Weblogic T3反序列化漏洞(CVE-2018-2628)

在Weblogic中RMI通信的实现是使用T3协议,并且在T3的传输过程中,和RMI一样,会进行序列化和反序列化的操作。目录前言T3协议概述漏洞复现修复方案 前言 WebLogic Server 是一个企业级的应用服务器,由Oracle公司开发,支持完整的Java EE规范,包括EJB、JSP、Servlet、JMS等,适…

方方方的数据结构

总算给我看懂到底是什么意思了。。。 首先我们来考虑按照时间+扫描线进行处理,假设操作如下黑色是加操作,黄色是乘操作,绿色是加操作,对于红色那条线所代表的点,随着时间的流逝,首先在刚刚进入黑色的时候,这一点的值就被加上了一个数,然后刚刚进入黄色的时候,这一点的…

手机硬件检测:-DeviceTest

手机硬件检测:Z-DeviceTest官方版是款针对手机硬件所打造的检测工具。手机硬件检测:Z-DeviceTest能够检测硬件和OS,硬件上不仅仅是电池、cpu、内存、OS,甚至连usb、扬声器、指南针、摄像头、GPS、听筒等都能检测。并且手机硬件检测:Z-DeviceTest还能够对市面众多的机型进行检…

OutOfMemoryError

以下的这段代码应该是报错的才对,但是我在运行了之后,程序一直卡在那里。最后请教老师了解到,原来jvm如果不指定运行参数,是会进行自动扩容的。 package com.coding.jvm.oom;public class NativeErrorDemo {public static void main(String[] args) {for (; ; ) {new Threa…

【工具使用】【Shell脚本】【gitlab】下拉所有的仓库代码

1 前言 电脑重置了或者新的项目代码,仓库里二三十个,一个一个拉属实有点拉跨,今儿空了整了个脚本,可以拉下所有的仓库代码。 2 前置 需要装一个解析 json的,windows 的话可以直接下载:下载,mac的话可以再官网下载:官网地址。 然后加入到 PATH 下,效果如下:3 脚本 …