热门标签 | 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 语言进行的优化技术。后面我们挑几个重要而且典型的优化进行讲解。



推荐阅读
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 本文将带你快速了解 SpringMVC 框架的基本使用方法,通过实现一个简单的 Controller 并在浏览器中访问,展示 SpringMVC 的强大与简便。 ... [详细]
  • 深入解析Struts、Spring与Hibernate三大框架的面试要点与技巧 ... [详细]
  • 如何撰写适应变化的高效代码:策略与实践
    编写高质量且适应变化的代码是每位程序员的追求。优质代码的关键在于其可维护性和可扩展性。本文将从面向对象编程的角度出发,探讨实现这一目标的具体策略与实践方法,帮助开发者提升代码效率和灵活性。 ... [详细]
  • 本文总结了Java初学者需要掌握的六大核心知识点,帮助你更好地理解和应用Java编程。无论你是刚刚入门还是希望巩固基础,这些知识点都是必不可少的。 ... [详细]
  • 基于iSCSI的SQL Server 2012群集测试(一)SQL群集安装
    一、测试需求介绍与准备公司计划服务器迁移过程计划同时上线SQLServer2012,引入SQLServer2012群集提高高可用性,需要对SQLServ ... [详细]
  • Spring – Bean Life Cycle
    Spring – Bean Life Cycle ... [详细]
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • Spring Boot 中配置全局文件上传路径并实现文件上传功能
    本文介绍如何在 Spring Boot 项目中配置全局文件上传路径,并通过读取配置项实现文件上传功能。通过这种方式,可以更好地管理和维护文件路径。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • oracle c3p0 dword 60,web_day10 dbcp c3p0 dbutils
    createdatabasemydbcharactersetutf8;alertdatabasemydbcharactersetutf8;1.自定义连接池为了不去经常创建连接和释放 ... [详细]
  • 如何在Java中使用DButils类
    这期内容当中小编将会给大家带来有关如何在Java中使用DButils类,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。D ... [详细]
  • 解决 Windows Server 2016 网络连接问题
    本文详细介绍了如何解决 Windows Server 2016 在使用无线网络 (WLAN) 和有线网络 (以太网) 时遇到的连接问题。包括添加必要的功能和安装正确的驱动程序。 ... [详细]
  • 如何将TS文件转换为M3U8直播流:HLS与M3U8格式详解
    在视频传输领域,MP4虽然常见,但在直播场景中直接使用MP4格式存在诸多问题。例如,MP4文件的头部信息(如ftyp、moov)较大,导致初始加载时间较长,影响用户体验。相比之下,HLS(HTTP Live Streaming)协议及其M3U8格式更具优势。HLS通过将视频切分成多个小片段,并生成一个M3U8播放列表文件,实现低延迟和高稳定性。本文详细介绍了如何将TS文件转换为M3U8直播流,包括技术原理和具体操作步骤,帮助读者更好地理解和应用这一技术。 ... [详细]
  • 深入理解Java中的多态性概念及其应用
    多态是面向对象编程中的三大核心特性之一,与封装和继承共同构成了面向对象的基础。多态使得代码更加灵活和可扩展,封装和继承则为其提供了必要的支持。本文将深入探讨多态的概念及其在Java中的具体应用,帮助读者全面理解和掌握这一关键知识点。 ... [详细]
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社区 版权所有