引用:博客上的专题图片可以在flickr上找到,并由Luca Galli创建。 以下部分之一中的图像也可以在flickr上找到,并由fklv(过时的时髦)创建。


GraalVM编译器是HotSpot的服务器端JIT编译 器的替代, 后者被广泛称为C2编译器 。 它是用Java编写的,目的是与C2编译器相比,具有更好的性能(除其他目标外)。 从Java 9开始的新更改意味着现在有了JVMCI ,我们可以将自己的手写C2编译器插入JVM。 Oracle实验室的研究人员和工程师)创建了启用JVMCI的JDK8变种,可用于构建GraalVM编译器 。 GraalVM编译器是开源的, 可在GitHub (以及构建GraalVM编译器所需的HotSpot JVMCI源 )上获得 。 这使我们能够分叉/克隆它并构建我们自己的GraalVM编译器版本。

在本文中,我们将在CircleCI上使用JDK8构建GraalVM编译器 。 产生的工件将是:

–嵌入GraalVM编译器的JDK8 ,以及
–包含Graal&Truffle模块/组件的zip存档。

注意:本文不介绍如何构建整个GraalVM套件,可以通过另一篇文章进行介绍。 尽管可以使用这些脚本,但是存在一个 分支,其中包含其余步骤。

为什么要使用CI工具来构建GraalVM编译器?


持续集成(CI)和持续部署(CD)工具有很多好处。 最大的功能之一就是能够检查代码库的运行状况。 了解构建失败的原因,可以为您提供更快修复的机会。 对于这个项目,重要的是我们能够验证和验证在本地和Docker容器中构建用于LinuxmacOS的GraalVM编译器所需的脚本。

CI / CD工具使我们可以添加自动化测试,以确保在合并每个PR时,可以从脚本中获得所需的结果。 除了确保我们的新代码不会带来重大变化之外,CI / CD工具的另一个重要功能是我们可以自动化二进制文件的创建和这些二进制文件的自动部署,从而使它们可用于开源分发。

让我们开始吧

在研究CircleCI作为构建GraalVM编译器的CI / CD解决方案的过程中,我了解到可以通过两种不同的方法来运行构建,即:

–使用标准Docker容器的CircleCI构建(更长的构建时间,更长的配置脚本)
–带有预先构建的,优化的Docker容器的CircleCI构建(更短的构建时间,更短的配置脚本)

现在,我们将通过上面提到的两种方法,来了解两种方法的利弊。

方法1:使用标准Docker容器

对于这种方法,CircleCI需要一个可在Docker Hub或其他有权访问的公共/私有注册表中使用的Docker映像。 为了成功构建,我们将必须在此可用环境中安装必要的依赖项。 我们希望构建第一次运行会更长,并且根据缓存的级别,它将加快运行速度。

要了解如何做到这一点,我们将通过CircleCI配置文件中去节逐节(存储在.circleci / circle.yml),见config.yml在.circleci的完整列表,请参阅提交df28ee7为源变化。

解释配置文件的各个部分

配置文件中的以下几行将确保缓存我们已安装的应用程序(指两个特定的目录),这样我们就不必在每次构建时都重新安装依赖项:

 dependencies: cache_directories: - "vendor/apt" - "vendor/apt/archives" 

我们将通过其全名来引用Docker映像(在http://hub.docker.com上可以使用所使用的帐户名– acceptopenjdk进行访问 )。 在这种情况下,这是一个包含JDK8的标准docker映像,由Adopt OpenJDK构建场背后的好人提供。 从理论上讲,只要支持构建过程,我们就可以使用任何图像。 它将充当我们将在其上安装必要依赖项的基础层:

 docker: - image: adoptopenjdk/openjdk8:jdk8u152-b16 

接下来,在预安装Os依赖项步骤中,我们将还原缓存(如果已经存在),这看起来可能有些奇怪,但是对于唯一的键标签, docs建议使用以下实现):

 - restore_cache: keys: - os-deps-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} - os-deps-{{ arch }}-{{ .Branch }} 

然后,在“ 安装操作系统依赖项”步骤中,我们运行相应的Shell脚本来安装所需的依赖项。 如果操作完成时间超过2分钟,则我们将此步骤设置为超时(请参阅docs以了解超时信息 ):

 - run: name: Install Os dependencies command: ./build/x86_64/linux_macos/osDependencies.sh timeout: 2m 

然后,在随后的“ 安装Os依赖项”步骤中,我们保存上一步的结果–上述运行步骤中的层(键名的格式设置为确保唯一性,并且包括要保存的特定路径):

 - save_cache: key: os-deps-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} paths: - vendor/apt - vendor/apt/archives 

然后,在通过脚本构建并安装make的预步骤中,如果缓存已经存在,我们将还原缓存:

 - restore_cache: keys: - make- 382 -{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} - make- 382 -{{ arch }}-{{ .Branch }} 

然后,在“ 通过脚本构建并安装make”步骤中,我们运行shell脚本以安装特定版本的make ,如果该步骤花费的时间超过1分钟,则将其设置为超时:

 - run: name: Build and install make via script command: ./build/x86_64/linux_macos/installMake.sh timeout: 1m 

然后,在通过脚本构建并安装make步骤中,我们将上述操作的结果保存到缓存中:

 - save_cache: key: make- 382 -{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} paths: - /make- 3.82 / - /usr/bin/make - /usr/local/bin/make - /usr/share/man/man1/make. 1 .gz - /lib/ 

然后,我们定义环境变量以在运行时更新JAVA_HOMEPATH 。 这里是环境变量的来源,以便我们在后续的后续步骤中记住它们,直到构建过程结束为止(请记住这一点):

 - run: name: Define Environment Variables and update JAVA_HOME and PATH at Runtime command: | echo '....' <== a number of echo-es displaying env variable values source ${BASH_ENV} 

然后,在显示硬件,软件,运行时环境和依赖项版本的步骤中 ,作为最佳实践,我们显示特定于环境的信息,并将其记录到日志中以供后代使用(在出错时进行调试时也很有用):

 - run: name: Display HW, SW, Runtime env. info and versions of dependencies command: ./build/x86_64/linux_macos/lib/displayDependencyVersion.sh 

然后,我们运行设置MX的步骤-从GraalVM编译器 ( mx )的角度来看,这一点很重要,该编译器是一个专门构建的系统,旨在促进Graal / GraalVM和组件的编译和构建:

 - run: name: Setup MX command: ./build/x86_64/linux_macos/lib/setupMX.sh ${BASEDIR} 

然后,如果该过程花费了超过15分钟的时间却没有任何输出,或者该过程总共花费了超过20分钟的时间,那么我们将执行重要的步骤以构建JDK JVMCI (在此处启用了JVMCI的情况下构建JDK)并超时:

 - run: name: Build JDK JVMCI command: ./build/x86_64/linux_macos/lib/build_JDK_JVMCI.sh ${BASEDIR} ${MX} timeout: 20m no_output_timeout: 15m 

然后,我们运行步骤Run JDK JVMCI Tests ,在构建JDK JVMCI之后运行该测试作为健全性检查的一部分:

 - run: name: Run JDK JVMCI Tests command: ./build/x86_64/linux_macos/lib/run_JDK_JVMCI_Tests.sh ${BASEDIR} ${MX} 

然后,我们运行设置环境和Build GraalVM Compiler步骤 ,以使用必要的环境变量来设置构建环境,后续步骤将使用这些变量:

 - run: name: Setting up environment and Build GraalVM Compiler command: | echo ">>>> Currently JAVA_HOME=${JAVA_HOME}" JDK8_JVMCI_HOME= "$(cd ${BASEDIR}/graal-jvmci-8/ && ${MX} --java-home ${JAVA_HOME} jdkhome)" echo "export JVMCI_VERSION_CHECK='ignore'" >> ${BASH_ENV} echo "export JAVA_HOME=${JDK8_JVMCI_HOME}" >> ${BASH_ENV} source ${BASH_ENV} 

然后,运行步骤Build GraalVM Compiler并将其嵌入到JDK(启用JVMCI的JDK8)中 ,如果该过程花费了超过7分钟的时间(没有任何输出)或总共花费了10分钟以上的时间来完成,则超时:

 - run: name: Build the GraalVM Compiler and embed it into the JDK (JDK8 with JVMCI enabled) command: | echo ">>>> Using JDK8_JVMCI_HOME as JAVA_HOME (${JAVA_HOME})" ./build/x86_64/linux_macos/lib/buildGraalCompiler.sh ${BASEDIR} ${MX} ${BUILD_ARTIFACTS_DIR} timeout: 10m no_output_timeout: 7m 

然后,在归档工件之前,我们运行简单的健全性检查以验证构建完成后创建的工件的有效性:

 - run: name: Sanity check artifacts command: | ./build/x86_64/linux_macos/lib/sanityCheckArtifacts.sh ${BASEDIR} ${JDK_GRAAL_FOLDER_NAME} timeout: 3m no_output_timeout: 2m 

然后,我们运行步骤归档工件 (意味着将最终工件压缩并将其复制到一个单独的文件夹中),如果该过程花费了超过2分钟的时间却没有任何输出或总共花费了超过3分钟的时间来完成,则会超时:

 - run: name: Archiving artifacts command: | ./build/x86_64/linux_macos/lib/archivingArtifacts.sh ${BASEDIR} ${MX} ${JDK_GRAAL_FOLDER_NAME} ${BUILD_ARTIFACTS_DIR} timeout: 3m no_output_timeout: 2m 

为了后代和调试目的,我们从各个文件夹中捕获生成的日志并将其存档:

 - run: name: Collecting and archiving logs (debug and error logs) command: | ./build/x86_64/linux_macos/lib/archivingLogs.sh ${BASEDIR} timeout: 3m no_output_timeout: 2m when: always - store_artifacts: name: Uploading logs path: logs/ 

最后,我们将生成的工件存储在指定的位置–以下几行将使该位置在CircleCI界面上可用(我们可以从此处下载工件):

 - store_artifacts: name: Uploading artifacts in jdk8-with-graal-local path: jdk8-with-graal-local/ 

方法2:使用预先构建的优化Docker容器

对于方法2,我们将使用预先构建的Docker容器,该容器已在本地创建并构建了所有必要的依赖项,保存了Docker映像,然后将其推送到远程注册表中,例如Docker Hub 。 然后,我们将通过配置文件在CircleCI环境中引用此docker映像。 这为我们节省了时间和精力来运行所有命令来安装必要的依赖关系,以创建用于此方法的必要环境(请参见方法1部分中的详细步骤)。

我们希望该构建与之前的构建相比可以运行更短的时间,并且这种加速是预先构建的docker镜像的结果(我们将在构建预先构建的docker镜像步骤部分中看到),以了解如何这个做完了)。 额外的速度优势来自CircleCI缓存docker映像层的事实,这反过来又导致了构建环境的更快启动。

我们将通过CircleCI配置文件中去节逐节(存储在.circleci / circle.yml)这种方法,请参阅config.yml在.circleci的完整列表,请参阅提交e5916f1为源的变化。

解释配置文件的各个部分

再次在这里,我们将通过全名引用docker映像。 这是neomatrix369提供的预构建的docker映像neomatrix369 / graalvm-suite- jdk8 。 在CircleCI构建开始之前,它已预先构建并上传到Docker Hub 。 它包含用于构建GraalVM编译器的必要依赖项:

 docker: - image: neomatrix369/graal-jdk8:${IMAGE_VERSION:-python- 2.7 } steps: - checkout 

下面的所有部分执行与方法1完全相同的任务(并出于相同的目的),请参阅解释配置文件部分的部分。

除此之外,我们删除了以下部分,因为方法2不再需要它们

 - restore_cache: keys: - os-deps-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} - os-deps-{{ arch }}-{{ .Branch }} - run: name: Install Os dependencies command: ./build/x86_64/linux_macos/osDependencies.sh timeout: 2m - save_cache: key: os-deps-{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} paths: - vendor/apt - vendor/apt/archives - restore_cache: keys: - make- 382 -{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} - make- 382 -{{ arch }}-{{ .Branch }} - run: name: Build and install make via script command: ./build/x86_64/linux_macos/installMake.sh timeout: 1m - save_cache: key: make- 382 -{{ arch }}-{{ .Branch }}-{{ .Environment.CIRCLE_SHA1 }} paths: - /make- 3.82 / - /usr/bin/make - /usr/local/bin/make - /usr/share/man/man1/make. 1 .gz 

在以下部分中,我将逐步展示如何构建预构建的Docker映像。 这将涉及运行bash脚本-./build/x86_64/linux_macos/osDependencies.sh./build/x86_64/linux_macos/installMake.sh,以安装必要的依赖项,作为构建docker映像的一部分。 并且,最后将映像推送到Docker Hub (可以推送到您选择的任何其他远程注册表)。

构建预构建的Docker映像的步骤

–运行build-docker-image.sh (请参阅bash脚本源代码),具体取决于Dockerfile的存在(请参阅docker脚本源代码)。 Dockerfile完成运行容器内依赖项的所有必要任务,即运行bash脚本./build/x86_64/linux_macos/osDependencies.sh./build/x86_64/linux_macos/installMake.sh:

 $ ./build-docker-image.sh 

–成功构建映像后, 在设置USER_NAMEIMAGE_NAME (请参阅源代码)之后运行push-graal-docker-image-to-hub.sh ,否则它将使用bash脚本中设置的默认值:

 $ USER_NAME= "[your docker hub username]" IMAGE_NAME= "[any image name]" \ ./push-graal-docker-image-to-hub.sh 

CircleCI配置文件统计信息:方法1与方法2

兴趣范围 方法1 方法2
配置文件(完整的源列表) 循环建立 使用预构建的docker-image进行构建
提交点(SHA) df28ee7 e5916f1
代码行(位置) 110线 85线
源行(sloc) 110格 85格
步骤(步骤:部分) 19 15
效果(请参见效果部分) 由于缓存, 速度有所提高,但比方法2慢 由于预构建的docker映像以及不同步骤的缓存而加快了速度。 比方法1更快的速度 确保已启用DLC分层(其付费功能)

该怎么办?

方法1的问题

我遇到了最初无法解决的问题,但后来通过更改配置文件或脚本进行了修复:

  • 请确保.circleci / config.yml始终位于文件夹的根目录中
  • .circleci / config.yml文件设置中使用store_artifacts指令时,请将值设置为固定文件夹名称,即jdk8-with-graal-local / –在我们的示例中,将路径设置为$ {BASEDIR} / project / jdk8一旦构建完成, -with-graal不会创建结果工件,因此建议使用固定的路径名​​。
  • 环境变量:使用环境变量时,请记住,每个命令都在自己的外壳中运行,因此在外壳执行环境内部看不到设置为环境变量的值,在外部看不到,请遵循本文所用的方法。 设置环境变量,以便所有命令都能看到其所需的值,以避免在每个步骤结束时出现异常行为或意外结果。
  • 缓存:阅读有关缓存的内容后,请使用缓存功能,有关CircleCI缓存的更多详细信息,请参阅缓存文档 。 在本文的上下文中了解它是如何实现的。 这将有助于避免混淆,还可以更好地利用CircleCI提供的功能。

方法2问题

  • 缓存:当尝试使用Docker Layer Caching (DLC)选项时,请检查文档,因为它是一项付费功能,一旦知道,对于“ Docker Layer”,对于“为何CircleCI在每次构建期间继续下载所有层”的疑问将得到澄清。缓存详细信息请参考docs 。 它还可以阐明为什么在非付费模式下我的构建仍然不如我希望的那样快。

一般说明:

  • 轻量级实例:为避免陷入思维障碍,我们可以运行重型构建,请查看有关实例技术规格的文档。 如果我们运行标准的Linux命令来探查实例的技术规格,我们可能会认为它们是高规格机器而被误导了。 请参阅列出实例的硬件和软件详细信息的步骤(请参阅显示硬件,软件,运行时环境信息和依赖性版本部分)。 实例实际上是具有2CPU / 4096MB资源的虚拟机或类似容器的环境。 这意味着我们不能像构建GraalVM套件那样运行长时间运行或繁重的构建。 也许有另一种方式来处理这类构建,或者可能需要将这些构建分解为较小的部分。
  • 全局环境变量:由于config.yml中的每个运行行都在其自己的shell上下文中运行,因此在该上下文中,其他正在执行的上下文所设置的环境变量无权访问这些值。 因此,为了克服这一点,我们采用了两种方法:
  • 作为变量传递给调用bash / shell脚本的参数,以确保脚本能够访问环境变量中的值
  • 使用source命令作为运行步骤,以使环境变量可全局访问

最终结果和摘要

构建成功完成后,我们看到以下屏幕(最后一步,即更新工件,列出了复制工件的位置):


现在,工件被放置在正确的文件夹中以进行下载。 我们主要关注jdk8-with-graal.tar.gz工件。

性能

在撰写本文之前,我对这两种方法进行了多次测试,并记下了完成构建所需的时间,如下所示:

方法1:标准CircleCI构建(启用缓存)
– 13分28秒
– 13分钟59秒 – 14分钟52秒 – 10分钟38秒 – 10分钟26秒 – 10分23秒 – 方法2:使用预先构建的docker映像(启用缓存, DLC )功能不可用) – 13分钟15秒 – 15分钟16秒 – 15分钟29秒 – 15分钟58秒 – 10分钟20秒 – 9分钟49秒

注意:使用付费层时,方法2应该表现出更好的性能,因为Docker Layer Caching (作为该计划的一部分)是可用的。

完整性检查

为了确保通过使用以上两种方法,我们实际上已经构建了嵌入GraalVM编译器的有效JDK,我们对创建的工件执行以下步骤:

–首先,从CircleCI仪表板上的“ 工件”选项卡下下载jdk8-with-graal.tar.gz工件(需要登录):


–然后,解压缩.tar.gz文件并执行以下操作:

配置文件(完整的源列表)

–此后,运行以下命令以检查JDK二进制文件是否有效:

配置文件(完整的源列表)

–最后检查是否得到以下输出:

配置文件(完整的源列表)

–同样,要确认JRE是否有效并内置了GraalVM编译器 ,我们可以这样做:

配置文件(完整的源列表)

–并检查是否获得与上述类似的输出:

配置文件(完整的源列表)

这样,我们就成功地构建了嵌入了GraalVM编译器的JDK8 ,并将Graal和Truffle组件捆绑在一个存档文件中,这两个文件都可以通过CircleCI界面下载。

注意:您会注意到,在将构建的二进制文件打包到压缩档案中之前,我们确实对其进行了完整性检查,这是构建步骤的一部分(请参阅CircleCI的底部部分的配置文件部分)。

漂亮的徽章!

我们都喜欢炫耀,也想知道我们建造工作的当前状态。 绿色的构建状态图标很好地表示成功,它类似于markdown README页面上的以下内容:


我们可以很容易地嵌入这两个状态标记,以显示我们在CircleCI(请参阅docs )上建立的项目的构建状态(特定于分支的,即master或您创建的另一个分支)(有关如何执行此操作)。

结论

我们探索了两种使用CircleCI环境构建GraalVM编译器的方法。 他们是比较两种方法的性能以及如何轻松实现它们的良好实验。 我们还看到了许多需要避免不要做的事情 ,还看到了一些CircleCI功能的有用性。 当尝试进行构建工作或遇到某些问题时,文档和论坛会很好地说明问题。

一旦我们了解了CircleCI环境,就可以轻松使用它,并且每次运行它时,都能始终给我们完全相同的响应(一致的行为)。 它的短暂性质意味着我们可以保证在每次运行之前都保持干净的环境,并在完成之后进行清理。 我们还可以为构建的每个步骤设置构建时间检查,如果完成某个步骤所花费的时间超过了阈值时间周期,则可以中止构建。

在CircleCI上使用预构建的Docker映像和Docker Layer Caching的能力可以极大地提高性能(为我们节省了在每次构建时重新安装任何必要依赖项所需的构建时间)。 CircleCI可以通过构建步骤的缓存来提高性能,这再次节省了构建时间,因为它们无需更改就无需重新运行相同的步骤。

CircleCI上有许多有用的功能以及大量文档,社区论坛上的每个人都很有帮助,并且很快就能回答问题。

接下来,让我们在另一个构建环境/构建服务器场上进行相同的构建,以及更多的内容-提示,提示,您是否认为与我相同? 采用OpenJDK构建场 )? 我们可以试试看!

感谢并感谢 CircleCI的 Ron Powell 和Oracle Labs的 OlegŠelajev进行了校对并提供了建设性的反馈。

请通过在下面的评论中添加一行或在 @ theNeomatrix369上发布 推文来 告知 我是否有 帮助 ,并且我也欢迎您提供反馈,看看您如何与 我联系 ,首先请查看上面提到的链接。

有用的资源

–链接到有用的CircleCI文档
– 关于入门 | 影片
– 关于Docker – Docker层缓存 – 关于缓存 – 关于通过SSH调试 – CircleCI速查表 – CircleCI社区(讨论) – 最新的社区话题 – CircleCI配置和支持文件 – 方法1: https //github.com/neomatrix369/awesome-graal/tree/build-on-circleci (配置文件和其他支持文件,例如脚本,目录布局等) – 方法2: https //github.com/neomatrix369/awesome-graal/tree/build-on-circleci-using-pre-built-docker-container (配置文件和其他支持文件,例如脚本,目录布局等… ) – 在Linux,macOS和Docker容器中构建Graal的脚本 – 松露在圣餐中使用:Graal和Truffle用于在JVM上进行多语种语言解释 – 学习使用Whally GraalVM! – 用松露打造全盛!

翻译自: https://www.javacodegeeks.com/2019/12/how-to-build-graal-enabled-jdk8-on-circleci.html

如何在CircleCI上构建支持Graal的JDK8?相关推荐

  1. graal java_如何在CircleCI上构建支持Graal的JDK8?

    graal java 引用:博客上的专题图片可以在flickr上找到,并由Luca Galli创建. 以下部分之一中的图像也可以在flickr上找到,并由fklv(过时的时髦)创建. GraalVM编 ...

  2. 如何在Docker上构建Node.js应用程序

    Docker has been the latest trending topic over the past couple of years and if you haven't been to s ...

  3. Harbor镜像库搭建以及如何在idea上构建镜像并推送Harbor

    一:Harbor镜像仓库搭建 1.软件准备: Docker 版本:20.10.6 Docker-compose 版本:v.2.4.0 2.docker 安装 下载docker-ce源到linux: c ...

  4. linux so 发布,如何在Linux上构建发布包含.so动态库的snap安装包

    Linux上要发布源码包比较容易,只要做个压缩包.tar.gz就可以.但是如果要做一个包含库和可执行文件的安装包,就比较麻烦.Linux的版本很多,需要做不同格式的安装包,比如.deb和.rpm,同时 ...

  5. Figma: 如何在 Web 上构建一个插件系统

    原文:https://www.figma.com/blog/how-we-built-the-figma-plugin-system/ 翻译:https://juejin.cn/post/684490 ...

  6. iframe如何发送请求_【第1803期】如何在 Web 上构建一个插件系统

    前言 上周末的这篇新闻[译]WebAssembly 1.0成为W3C推荐标准,也是在浏览器中运行的第四种语言不知道你注意到了吗?今日早读文章由@夏华翻译分享,本文由@刘岩推荐. 正文从这开始-- 在 ...

  7. java http ipv6_如何在Java上同时支持IPv4和IPv6

    小编典典 我怀疑这不是Java编程问题,而是OS网络堆栈/ OS网络配置问题: 在某些操作系统上,单个本机TCP套接字可以同时侦听IPv4和IPv6上的端口.它能够接受来自远程IPv4和远程IPv6客 ...

  8. linux php jsp网页,Linux上架设支持JSP+PHP的Web服务器

    近年来Linux在服务器市场占有比例日渐攀升,除了缘于Linux的免费和安全性之外,还因为Linux上的应用服务日益丰富.大部分常见的服务都在Linux上有了较好的解决方案.而对于Intenet上应用 ...

  9. qq群 voiceover_如何在iOS上使用VoiceOver为所有人构建应用程序

    qq群 voiceover by Jayven N 由Jayven N 如何在iOS上使用VoiceOver为所有人构建应用程序 (How to build apps for everyone usi ...

最新文章

  1. windowserver2008官方不提供POP3服务
  2. 网络安全第一讲 计算机网络安全概述
  3. 手机的寿命到底是多久,你多长时间换一次手机?
  4. 更改VMware虚拟机硬件版本
  5. idea怎么调试jsp页面_JSP+Servlet+C3P0+Mysql实现的YCU movies电影网站
  6. Router OS 全攻略
  7. 利用python并发模块进行网站的状态检测
  8. 由最近Win10升级事故,回忆一次经历
  9. linux监控内存情况同时清理内存脚本
  10. Base64原理及魔改更换码表
  11. 草图大师SketchUp2016下载和安装教程
  12. win7系统下文件夹重命名提示找不到该项目:的解决!
  13. linux查询文件重复记录,Linux 查询文件内容重复数 uniq
  14. 【微信小程序更改appid失败】微信小程序修改appid一直失败报错tourist appid解决办法
  15. 走着瞧Anbsp;Talenbsp;ofnbsp;Twonbsp;Donkeysnbsp;(200…
  16. word文档另存为pdf格式,如何设置导航栏
  17. 【MySQL】MySQL用 limit 为什么会影响性能?
  18. oracle基础--建库建表
  19. 文本与文本处理(一)
  20. wsl 上使用docker_首先通过在WSL和Docker中进行测试,将ASP.NET Core从Windows上的Azure应用服务迁移到Linux

热门文章

  1. P1169-[ZJOI2007]棋盘制作【贪心】
  2. 2018GDKOI——记录
  3. P3157 动态逆序对 ,树状数组套动态开点线段树
  4. 汇编语言(一)之反转字符串输出
  5. tomcat7.0.42如何设置mysql数据库连接池
  6. 03-映射文件的sql语句中 #{} 和 ${} 的区别以及实现模糊查询
  7. 【php】正则无法截取\反斜杠的解决方法
  8. 20级、19级 | 一天一瞬间!【日更】
  9. java实现动态验证码源代码——接受ajax的jsp
  10. sql server模糊查询、分组