热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

rasp系统_RASP研发踩坑之解释器与编译器

怪力乱神一般认为开启注入后,http调用栈变长,响应时间变长了,系统的QPS下降,但是我的组员在对RASP进行性能压测时发现

怪力乱神

一般认为开启注入后,http调用栈变长,响应时间变长了,系统的 QPS下降,但是我的组员在对RASP 进行性能压测时发现了一个“奇怪的事情”,相比于没有开启注入的场景,开启注入之后系统的 QPS 没有降低反而更大了。

我重现了她的测试过程。首先,启动一个 web服务,压测得到 QPS

不开启注入时文件上传接口 QPS为296

开启注入时文件上传接口 QPS为300

从上面的2张图我发现了一个问题,测试时先测了不开启注入的场景,测试完成之后,web 进程没有重启,接着开启注入,再次测试,QPS 没有降低(或者变化不明显)。

初步怀疑与 web 进程在压测时系统经过了一个预热的过程(JIT编译),等到再次开启注入时,系统的 QPS 没有明显变化。

再次测试,先测试不开启注入时的 QPS,进程重启之后,开启注入,测试 QPS

测试数据:

不开启注入时文件上传接口 QPS为242

开启注入时文件上传接口 QPS为224

开启注入后对文件上传的 QPS 有影响,QPS 下降 (242-224)/ 242=7.4%

测试结论的差异在于正常的压测后是否重启了进程,这个对 RASP 的影响有较大的影响,并且测试结果还说明了一点,进过预热的后, RASP性能影响会降低,最后影响趋近于0.

下面回顾下 JVM 的解释器与编译器,加深对这个过程的理解。

概述

在JVM 中,Java 程序运行之初都是通过解释器解释执行,运行时某些方法或代码块调用超过设定的阈值后(热点代码),为了提高热点代码的执行效率,虚拟机会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器被称为即时编译器(Just In Time Compiler,简称 JIT 编译器)。

Hotspot 虚拟机内的即时编译器

本节介绍 Hotspot 虚拟机内的即时编译器的运作过程,同时还要解决以下几个问题:

1. 为何 Hotspot 虚拟机要使用解释器和编译器并存的架构?

2. 为何 Hotspot 虚拟机要实现两个不同的即时编译器?

3. 程序何时使用解释器执行?何时使用编译器执行?

4. 哪些程序代码会被编译为本地代码?如何编译为本地代码?

解释器与编译器

尽管不是所有的 Java 虚拟机都采样解释器与编译器并存的价格,但是许多主流的虚拟机,比如 Sun Hotspot、IBM J9,都同时包含解释器与编译器。解释器与编译器有各自的优势:当程序需要快速启动时,解释器可以发挥作用,省去编译时间立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率。

当程序运行环境的内存资源限制较大时,使用解释器执行节省内存,反之可以使用编译执行提升效率。同时,解释器还可以作为编译器激进优化时的一个“逃生门”,让编译器根据概率选择一个大多数时候都能提升运行速度的优化手段,当激进优化的假设不成立时,比如加载了新类后类型继承结构出现变化,出现“罕见陷阱”时可以通过逆优化退回到解释状态继续执行。因此,在虚拟机中解释器和编译器经常配合工作,如下图所示:

[图片上传失败...(image-c08c7a-1592405406505)]

HotSpot 虚拟机内置了两个即时编译器:Client Compiler 和 Server Compiler,简称 C1 编译器和 C2 编译器。默认采用解释器和其中一个编译器直接配合的方式工作,具体使用哪个编译器,取决于虚拟机工作的模式,用户可以使用 -client 参数或 -server 参数指定虚拟机的工作模式,还可以使用 -Xint 强制虚拟机运行于“解释模式”。

由于即时编译器编译本地代码需要占用程序运行时间,要编译出优化程度更高的代码,所需时间会更长。同时,解释器还要替编译器收集性能监控信息,这对解释执行速度也有影响。为了在程序启动响应速度与运行效率之间达到最佳平衡,HotSpot 虚拟机又引入了分层编译的策略。分层编译根据编译器编译、优化的规模与耗时,划分为不同的编译层次,包括:

1. 第 0 层,程序解释执行,不开启性能监控功能,可触发第 1 层编译。

2. 第 1 层,称为 C1 编译,将字节码编译为本地代码,并进行简单可靠的优化,如有必要将加入性能监控逻辑。

3. 第 2 层,称为 C2 编译,也是将字节码编译为本地代码,但是会进行耗时较长的优化,甚至会根据性能监控信息进行一些不完全可靠的激进优化。

实施分层编译后,Client Compiler 和 Server Compiler 会同时工作,许多代码可能会被编译多次,用 Client Compiler 获得更快的编译速度,用 Server Compiler 获取更好的编译质量,在解释执行的时候也无需再承担收集性能监控信息的任务。

编译对象与触发条件

在运行过程中,会被即时编译器编译的热点代码有两类:

1. 被多次调用的方法。

2. 被多次执行的循环体。

这两种情况,编译器都会编译整个方法。因为编译发生在方法执行过程中,因此形象地称之为栈上替换(On Stack Replacement,简称 OSR,即方法栈帧还在栈上,方法就被替换了)。

判断一段代码是不是热点代码,是否需要触发即时编译,这样的行为称为热点探测(Hot Spot Detection),热点探测方式主要有两种:

1. 基于采样的热点探测:采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果某个方法经常出现在栈顶,那它就是热点方法。其优点是简单、高效,还可以获取方法调用关系;缺点是不够精确,容易受到线程阻塞或其他外接因素的影响。

2. 基于计数的热点探测:采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法执行次数,次数超过一定阈值就认为是热点方法。这种方法实现起来麻烦,但是其统计结果相对来说更加精确和严谨。

在 HotSpot 虚拟机里使用的是第二种方法,因此它为每个方法准备了两类计数器:方法调用计数器(Invocation Counter)和回边计数器(Back edge Counter)。在确定虚拟机运行参数的情况下,这两个计数器都有一定的阈值,超过阈值就会触发 JIT 编译。

方法调用计数器

方法调用计数器用于统计方法被调用的次数,其默认阈值在 Client 模式下是 1500,在 Server 模式下是 10000,该阈值可以通过虚拟机参数 -XX:CompileThreshold 来设置。方法调用计数器与 JIT 编译的交互如下:

[图片上传失败...(image-b03abc-1592405406505)]

默认情况下,方法调用计数器统计的不是方法被调用的绝对次数,而是一段时间内的方法被调用的次数。当超过一段的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半。这个过程称为热度衰减(Counter Decay),这段时间称为方法统计的半衰周期(Counter Half Life Time)。进行热度衰减的动作是虚拟机在垃圾收集时顺便进行的,可以使用虚拟机参数 -XX:-UseCounterDecay 来关闭热度衰减。另外,还可以使用 -XX:CounterHalfLifeTime 来设置半衰周期的时间,单位是秒。

回边计数器

回边计数器的作用是统计方法体中循环体代码执行次数,在字节码中遇到遇到控制流向后调整的指令称为回边。显然,建立回边计数器统计的目的就是为了触发 OSR 编译。在 HotSpot 虚拟机里,通过参数 -XX:OnStackReplacePercentage 来间接调整回边计数器的阈值,其计算公式如下:

1. Client 模式下,回边计数器阈值计算公式为:方法调用计数器阈值 * OnStackReplacePercentage / 100

2. Server 模式下,回边计数器阈值计算公式为:方法调用计数器阈值 * (OnStackReplacePercentage - 解释器监控比率) / 100

回边计数器触发即时编译的过程如下所示:

[图片上传失败...(image-68a081-1592405406505)]

与方法计数器不同,回边计数器没有衰减过程,因此统计的就是绝对次数。当计数器溢出的时候,它会把方法计数器也调整到溢出状态,它还会把方法计数器也调整到溢出状态,这样下次再进入该方法时就会触发即时编译。

在 HotSpot 虚拟机的源码里,MethodOop.hpp 文件定义了虚拟机中的内存布局,如下所示:

[图片上传失败...(image-caa2b4-1592405406505)]

在这个内存布局中,一行长度为 32bit,从中可以清楚地看到方法调用计数器和回边计数器的位置和长度,还有 from_compiled_entry 和 from_interpreted_entry 这两个方法的入口。

编译过程

Client Compiler

默认情况下,即时编译是在后台进行的,编译完成之前还是按照解释方式执行,用户可以通过参数 -XX:-BackgroundCompilation 来禁止后台编译。

那么在后台编译过程中,做了什么事情呢?Client Compiler 和 Server Compiler 两个编译器的编译过程是不一样的。Client Compiler 是一个简单快速的三段式编译器,主要关注点在于局部优化,放弃了许多耗时的全局优化手段。

1. 在第一个阶段,一个平台独立的前端将字节码构造成一种高级中间代码(High Level Intermediate Representation,HIR)表示。HIR 使用静态单分配的形式来代表代码值,这使得一些在 HIR 之后和之中进行的优化动作更容易实现。在此之前编译器会在字节码上完成一部分基础优化,如方法内敛、常量传播等。

2. 在第二个阶段,一个平台相关的后端从 HIR 中产生低级中间代码表示(Low Level Intermediate Representation,LIR)表示。在此之前,会在 HIR 上完成另一些优化,比如空值检查消除、范围检查消除,以便让 HIR 达到更高效的代码表示形式。

3. 最后阶段,是在平台相关的后端,使用线性扫描算法在 LIR 上分配寄存器,并在 LIR 上做窥孔优化,然后产生机器代码。

Client Compiler 大致执行过程如下图所示:

[图片上传失败...(image-b0e74-1592405406505)]

Server Compiler

Server Compiler 是面向服务端的,并且为服务端性能配置进行了特别调整,是一个充分优化过的高级编译器,几乎能达到 GNU 编译器使用 -O2 参数时的优化强度。它会执行所有经典的优化动作,比如无用代码消除、循环展开、循环表达式外提、消除公共子表达式、常量传播、基本块重排序等,还会实施一些与 Java 语言特征密切相关的技术,比如范围检查消除、空值检查消除。另外,还可能根据解释器或 Client Compiler 提供的性能监控信息,进行一些不稳定的激进优化,如守护内联、分支频率预测等。后面会挑选部分优化手段进行详细的讲解。

Server Compiler 的寄存器分配器是一个全局图着色分配器,它可以充分利用某些处理器架构上的大寄存器集合。以即时编译的标准来看,Server Compiler 无疑是比较缓慢的,但它的编译速度依然超过传统的静态优化编译器,而且相对于 Client Compiler 来说代码质量有所提高,可以减少本地代码执行时间,从而抵消了额外的编译时间开销。

编译优化技术

Java 虚拟机设计团队几乎对代码的所有优化措施都集中在了即时编译器中,因此一般来说,即时编译器产生的本地代码会比 javac 产生的字节码更加优秀。下面,我们介绍一些 HotSpot 虚拟机即时编译器生成代码时采用的代码优化技术。

优化技术概览

下面列出了 HotSpot 虚拟机即时编译器采用的一些优化技术,既有经典编译器的优化技术,也有针对 Java 语言进行的优化技术。后面我们挑几个重要而且典型的优化进行讲解。



推荐阅读
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 背景应用安全领域,各类攻击长久以来都危害着互联网上的应用,在web应用安全风险中,各类注入、跨站等攻击仍然占据着较前的位置。WAF(Web应用防火墙)正是为防御和阻断这类攻击而存在 ... [详细]
  • 本文介绍了如何使用PHP向系统日历中添加事件的方法,通过使用PHP技术可以实现自动添加事件的功能,从而实现全局通知系统和迅速记录工具的自动化。同时还提到了系统exchange自带的日历具有同步感的特点,以及使用web技术实现自动添加事件的优势。 ... [详细]
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 本文介绍了在Linux下安装和配置Kafka的方法,包括安装JDK、下载和解压Kafka、配置Kafka的参数,以及配置Kafka的日志目录、服务器IP和日志存放路径等。同时还提供了单机配置部署的方法和zookeeper地址和端口的配置。通过实操成功的案例,帮助读者快速完成Kafka的安装和配置。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • SpringMVC接收请求参数的方式总结
    本文总结了在SpringMVC开发中处理控制器参数的各种方式,包括处理使用@RequestParam注解的参数、MultipartFile类型参数和Simple类型参数的RequestParamMethodArgumentResolver,处理@RequestBody注解的参数的RequestResponseBodyMethodProcessor,以及PathVariableMapMethodArgumentResol等子类。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
author-avatar
ColinYinbaohua
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有