如果你想要提升开发效率,通常是jvm要加载一个
- 编辑:巴黎人baliren登陆 -如果你想要提升开发效率,通常是jvm要加载一个
原标题:去何方系统高可用之法:搭建故障演习平台
Classloader担任将Class加载到JVM中,而且鲜明由特别ClassLoader来加载(父优先的阶段加运载飞机制)。还或然有三个义务正是将Class字节码重新讲明为JVM统一供给的格式
在面向对象编制程序推行中,大家通过广大的类来公司一个眼花缭乱的体系,这一个类之间交互关联、调用使他们的关系变成了三个头昏眼花紧凑的互连网。当系统运转时,出于品质、能源利用多地点的虚拟,大家不恐怕供给JVM 二次性将全方位的类都加载成功,而是只加载能够帮忙系统顺遂运转和周转的类和财富就可以。那么在系统运作进度中只要须求选拔未在运维时加载的类或能源时该怎么做吧?那就要靠类加载器来成功了。
论及知识点:APM, java Agent, plugin, bytecode, asm, InvocationHandler, smail
笔者介绍
Ali妹导读:调整和裁减故障的最棒点子正是让故障平常性的暴发。通过不停重复退步进程,持续升高系统的容错和弹性技能。明天,阿里Baba(Alibaba)把七年来在故障演习领域的新意和实行汇浓缩而成的工具进行开源,它就是“ChaosBlade”。倘令你想要进步开荒成效,无妨来打探一下。
1.Classloader类结构深入分析
什么样是类加载器
类加载器(ClassLoader)就是在系统运作进程中动态的将字节码文件加载到 JVM
中的工具,基于这一个工具的全体类加载流程,我们称作类加运载飞机制。大家在 IDE
中编辑的都以源代码文件,今后缀名 .java
的公文方式存在于磁盘上,通过编写翻译后生成后缀名 .class
的字节码文件,ClassLoader 加载的便是那个字节码文件。
APM : 应用程序品质管理。 2013年时外国的APM行当 NewRelic 和 应用程式Dynamics 已经在该领域拔得头筹,本国近几来来也出现一些APM厂家,如: 听云, OneAPM, 博睿 云智慧,Ali百川码力。 (据深入分析,国内android端方案都是抄袭NewRelic公司的,由于该集团的sdk未混淆,产业界良心)
王鹏,前年步入去何方机票职业部,首要从事后端研究开发工作,最近在机票职业部担当行程单和故障练习平台以及国有服务ES、数据同步中间件等连锁的研究开发专门的学业。
高可用架构是涵养服务稳固性的基本。
(1)首要由多个方法,分别是defineClass,findClass,loadClass,resolveClass
<1>defineClass(byte[] , int ,int) 将byte字节流剖判为JVM能够辨识的Class对象(间接调用那几个艺术生成的Class对象还一向不resolve,那些resolve将会在那几个目的真正实例化时resolve)
<2>findClass,通过类名去加载对应的Class对象。当大家兑现自定义的classLoader日常是重写那个艺术,依据传入的类名找到对应字节码的公文,并因而调用defineClass深入分析出Class独享
<3>loadClass运转时得以通过调用此方法加载二个类(由于类是动态加载进jvm,用略带加载多少的?)
<4>resolveClass手动调用那几个使得被加到JVM的类被链接(分析resolve那么些类?)
有何样类加载器
Java 暗中认可提供了八个 ClassLoader,分别是 AppClassLoader、ExtClassLoader、BootStrapClassLoader,依次后者分别是前面一个的「父加载器」。父加载器不是「父类」,三者之间未有继续关系,只是因为类加载的流程使三者之间变成了父亲和儿子关系,下文子禽详细陈说。
能做怎么着: crash监察和控制,卡顿监察和控制,内部存款和储蓄器监察和控制,扩展trace,网络质量监察和控制,app页面自动埋点,等。
去何方网二零零七年确立到现在,随着系统规模的日渐扩大,已经有那二个个使用系列,那些种类之间的耦合度和链路的复杂度不断做实,对于我们营造布满式高可用的种类架构具备十分大挑战。大家要求贰个阳台在运营期自动注入故障,核查故障预案是不是起效——故障演练平台。
阿里Baba(Alibaba)在海量网络服务以及每年双11光景的实践进度中,沉淀出了包罗全链路压测、线上流量管理调节、故障演练等高可用焦点技艺,并透过开源和云上服务的款型对外出口,以协理公司顾客和开垦者享受Alibaba的手艺红利,升高开拓功用,裁减职业的创设流程。
(2)实现自定义ClassLoader一般会接二连三UWranglerLClassLoader类,因为那个类实现了大部分办法。
BootStrapClassLoader
BootStrapClassLoader 也叫「根加载器」,它是退出 Java 语言,使用 C/C++
编写的类加载器,所以当你品味运用 ExtClassLoader 的实例调用 getParent()
方法取得其父加载器时会获得二个 null
值。
// 返回一个 AppClassLoader 的实例ClassLoader appClassLoader = this.getClass().getClassLoader();// 返回一个 ExtClassLoader 的实例ClassLoader extClassLoader = appClassLoader.getParent();// 返回 null,因为 BootStrapClassLoader 是 C/C++ 编写的,无法在 Java 中获得其实例ClassLoader bootstrapClassLoader = extClassLoader.getParent();
根加载器会暗中同意加载系统变量 sun.boot.class.path
内定的类库(jar 文件和
.class 文件),私下认可是 $JRE_HOME/lib
下的类库,如 rt.jar、resources.jar
等,具体能够输出该情状变量的值来查看。
String bootClassPath = System.getProperty("sun.boot.class.path");String[] paths = bootClassPath.split(":");for (String path : paths) { System.out.println;}// output// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/resources.jar// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/rt.jar// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/sunrsasign.jar// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jsse.jar// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jce.jar// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/charsets.jar// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/jfr.jar// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/classes
除此而外加载这一个暗中同意的类库外,也得以运用 JVM 参数 -Xbootclasspath/a
来追加额外部供给要让根加载器加载的类库。例如我们自定义二个
com.ganpengyu.boot.DateUtils
类来让根加载器加载。
package com.ganpengyu.boot;import java.text.SimpleDateFormat;import java.util.Date;public class DateUtils { public static void printNow() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(new Date; }}
大家将其制作成四个名字为 gpy-boot
的 jar 包放到 /Users/yu/Desktop/lib
下,然后写三个测量检验类去品尝加载 DateUtils。
public class Test { public static void main(String[] args) throws Exception { Class<?> clz = Class.forName("com.ganpengyu.boot.DateUtils"); ClassLoader loader = clz.getClassLoader(); System.out.println(loader == null); }}
运维那些测量检验类:
java -Xbootclasspath/a:/Users/yu/Desktop/lib/gpy-boot.jar -cp /Users/yu/Desktop/lib/gpy-boot.jar:. Test
能够观望输出为 true
,也便是说加载 com.ganpengyu.boot.DateUtils
的类加载器在 Java
中不能获得其援用,而其余类都不能够不通过类加载器加载本领被选拔,所以测算出那么些类是被
BootStrapClassLoader 加载的,也证实了 -Xbootclasspath/a
参数确实能够扩张要求被根加载器额外加载的类库。
总的说来,对于 BootStrapClassLoader 这一个根加载器大家供给精通三点:
- 根加载器使用 C/C++ 编写,大家无法在 Java 中收获其实例
- 根加载器默许加载系统变量
sun.boot.class.path
钦命的类库 - 可以行使
-Xbootclasspath/a
参数追加根加载器的暗中同意加载类库
质量监察和控制其实正是hook 代码到项目代码中,进而做到各个监督。常规花招都以在类型中追加代码,但什么成功非侵入式的,即多个sdk就可以。
一、背景
诸如,借助Ali云品质测量检验 PTS,高功效创设全链路压测类别,通过开源组件 Sentinel 达成限流和贬低效率。那一次,经历了 6 年光阴的精耕细作和实践,累计在线上进行演习场景达数万次,大家将Alibaba在故障演习领域的创新意识和推行,浓缩成多少个混沌工程工具,并将其开源,命名字为ChaosBlade。
2.ClassLoader的级差加运载飞机制
ExtClassLoader
ExtClassLoader 也叫「增加类加载器」,它是三个选用 Java
完结的类加载器(sun.misc.Launcher.ExtClassLoader
),用于加载系统所供给的扩张类库。暗中认可加载系统变量
java.ext.dirs
钦点位置下的类库,日常是 $JRE_HOME/lib/ext
目录下的类库。
public static void main(String[] args) { String extClassPath = System.getProperty("java.ext.dirs"); String[] paths = extClassPath.split(":"); for (String path : paths) { System.out.println; }}// output// /Users/leon/Library/Java/Extensions// /Library/Java/JavaVirtualMachines/jdk1.8.0_151.jdk/Contents/Home/jre/lib/ext// /Library/Java/Extensions// /Network/Library/Java/Extensions// /System/Library/Java/Extensions// /usr/lib/java
咱俩得以在运营时修改java.ext.dirs
变量的值来修改扩充类加载器的默许类库加载目录,但日常并不提出如此做。假如大家真正有亟待扩展类加载器在运维时加载的类库,能够将其放置在暗许的加载目录下。总之,对于
ExtClassLoader 那一个增添类加载器大家必要精晓两点:
- 推而广之类加载器是选拔 Java 达成的类加载器,我们得以在前后相继中获得它的实例并利用。
- 日常不提议修改
java.ext.dirs
参数的值来修改暗许加载目录,如有必要,可以将在加载的类库放到那些默许目录下。
1. 如何hook
断面编制程序-- AOP。大家的方案是AOP的一种,通过修改app class字节码的款型将大家项目标class文件实行改造,进而做到放权大家的监督检查代码。
androidbuilder.jpg
经过查看Adnroid编译流程图,能够理解编译器会将具备class文件打包称dex文件,最后打包成apk。那么大家就供给在class编写翻译成dex文件的时候实行代码注入。比如作者想总括有些方法的执行时间,那自个儿只必要在每种调用了那一个点子的代码前后都加二个光阴总括就能够了。关键点就在于编写翻译dex文件时候注入代码,那一个编写翻译进度是由dx实践,具体类和方法为com.android.dx.command.dexer.Main#processClass
。此方法的第贰个参数就是class的byte数组,于是我们只必要在步入processClass方法的时候用ASM工具对class实行更动并替换掉第三个参数,最一生成的apk正是大家改变之后的了。
类:com.android.dx.command.dexer.Main
新的难题: 要让jvm在施行processClass在此之前先实践我们的代码,应当要对com.android.dx.command.dexer.Main(以下简称为dexer.Main)举办改变。如何技巧完成这些指标?这时Instrumentation和VirtualMachine就进场了,参照他事他说加以考察第一节。
那是某职业部的系统拓扑图:
ChaosBlade 是什么?
ChaosBlade 是一款服从混沌工程实践原理,提供丰盛故障场景达成,协理布满式系统提高容错性和可苏醒性的无知工程工具,可完结底层故障的注入,特点是操作轻松、无侵入、扩展性强。
ChaosBlade 基于 Apache License v2.0 开源合同,近些日子有 chaosblade 和 chaosblade-exe-jvm 八个宾馆。
chaosblade 富含 CLI 和选择 Golang 完毕的根基能源、容器相关的无知实验施行实施模块。chaosblade-exe-jvm 是对运作在 JVM 上的利用实行混沌实验的实践器。
ChaosBlade 社区承接还可能会增添 C++、Node.js 等任何语言的鸠拙实验施行器。
(1)JVM平台提供三层的ClassLoader,那三层ClassLoader能够分成两类,分别是劳动JVM本身的,和服务周围普通类的。分别是:
<1>BootstrapClassLoader:主要加载JVM本身职业所须要的类,该ClassLoader未有父类加载器和子类加载器
<2>ExtClassLoader:那个类加载器同样是JVM自个儿的一某个,然并非由JVM达成,首要用来加载System.getProperty(“java.ext.dirs”)目录地下的类,如本机的值“D:javajdk7jrelibext;C:WindowsSunJavalibext”
<3>AppClassLoader:加载System.getProperty("java.class.path")(注意了在ide中运作程序时,该值日常是该类型的classes文件夹)中的类。全数的自定义类加载器不管直接促成ClassLoader,是一而再自ULacrosseLClassLoader或其子类,其父加载器(注意:父加载器与父类的分别)都以AppClassLoader,因为随意调用哪个父类的构造器,最终都将调用getSystemClassLoader作为父加载器,而该方法再次回到的难为AppClassLoader。(当应用程序中没有另外自定义的classLoader,那么除了System.getProperty(“java.ext.dirs”)目录中的类,其余类都由AppClassLoader加载)
AppClassLoader
AppClassLoader 也叫「应用类加载器」,它和 ExtClassLoader 同样,也是应用
Java
达成的类加载器(sun.misc.Launcher.AppClassLoader
)。它的意义是加载应用程序
classpath
下全体的类库。那是我们最常打交道的类加载器,大家在前后相继中调用的多多
getClassLoader()
方法重临的都是它的实例。在大家自定义类加载器时一旦未有特意钦赐,那么大家自定义的类加载器的暗中认可父加载器也是其一应用类加载器。总来讲之,对于
AppClassLoader 这么些应用类加载器我们须要精通两点:
- 应用类加载器是使用 Java 完成的类加载器,担当加载应用程序
classpath
下的类库。 - 应用类加载器是和大家最常打交道的类加载器。
- 从未有过特别钦命的景况下,自定义类加载器的父加载器正是应用类加载器。
2. hook 到哪里
一期首即便互连网质量监察和控制。如何能收获到互联网数据经过调研究开发掘日前有上面聚焦方案:
- root手提式有线电话机,通过adb 命令进行收缴。
- 确立vpn,将持有互连网央求举行收缴。
- 参照听云,newrelic等出品,针对一定库开展代理截获。
兴许还会有别的的法子,须要持续调查钻探。
眼下我们参考newrelic等店肆产品,针对一定互联网央求库开展代理的的章程实行网络数据截获。比方okhtt3, httpclient, 等网络库。
In general, a javaagent is a JVM “plugin”, a specially crafted .jar file, that utilizes the Instrumentation API that the JVM provides.
由于咱们要修改Dexer 的Main类, 而该类是在编写翻译时代由java设想机运行的, 所以大家要求通过agent来修改dexer Main类。
javaagent的首要成效如下:
- 能够在加载class文书从前作拦截,对字节码做修改
- 能够在运维期对已加载类的字节码做更改
JVMTI:JVM Tool Interface,是JVM暴表露来的一些供客户扩大的接口会集。JVMTI是依据事件驱动的,JVM每试行到一定的逻辑就能调用一些风云的回调接口,那个接口能够供开采者扩大自身的逻辑。
instrument agent: javaagent功效正是它来兑现的,别的instrument agent还会有各自名称叫JPLISAgent(Java Programming Language Instrumentation Services Agent),这几个名字也统统反映了其最本色的效果与利益:正是专程为Java语言编写的插桩服务提供支撑的。
二种加载agent的情势:
- 在运维时加载, 运转JVM时内定agent类。这种措施,Instrumentation的实例通过agent class的premain方法被传出。
- 在运维时加载,JVM提供一种当JVM运营成功后开启agent机制。这种情况下,Instrumention实例通过agent代码中的的agentmain传入。
参照例子instrumentation 成效介绍(javaagent)
有了javaagent, 大家就可以在编写翻译app时再也修改dex 的Main类,对应修改processClass方法。
何以修改class文件? 大家要求掌握java字节码,然后供给通晓ASM开垦。通过ASM编程来修改字节码,进而修改class文件。(也得以应用javaassist来展开改造)
在介绍字节代码指令从前,有不能缺少先来介绍 Java 虚构机实施模型。大家驾驭,Java 代码是 在线程内部进行的。各样线程都有本人的实行栈,栈由帧组成。种种帧表示一个艺术调用:每一回调用叁个形式时,会将多少个新帧压入当前线程的实施栈。当方法再次回到时,只怕是健康再次来到,大概是因为十一分重返,会将以此帧从施行栈中弹出,实行进程在发出调用的措施中持续开展(这么些方 法的帧今后投身栈的最上端)。
每一帧包括两片段:一个有的变量部分和一个操作数栈部分。局地变量部分含有可依赖索引 以随机顺序访谈的变量。由名字能够看出,操作数栈部分是叁个栈,个中蕴含了供字节代码指令 用作操作数的值。
字节代码指令 字节代码指令由三个标志该指令的操作码和固化数指标参数组成:
- 操作码是多个无符号字节值——即字节代码名
- 参数是静态值,鲜明了确切的授命行为。它们紧跟在操作码之后给出.比方GOTO标志指令(其操作码的值为 167)以二个指明下一条待实施命令的符号作为参数标志。不要 将指令参数与指令操作数相混淆:参数值是静态已知的,存款和储蓄在编写翻译后的代码中,而 操作数值来自操作数栈,唯有到运营时本领明了。
参考:
广大指令:
- const 将如何数据类型压入操作数栈。
- push 表示将单字节或短整型的常量压入操作数栈。
- ldc 代表将何以项目标数目从常量池中压入操作数栈。
- load 将某项指标有的变量数据压入操作数栈顶。
- store 将操作数栈顶的数据存入钦定的一对变量中。
- pop 从操作数栈顶弹出多少
- dup 复制栈顶的多少并将复制的值也压入栈顶。
- swap 交换栈顶的数量
- invokeVirtual 调用实例方法
- invokeSepcial 调用超类构造方法,实例伊始化,私有方法等。
- invokeStatic 调用静态方法
- invokeInterface 调用接口
- getStatic
- getField
- putStatic
- putField
- New
查看demo:Java源代码
public static void print(String param) { System.out.println("hello " + param); new TestMain().sayHello();}public void sayHello() { System.out.println("hello agent");}
字节码
// access flags 0x9 public static print(Ljava/lang/String;)V GETSTATIC java/lang/System.out : Ljava/io/PrintStream; NEW java/lang/StringBuilder DUP INVOKESPECIAL java/lang/StringBuilder.<init> ()V LDC "hello " INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; ALOAD 0 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V NEW com/paic/agent/test/TestMain DUP INVOKESPECIAL com/paic/agent/test/TestMain.<init> ()V INVOKEVIRTUAL com/paic/agent/test/TestMain.sayHello ()V RETURN public sayHello()V GETSTATIC java/lang/System.out : Ljava/io/PrintStream; LDC "hello agent" INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V RETURN
鉴于程序剖析、生成和转变本领的用处众多,所以大家针对广大言语实现了好些个用于解析、 生成和更改程序的工具,那几个语言中就回顾 Java 在内。ASM 便是为 Java 语言设计的工具之一, 用于开展运维时类生成与转移。于是,大家设计了 ASM1库,用于拍卖经过编写翻译 的 Java 类。
ASM 而不是惟一可变通和转变已编写翻译 Java 类的工具,但它是新型、最高效的工具之一,可 从 下载。其利害攸关优点如下:
- 有多个简约的模块API,设计宏观、使用方便。
- 文书档案齐全,具有多少个相关的Eclipse插件。
- 支持最新的 Java 版本——Java 7。
- 小而快、特别可相信。
- 装有巨大的客户社区,可感到新顾客ﰁ供扶助。
- 源许可开放,差非常的少允许专擅使用。
ASM_transfer.png
核心类: ClassReader, ClassWriter, ClassVisitor
参考demo:
{ // print 方法的ASM代码 mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "print", "(Ljava/lang/String;)V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn; mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false); mv.visitLdcInsn; mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitVarInsn; mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv.visitTypeInsn(NEW, "com/paic/agent/test/TestMain"); mv.visitInsn; mv.visitMethodInsn(INVOKESPECIAL, "com/paic/agent/test/TestMain", "<init>", "()V", false); mv.visitMethodInsn(INVOKEVIRTUAL, "com/paic/agent/test/TestMain", "sayHello", "()V", false); mv.visitInsn; mv.visitEnd();}{ //sayHello 的ASM代码 mv = cw.visitMethod(ACC_PUBLIC, "sayHello", "()V", null, null); mv.visitCode(); mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;"); mv.visitLdcInsn("hello agent"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false); mv.visitInsn; mv.visitEnd();}
VirtualMachine有个loadAgent方法,它钦赐的agent会在main方法前运营,并调用agent的agentMain方法,agentMain的第1个参数是Instrumentation,那样大家就可见给Instrumentation设置ClassFileTransformer来完毕对dexer.Main的改建,一样也得以用ASM来达成。一般的话,APM工具满含八个部分,plugin、agent和实际的专门的职业jar包。那几个agent正是我们说的由VirtualMachine运维的代理。而plugin要做的业务正是调用loadAgent方法。对于Android Studio来说,plugin正是七个Gradle插件。 达成gradle插件能够用intellij成立三个gradle工程并完结Plugin< Project >接口,然后把tools.jar(在jdk的lib目录下)和agent.jar参与到Libraries中。在META-INF/gradle-plugins目录下开创多少个properties文件,并在文书中投入一行内容“implementation-class=插件类的全限定名“。artifacs配置把源码和META-INF加上,但不可能加tools.jar和agent.jar。(tools.jar 在 jdk中, 但是一般供给团结拷贝到工程目录中的, agent.jar开荒到位前寄放plugin工程中用来获取jar包路线)。
agent的落到实处相对plugin则复杂非常多,首先供给提供agentmain(String args, Instrumentation inst)方法,并给Instrumentation设置ClassFileTransformer,然后在transformer里改动dexer.Main。当jvm成功实践到大家设置的transformer时,就能够发觉传进来的class根本就从未dexer.Main。坑爹呢那是。。。后面提到了,执行dexer.Main的是dx.bat,约等于说,它和plugin根本不在二个进度里。
dx.bat其实是由ProcessBuilder的start方法运转的,ProcessBuilder有贰个command成员,保存的是开发银行目的经过指引的参数,只要我们给dx.bat带上-javaagent参数就能够给dx.bat所在经过钦点大家的agent了。于是大家能够在施行start方法前,调用command方法获得command,并往当中插入-javaagent参数。参数的值是agent.jar所在的路子,能够接纳agent.jar个中二个class类实例的getProtectionDomain().getCodeSource().getLocation.getPath()获得。可是到了这边大家的次第恐怕如故不能正确改动class。假若大家把更动类的代码单独置于三个类中,然后用ASM生成字节码调用这些类的章程来对command参数进行退换,就能够意识抛出了ClassDefNotFoundError错误。这里涉及到了ClassLoader的学识。
关于ClassLoader的牵线非常多,这里不再赘言。ProcessBuilder类是由Bootstrap ClassLoader加载的,而大家自定义的类则是由AppClassLoader加载的。Bootstrap ClassLoader处于AppClassLoader的上层,大家清楚,上层类加载器所加载的类是无力回天直接援引下层类加载器所加载的类的。但只要下层类加载器加载的类完成或接二连三了上层类加载器加载的类或接口,上层类加载器加载的类获取到下层类加载的类的实例就能够将其强制转型为父类,并调用父类的不二法门。那么些上层类加载器加载的接口,部分APM使用InvocationHandler。还应该有贰个主题素材,ProcessBuilder怎么工夫博获得InvocationHandler子类的实例呢?有贰个比较奇妙的做法,在agent运转的时候,创立InvocationHandler实例,并把它赋值给Logger的treeLock成员。treeLock是贰个Object对象,而且只是用来加锁的,未有别的用途。但treeLock是几个final成员,所以记得要修改其修饰,去掉final。Logger同样也是由Bootstrap ClassLoader加载,那样ProcessBuilder就能够经过反射的章程来获得InvocationHandler实例了。(详见:核心代码例子)
上层类加载器所加载的类是无计可施间接援引下层类加载器所加载的类的
层次 | 加载器 | 类 |
---|---|---|
上层 | BootStrapClassLoader | ProcessBuilder |
下层 | AppClassLoader | ProcessBuilderMethodVisitor操作的自定义类 |
这一句话的理解: 大家的目标是经过ProcessBuilderMethodVisitor将大家的代码写入ProcessBuilder.class中去让BootStrapClassLoader类加载器实行加载,而此时, BootStrapClassLoader是不能引用到我们自定义的类的,因为大家自定义的类是AppClassLoader加载的。
但一旦下层类加载器加载的类达成或接二连三了上层类加载器加载的类或接口,上层类加载器加载的类获取到下层类加载的类的实例就能够将其强制转型为父类,并调用父类的法子。
层次 | 加载器 | 类 |
---|---|---|
上层 | BootStrapClassLoader | Looger |
下层 | AppClassLoader | InvocationDispatcher |
这句话的驾驭: 这里大家得以观察自定义类InvocationDispatcher是由AppClassLoader加载的, 大家在运行RewriterAgent(AppClassLoader加载)类时,通过反射的不二秘籍将InvocationDispatcher对象归入Looger(由于引用了Looger.class,所以此时logger已经被BootStrapClassLoader加载)类的treelock对象中,即下层类加载器加载的类达成了上层类加载器加载的类;当大家由此ProcessBuilderMethodVisitor类管理ProcessBuilder.class文件时,能够通过Logger提取成员变量,插入对应的调用逻辑。当运营到ProcessBuilder时,再经过这段代码动态代理的方法调用对应的作业。能够将其挟持转型为父类,并调用父类的主意 ,请参考 这里详细介绍了invokeInterface 和 invokeVirtual 的差别。
贯彻上大家当下关键做那二种, 一种是代码调用替换, 另一种是代码包裹再次来到。主假若提前写好相应准则的轮换代码, 生成配置文件表, 在agent中visit每贰个class代码, 遇到对应相配调用时将张开代码替换。
ProcessBuilderMethodVisitor
DexClassTransformer#createDexerMainClassAdapter
InvocationDispatcher
BytecodeBuilder
public BytecodeBuilder loadInvocationDispatcher() { this.adapter.visitLdcInsn(Type.getType(TransformConstant.INVOCATION_DISPATCHER_CLASS)); this.adapter.visitLdcInsn(TransformConstant.INVOCATION_DISPATCHER_FILED_NAME); this.adapter.invokeVirtual(Type.getType(Class.class), new Method("getDeclaredField", "(Ljava/lang/String;)Ljava/lang/reflect/Field;")); this.adapter.dup(); this.adapter.visitInsn(Opcodes.ICONST_1); this.adapter.invokeVirtual(Type.getType(Field.class), new Method("setAccessible", "; this.adapter.visitInsn(Opcodes.ACONST_NULL); this.adapter.invokeVirtual(Type.getType(Field.class), new Method("get", "(Ljava/lang/Object;)Ljava/lang/Object;")); return this; }
解析:
顺序 | 栈 | 指令 | 描述 |
---|---|---|---|
8 | InvocationDispatcher object | invokeVirtual | 调用get方法返回具体实例对象 |
7 | null | ACONST_NULL | null 入栈 |
6 | Field object | invokeVirtual | 调用setAccessible,改为可访问的,目前栈中只剩一个对象 |
5 | true | ICONST_1 | 1 即为true,入栈 |
4 | Field object | dup | 拷贝一份,目前栈中只剩两个对象 |
3 | Field object | invokeVirtual | 调用getDeclaredField 获取treeLock存储的Field |
2 | treelock | ldc | treelock 入栈 |
1 | Logger.class Type | ldc | Logger.class type 入栈 |
WrapMethodClassVisitor#MethodWrapMethodVisitor
private boolean tryReplaceCallSite(int opcode, String owner, String name, String desc, boolean itf) { Collection<ClassMethod> replacementMethods = this.context.getCallSiteReplacements(owner, name, desc); if (replacementMethods.isEmpty { return false; } ClassMethod method = new ClassMethod(owner, name, desc); Iterator<ClassMethod> it = replacementMethods.iterator(); if (it.hasNext { ClassMethod replacementMethod = it.next(); boolean isSuperCallInOverride = (opcode == Opcodes.INVOKESPECIAL) && !owner.equals(this.context.getClassName && this.name.equals && this.desc.equals; //override 方法 if (isSuperCallInOverride) { this.log.info(MessageFormat.format("[{0}] skipping call site replacement for super call in overriden method : {1}:{2}", this.context.getFriendlyClassName(), this.name, this.desc)); return false; } Method originMethod = new Method(name, desc); //处理init方法, 构造对象, 调用替换的静态方法来替换init。 if (opcode == Opcodes.INVOKESPECIAL && name.equals("<init>")) { //调用父类构造方法 if (this.context.getSuperClassName() != null && this.context.getSuperClassName().equals { this.log.info(MessageFormat.format("[{0}] skipping call site replacement for class extending {1}", this.context.getFriendlyClassName(), this.context.getFriendlySuperClassName; return false; } this.log.info(MessageFormat.format("[{0}] tracing constructor call to {1} - {2}", this.context.getFriendlyClassName(), method.toString); //开始处理创建对象的逻辑 //保存参数到本地 int[] arguments = new int[originMethod.getArgumentTypes().length]; for (int i = arguments.length -1 ; i >= 0; i--) { arguments[i] = this.newLocal(originMethod.getArgumentTypes; this.storeLocal(arguments[i]); } //由于init 之前会有一次dup,及创建一次, dup一次, 此时如果执行了new 和 dup 操作树栈中会有两个对象。 this.visitInsn(Opcodes.POP); if (this.newInstructionFound && this.dupInstructionFound) { this.visitInsn(Opcodes.POP); } //载入参数到操作数栈 for (int arg : arguments) { this.loadLocal; } //使用要替换的方法,执行静态方法进行对象创建 super.visitMethodInsn(Opcodes.INVOKESTATIC, replacementMethod.getClassName(), replacementMethod.getMethodName(), replacementMethod.getMethodDesc; //如果此时才调用了dup,也需要pop, (这一部分的场景暂时还没有构造出来, 上面的逻辑为通用的) if (this.newInstructionFound && !this.dupInstructionFound) { this.visitInsn(Opcodes.POP); } } else if (opcode == Opcodes.INVOKESTATIC) { //替换静态方法 this.log.info(MessageFormat.format("[{0}] replacing call to {1} with {2}", this.context.getFriendlyClassName(), method.toString(), replacementMethod.toString; super.visitMethodInsn(Opcodes.INVOKESTATIC, replacementMethod.getClassName(), replacementMethod.getMethodName(), replacementMethod.getMethodDesc; } else { // 其他方法调用, 使用新方法替换旧方法的调用。 先判断创建的对象是否为null, Method newMethod = new Method(replacementMethod.getMethodName(), replacementMethod.getMethodDesc; this.log.info(MessageFormat.format("[{0}] replacing call to {1} with {2}", this.context.getFriendlyClassName(), method.toString(), replacementMethod.toString; //从操作数栈上取原始参数类型到本地变量中 int[] originArgs = new int[originMethod.getArgumentTypes().length]; for (int i = originArgs.length -1 ; i >= 0; i--) { originArgs[i] = this.newLocal(originMethod.getArgumentTypes; this.storeLocal(originArgs[i]); } //操作数栈中只剩操作对象了, 需要dup, 拷贝一份作为检查新method的第一个参数。 this.dup(); //检查操作数栈顶对象类型是否和新method的第一个参数一致。 this.instanceOf(newMethod.getArgumentTypes; Label isInstanceOfLabel = new Label(); //instanceof 结果不等于0 则跳转到 isInstanceofLabel,执行替换调用 this.visitJumpInsn(Opcodes.IFNE, isInstanceOfLabel); //否则执行原始调用 for (int arg : originArgs) { this.loadLocal; } super.visitMethodInsn(opcode, owner, name, desc, itf); Label endLabel = new Label(); //跳转到结束label this.visitJumpInsn(Opcodes.GOTO, endLabel); this.visitLabel(isInstanceOfLabel); //处理替换的逻辑 //load 参数, 第一个为 obj, 后面的为原始参数 this.checkCast(newMethod.getArgumentTypes; for (int arg: originArgs) { this.loadLocal; } super.visitMethodInsn(Opcodes.INVOKESTATIC, replacementMethod.getClassName(), replacementMethod.getMethodName(), replacementMethod.getMethodDesc; //结束 this.visitLabel; } this.context.markModified(); return true; } return false; }
解析 详细见tryReplaceCallSite
讲授就可以。
缘何要开源?
有的是供销合作社曾经起来关怀并追究混沌工程,慢慢成为测量试验系统高可用,创设对系统消息不得缺失的工具。但混沌工程领域近年来还地处叁个十分的快多变的品级,最棒施行和工具框架未有统一规范。实行混沌工程或者会拉动一些隐私的政工风险,经验和工具的衰竭也将越发阻止 DevOps 职员进行混沌工程。
混沌工程领域近年来也是有成都百货上千能够的开源工具,分别覆盖有些圈子,但这么些工具的运用方法差别,当中多少工具上手难度大,学习开销高,混沌实验能力单一,使非常多人对混沌工程领域望而生畏。
阿里Baba(Alibaba)公司在混沌工程领域已经实行多年,将混沌实验工具 ChaosBlade 开源指标,我们希望:
- 让更多少人了然并出席到混沌工程领域;
- 缩水营造混沌工程的门道;
- 还要依据社区的力量,完善越来越多的无知实验现象,共同推进混沌工程领域的腾飞。
(2)Jvm加载class文件到内有着三种办法,隐式加载和显示加载,日常那三种艺术是长短不一使用的
<1>隐式加载:是由此JVM来自动加载要求的类到内部存款和储蓄器的措施,当有个别类被运用时,JVM开采此类不在内存中,那么它就能够自行加载该类到内部存款和储蓄器
<2>展现加载:通过调用this.getClasss.getClassLoader.loadClass(),Class.forName,自个儿落成的ClassLoader的findClass方法
自定义类加载器
除去上述三种 Java 私下认可提供的类加载器外,咱们仍是能够通过承袭
java.lang.ClassLoader
来自定义三个类加载器。要是在开创自定义类加载器前卫未点名父加载器,那么默许使用
AppClassLoader
作为父加载器。关于自定义类加载器的始建和动用,大家会在末端的章节详细讲授。
8. 验证
将转变的apk反编写翻译,查看class 字节码。大家一般会通过JD-GUI来查阅。我们来查看一下sample生成的结果:
private void testOkhttpCall() { OkHttpClient localOkHttpClient = new OkHttpClient.Builder; Object localObject = new Request.Builder().url("https://test3-fbtoam.pingan.com.cn:15443/btoa/portal/common/getPublicKey"); if (!(localObject instanceof Request.Builder)) { localObject = ((Request.Builder)localObject).build(); if ((localOkHttpClient instanceof OkHttpClient)) { break label75; } } label75: for (localObject = localOkHttpClient.newCalllocalObject);; localObject = OkHttp3Instrumentation.newCall((OkHttpClient)localOkHttpClient, localObject)) { localObject).enqueue(new Callback() { public void onFailure(Call paramAnonymousCall, IOException paramAnonymousIOException) { } public void onResponse(Call paramAnonymousCall, Response paramAnonymousResponse) throws IOException { } }); return; localObject = OkHttp3Instrumentation.build((Request.Builder)localObject); break; } }
位置的代码预计未有几人能够看懂, 尤其for循环里面包车型地铁逻辑。其实是出于分歧的反编译工具产生的分析难题导致的,所以看起来逻辑混乱,不能够符合预期。
想用查看真实的结果, 大家来看下反编写翻译后的smail。详细smail指令参谋
.method private testOkhttpCall()V .locals 6 .prologue .line 35 const-string v3, "https://test3-fbtoam.pingan.com.cn:15443/btoa/portal/common/getPublicKey" .line 36 .local v3, "url":Ljava/lang/String; new-instance v4, Lokhttp3/OkHttpClient$Builder; invoke-direct {v4}, Lokhttp3/OkHttpClient$Builder;-><init>()V invoke-virtual {v4}, Lokhttp3/OkHttpClient$Builder;->build()Lokhttp3/OkHttpClient; move-result-object v1//new OkHttpClient.Builder; 即为okhttpclient,放到 v1 中 .line 37 .local v1, "okHttpClient":Lokhttp3/OkHttpClient; new-instance v4, Lokhttp3/Request$Builder; invoke-direct {v4}, Lokhttp3/Request$Builder;-><init>()V invoke-virtual {v4, v3}, Lokhttp3/Request$Builder;->url(Ljava/lang/String;)Lokhttp3/Request$Builder; move-result-object v4 //new Request.Builder().url执行了这一段语句,将结果放到了v4中。 instance-of v5, v4, Lokhttp3/Request$Builder; if-nez v5, :cond_0 invoke-virtual {v4}, Lokhttp3/Request$Builder;->build()Lokhttp3/Request; move-result-object v2 .line 38 .local v2, "request":Lokhttp3/Request; //判断v4中存储的是否为Request.Builder类型,如果是则跳转到cond_0, 否则执行Request.Builder.build()方法,将结果放到v2中. :goto_0 instance-of v4, v1, Lokhttp3/OkHttpClient; if-nez v4, :cond_1 invoke-virtual {v1, v2}, Lokhttp3/OkHttpClient;->newCall(Lokhttp3/Request;)Lokhttp3/Call; move-result-object v0 .line 39 .end local v1 # "okHttpClient":Lokhttp3/OkHttpClient; .local v0, "call":Lokhttp3/Call; //goto_0 标签:判断v1 中的值是否为 OKHttpclient 类型, 如果是跳转为cond_1 , 否则调用OKHttpclient.newCall, 并将结果放到v0 中。 :goto_1 new-instance v4, Lcom/paic/apm/sample/MainActivity$1; invoke-direct {v4, p0}, Lcom/paic/apm/sample/MainActivity$1;-><init>(Lcom/paic/apm/sample/MainActivity;)V invoke-interface {v0, v4}, Lokhttp3/Call;->enqueue(Lokhttp3/Callback;)V .line 51 return-void //goto_1 标签: 执行 v0.enqueue(new Callback;并return; .line 37 .end local v0 # "call":Lokhttp3/Call; .end local v2 # "request":Lokhttp3/Request; .restart local v1 # "okHttpClient":Lokhttp3/OkHttpClient; :cond_0 check-cast v4, Lokhttp3/Request$Builder; invoke-static {v4}, Lcom/paic/agent/android/instrumentation/okhttp3/OkHttp3Instrumentation;->build(Lokhttp3/Request$Builder;)Lokhttp3/Request; move-result-object v2 goto :goto_0 //cond_0:标签: 执行com.paic.agent.android.instrumentation.okhttp3.OkHttp3Instrumentation.build, 并将结果放到v2中,并goto 到 goto_0 .line 38 .restart local v2 # "request":Lokhttp3/Request; :cond_1 check-cast v1, Lokhttp3/OkHttpClient; .end local v1 # "okHttpClient":Lokhttp3/OkHttpClient; invoke-static {v1, v2}, Lcom/paic/agent/android/instrumentation/okhttp3/OkHttp3Instrumentation;->newCall(Lokhttp3/OkHttpClient;Lokhttp3/Request;)Lokhttp3/Call; move-result-object v0 goto :goto_1 //cond_1 标签: 执行com.paic.agent.android.instrumentation.okhttp3.OkHttp3Instrumentation.newCall, 并将结果放到v0中, goto 到goto_1 .end method
剖析后的伪代码
String v3 = "https://test3-fbtoam.pingan.com.cn:15443/btoa/portal/common/getPublicKey";object v1 = new OkhttpClient.Builder;object v4 = new Reqeust.Builder;object v2 ;object v0 ;if (v4 instanceof Request.Builder) { cond_0: v2 = com.paic.agent.android.instrumentation.okhttp3.OkHttp3Instrumentation.build; } else { v2 = (Request.Builder)v4.build();}goto_0:if (v1 instanceof OkHttpClient) { cond_1: v0 = com.paic.agent.android.instrumentation.okhttp3.OkHttp3Instrumentation.newCall;} else { v0 = v1.newCall; // v0 is Call}goto_1:v4 = new Callback();v0.enqueue;return;
翻开伪代码, 符合预期结果。验证实现。
系统里面包车型大巴依赖特别复杂、调用链路很深、服务中间未有分支。在这种复杂的借助下,系统发生了几起故障:
ChaosBlade 能化解什么难点?
权衡微服务的容错手艺
通过模拟调用延迟、服务不可用、机器能源满载等,查看爆发故障的节点或实例是或不是被机关隔开分离、下线,流量调治是还是不是科学,预案是不是管用,同期观看系统一体化的 QPS 或 RT 是还是不是受影响。在此基础上得以舒缓扩大故障节点范围,验证上游服务限流降级、熔断等是不是行得通。最后故障节点扩张到请求服务超时,推测系统容错红线,度量系统容错工夫。
阐明容器编排配置是或不是合理
透过模拟杀服务 Pod、杀节点、增大 Pod 财富负载,观看系统服务可用性,验证别本配置、能源限制配置以及 Pod 下布置的容器是还是不是站得住。
测验 PaaS 层是不是结实
经过模拟上层财富负载,验证调整系统的卓有作用;模拟正视的布满式存款和储蓄不可用,验证系统的容错技艺;模拟调整节点不可用,测量试验调治职分是还是不是自动员搬迁移到可用节点;模拟主备节点故障,测量试验主备切换是或不是正规。
表明监察和控制告警的时效性
经过对系统注入故障,验香港证肆股票(stock)交易监督委员会察和控制指标是还是不是标准,监察和控制维度是不是完善,告警阈值是还是不是站得住,告警是或不是神速,告警接收人是还是不是正确,公告路子是不是可用等,提高监督检查告警的标准和时效性。
定点与缓慢解决难点的应急力量
通过故障突袭,随机对系统注入故障,考察相关人士对标题标应急本领,以及难题上报、管理流程是还是不是站得住,达到以战养战,练习人永远与消除难题的技艺。
(3)上级委托机制:当一个加载器加载类字时,先委托其父加载器加载,若加载成功则反映给该加载器,若父加载器不可能加载,则由该加载器加载
类加载器的运转顺序
上文已经提到过 BootStrapClassLoader 是一个采取 C/C++
编写的类加载器,它曾经松开到了 JVM 的根本之中。当 JVM
运行时,BootStrapClassLoader
也会随之运维并加载核心类库。当主题类库加载成功后,BootStrapClassLoader
会成立 ExtClassLoader 和 AppClassLoader 的实例,四个 Java
已毕的类加载器将会加载本人担当路线下的类库,这一个历程大家得以在
sun.misc.Launcher
中窥见。
- 弱依赖挂掉,主流程挂掉,修改报废凭证的付出景况,下单主流程退步;
- 宗旨服务调用量陡增,某服务超时引起相关联的具备服务“雪崩”;
- 机房互连网恐怕有些机器挂掉,不可能提供基本服务。
功效和特征
场景丰硕度高
ChaosBlade 辅助的呆滞实验现象不仅仅覆盖基础能源,如 CPU 满载、磁盘 IO 高、互连网延迟等,还包蕴运维在 JVM 上的运用试验现象,如 Dubbo 调用超时和调用相当、内定方法延迟或抛卓殊以及重回特定值等,同临时间提到容器相关的试验,如杀容器、杀 Pod。后续会不停的增加实践现象。
行使轻巧,易于通晓
ChaosBlade 通过 CLI 格局试行,具备自身的授命提醒效果,可以轻松急忙的右侧使用。命令的书写遵从阿里Baba(Alibaba)集团内多年故障测验和排练试行抽象出的故障注入模型,档期的顺序明显,易于阅读和清楚,降低了混沌工程实践的门径。
场地扩充方便
怀有的 ChaosBlade 实验实践器一样坚守上述提到的故障注入模型,使实验现象模型统一,便于开垦和维护。模型本人简单明了,学习花费低,可以依照模型方便快捷的增添更加的多的鸠拙实验现象。
3.怎么加载class文件:
分为四个步骤 加载字节码到内部存储器、Linking、类字节初叶化赋值
ExtClassLoader 的始建进度
大家将 Launcher 类的构造方法源码精简展现如下:
public Launcher() { // 创建 ExtClassLoader Launcher.ExtClassLoader var1; try { var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } // 创建 AppClassLoader try { this.loader = Launcher.AppClassLoader.getAppClassLoader; } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } // 设置线程上下文类加载器 Thread.currentThread().setContextClassLoader(this.loader); // 创建 SecurityManager}
能够见见当 Launcher 被开头化时就能够相继创立 ExtClassLoader 和
AppClassLoader。大家进去 getExtClassLoader()
方法并追踪创造流程,开采这里又调用了 ExtClassLoader
的构造方法,在那一个构造方法里调用了父类的构造方法,那正是 ExtClassLoader
创立的关键步骤,注意这里流传父类构造器的第二个参数为
null。接着我们去查看那几个父类构造方法,它献身 java.net.URLClassLoader
类中:
URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory)
经过那一个构造方法的具名和注释大家能够鲜明的明亮,第一个参数 parent
表示的是当下要创设的类加载器的父加载器。结合前边我们关系的
ExtClassLoader 的父加载器是 JVM 内核中 C/C++ 开拓的
BootStrapClassLoader,且不能够在 Java
中取得那几个类加载器的援用,同一时间各个类加载器又一定有三个父加载器,咱们得以反证出,ExtClassLoader
的父加载器便是 BootStrapClassLoader。
多少个故障原因:
ChaosBlade 的演进史
EOS(2012-2015):故障演习平台的中期版本,故障注入本领通过字节码加强格局完成,模拟常见的 RPC 故障,消除微服务的强弱正视治理难题。
MonkeyKing(2016-2018):故障演习平台的晋级版本,丰硕了故障场景(如:能源、容器层场景),发轫在生养条件举办部分规模化的排演。
AHAS(2018.9-至今):阿里云应用高可用服务,内置演练平台的百分之百功力,帮忙可编写制定演习、练习插件扩大等力量,并结成了架构感知和限流降级的功能。
ChaosBlade:是 MonkeyKing 平台底层故障注入的兑现工具,通过对演习平台底层的故障注入技能举办抽象,定义了一套故障模型。合营顾客本身的 CLI 工具实行开源,帮衬云原生客商举办混沌工程测量检验。
(1)加载字节码到内部存款和储蓄器:(这一步常常经过findclass()方法落成)
以U景逸SUVLClassLoader为例:该类的构造函数返现必需制订三个U奥迪Q5L数据技巧创立该对象,该类中包涵一个U安德拉LClassPath对象,UTucsonLClassPath会决断传过来的UEscortL是文本或许Jar包,成立相应的FileLoader也许JarLoader或然默许加载器,当jvm调用findclass时,这个加载器将class文件的字节码加载到内存中
AppClassLoader 的始建进度
理清了 ExtClassLoader 的开创进度,大家来看 AppClassLoader
的创始进度就明明白白比非常多了。追踪 getAppClassLoader()
方法的调用进程,能够看看那么些点子本人将 ExtClassLoader
的实例作为参数字传送入,最终依然调用了 java.net.URLClassLoader
的构造方法,将 ExtClassLoader 的实例作为父构造器 parent
参数值传入。所以那边大家又有啥不可明确,AppClassLoader 的父构造器便是ExtClassLoader。
- 系统强弱正视混乱、弱依赖无降级;
- 系统流量剧增,系统体积不足,未有限流熔断机制;
- 硬件财富互联网出现难题影响系统运营,未有高可用的互连网架构。
那二日布置
效能迭代:
- 升高 JVM 练习场景,协理越多的 Java 主流框架,如 Redis,GRPC
- 加强 Kubernetes 演练场景
- 追加对 C++、Node.js 等采取的补助
(2)Linking:验证与剖判,包罗3步:
<1>字节码验证
<2>类希图:计划代表每种类中定义的字段、方法和贯彻接口所需的数据结构
<3>分析:这一个阶段类装入器转入类所利用的其余类
怎么加载二个类
将一个 .class
字节码文件加载到 JVM 中产生一个 java.lang.Class
实例供给加载这一个类的类加载器及其全数的父级加载器共同参加形成,那重大是依据「双亲委派原则」。
丰富多彩的标题,在这种复杂的信赖性结构下被推广,多少个依赖二17个SOA服务的系统,每种服务99.99%可用。99.99%的三拾一遍方≈99.7%。0.3%表示一亿次呼吁会有3,000,00次失利,换算成时间大要每月有2个钟头服务不安静。随着服务正视数量的变多,服务不安宁的票房价值会呈指数性进步,这个难题最后都会转化为故障表现出来。
社区一起创建:
应接访问 ChaosBlade@GitHub,出席社区一起创建,富含但不压制:
- 框架结构划虚构计
- 模块设计
- 代码完成
- Bug Fix
- Demo样例
- 文书档案、网址和翻译
本文小编:中亭
读书原作
本文来源云栖社区合营友人“ 阿里能力”,如需转发请联系原著者。
(3)初始化class对象,推行静态初阶化器并在那阶段末尾初叶化静态字段为暗中同意值
老人民委员会派
当大家要加载贰个应用程序 classpath
下的自定义类时,AppClassLoader
会首先查看自个儿是或不是业已加载过这一个类,借使已经加载过则直接重回类的实例,不然将加载职责委托给自个儿的父加载器
ExtClassLoader。同样,ExtClassLoader
也会先查看自身是还是不是业已加载过这一个类,倘诺已经加载过则一向再次回到类的实例,不然将加载职分委托给和睦的父加载器
BootStrapClassLoader。
BootStrapClassLoader 收到类加载职务时,会率先检查自个儿是或不是已经加载过那个类,如若已经加载则一向再次回到类的实例,不然在自身承担的加载路线下搜索那些类并尝试加载。纵然找到了那么些类,则进行加载职责并赶回类实例,不然将加载职责交给 ExtClassLoader 去实施。
ExtClassLoader 一样也在团结承受的加载路线下搜索那个类并尝试加载。如果找到了那个类,则实施加载职务并赶回类实例,不然将加载职责交给 AppClassLoader 去施行。
是因为自身的父加载器 ExtClassLoader 和 BootStrapClassLoader
都未能成功加载到那几个类,所以最终由 AppClassLoader
来尝试加载。一样,AppClassLoader 会在 classpath
下全体的类库中寻觅这一个类并尝试加载。假设最后还是没有找到那几个类,则抛出
ClassNotFoundException
异常。
综上,当类加载器要加载叁个类时,倘若自个儿一度未有加载过这么些类,则少有升高委托给父加载器尝试加载。对于 AppClassLoader 来说,它上边有 ExtClassLoader 和 BootStrapClassLoader,所以大家称为「双亲委派」。可是如果我们是使用自定义类加载器来加载类,且那么些自定义类加载器的默许父加载器是 AppClassLoader 时,它下面就有多少个父加载器,这时再说「双亲」就不太适合了。当然,精通了加载一个类的整套工艺流程,那些名字就毫无干系痛痒了。
二、系统高可用的方法论
4.周边加载类错误分析
缘何需求家长江水利委员会派机制
「双亲委派机制」最大的补益是制止自定义类和骨干类库龃龉。比如大家大批量应用的
java.lang.String
类,即使我们和好写的三个 String
类被加载成功,那对于利用系统的话完全部都以毁灭性的毁伤。大家得以品味着写三个自定义的
String 类,将其包也安装为 java.lang
:
package java.lang;public class String { private int n; public String { this.n = n; } public String toLowerCase() { return new String(this.n + 100); }}
我们将其成立成一个 jar 包,命名称叫 thief-jdk
,然后写多少个测验类尝试加载
java.lang.String
并利用抽取一个 int 类型参数的构造方法创设实例。
import java.lang.reflect.Constructor;public class Test { public static void main(String[] args) throws Exception { Class<?> clz = Class.forName("java.lang.String"); System.out.println(clz.getClassLoader() == null); Constructor<?> c = clz.getConstructor(int.class); String str = c.newInstance; str.toLowerCase(); }}
运转测验程序
java -cp /Users/yu/Desktop/lib/thief/thief-jdk.jar:. Test
程序抛出 NoSuchMethodException 至极,因为 JVM 不可见加载大家自定义的
java.lang.String
,而是从 BootStrapClassLoader
的缓存中回到了着力类库中的 java.lang.String
的实例,且基本类库中的
String 没有接过 int 类型参数的构造方法。同有的时候间大家也看到 Class
实例的类加载器是 null
,那也印证了我们得到的 java.lang.String
的实例确实是由 BootStrapClassLoader 加载的。
一句话来讲,「双亲委派」机制的效果就是确认保证类的独一性,最直接的例证正是防止大家自定义类和骨干类库冲突。
哪些营造三个高可用的体系啊?首先要剖析一下不可用的成分都有哪些:
(1)ClassNotFoundException:
普普通通是jvm要加载一个文件的字节码到内部存款和储蓄器时,未有找到这一个字节码(如forName,loadClass等艺术)
JVM 怎么剖断多个类是一致的
「双亲委派」机制用来保障类的唯一性,那么 JVM 通过什么样条件来决断独一性呢?其实很轻易,只要四个类的全路线名称一致,且都以同三个类加载器加载,那么就决断那四个类是同样的。若是同样份字节码被不一致的多少个类加载器加载,那么它们就不会被 JVM 判定为同叁个类。
Person 类
public class Person { private Person p; public void setPerson(Object obj) { this.p = obj; }}
setPerson(Object obj)
方法接收八个指标,并将其强制调换为 Person
类型赋值给变量 p。
测试类
import java.lang.reflect.Method;public class Test { public static void main(String[] args) { CustomClassLoader classLoader1 = new CustomClassLoader("/Users/yu/Desktop/lib"); CustomClassLoader classLoader2 = new CustomClassLoader("/Users/yu/Desktop/lib"); try { Class c1 = classLoader1.findClass("Person"); Object instance1 = c1.newInstance(); Class c2 = classLoader2.findClass("Person"); Object instance2 = c2.newInstance(); Method method = c1.getDeclaredMethod("setPerson", Object.class); method.invoke(instance1, instance2); } catch (Exception e) { e.printStackTrace(); } }}
CustomClassLoader
是叁个自定义的类加载器,它将字节码文件加载为字符数组,然后调用
ClassLoader 的 defineClass()
方法创制类的实例,后文仲详细疏解怎么自定义类加载器。在测量检验类中,我们创造了七个类加载器的实例,让他们各自去加载同一份字节码文件,即
Person 类的字节码。然后在实例一上调用 setPerson()
方法将实例二传入,将实例二强制转型为实例一。
运作程序拜望到 JVM 抛出了 ClassCastException
相当,格外音信为
Person cannot be cast to Person
。从那我们就可以驾驭,同一份字节码文件,假诺应用的类加载器区别,那么
JVM 就可以咬定他们是例外的品类。
(2)NoClassDefFoundError:
经常是运用new关键字,属性援用了某些类,承袭了某些类或接口,但JVM加载这么些类时开采那么些类荒诞不经的百般
一同肩负
「全盘负担」是类加载的另二个规范。它的意味是假如类 A 是被类加载器 X 加载的,那么在尚未显得钦赐别的类加载器的情形下,类 A 援用的其余兼具类都由类加载器 X 担负加载,加载进程遵循「双亲委派」原则。大家编辑多个类来证实「全盘负担」原则。
Worker 类
package com.ganpengyu.full;import com.ganpengyu.boot.DateUtils;public class Worker { public Worker() { } public void say() { DateUtils dateUtils = new DateUtils(); System.out.println(dateUtils.getClass().getClassLoader() == null); dateUtils.printNow(); }}
DateUtils 类
package com.ganpengyu.boot;import java.text.SimpleDateFormat;import java.util.Date;public class DateUtils { public void printNow() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(new Date; }}
测试类
import com.ganpengyu.full.Worker;import java.lang.reflect.Constructor;public class Test { public static void main(String[] args) throws Exception { Class<?> clz = Class.forName("com.ganpengyu.full.Worker"); System.out.println(clz.getClassLoader() == null); Worker worker = clz.newInstance(); worker.say(); }}
运作测验类
java -Xbootclasspath/a:/Users/yu/Desktop/lib/worker.jar Test
运转结果
truetrue2018-09-16 22:34:43
我们将 Worker 类和 DateUtils 类制作成名称叫worker
的 jar
包,将其设置为由根加载器加载,那样 Worker
类就料定是被根加载器加载的。然后在 Worker 类的 say()
方法中初叶化了
DateUtils 类,然后决断 DateUtils
类是或不是由根加载器加载。从运营结果看出,Worker 和其援用的 DateUtils
类都被跟加载器加载,符合类加载的「全盘委托」原则。
「全盘委托」原则实际是为「双亲委派」原则提供了担保。假诺不屈从「全盘委托」原则,那么等同份字节码或然会被 JVM 加载出多个分化的实例,那就能够导致应用系统中对此类援引的混乱,具体能够仿效上文「JVM 怎么决断多个类是同等的」这一节的演示。
高可用系统独立执行
(3)UnsatisfiedLinkErrpr:
如native的办法找不到本机的lib
自定义类加载器
除去使用 JVM 预约义的三体系加载器外,Java 还同意我们自定义类加载器以让大家系统的类加载格局更加灵敏。要自定义类加载器特别轻巧,平常只须要八个步骤:
- 继承
java.lang.ClassLoader
类,让 JVM 知道那是四个类加载器 - 重写
findClass(String name)
方法,告诉 JVM 在使用那些类加载器时应有按什么格局去追寻.class
文件 - 调用
defineClass(String name, byte[] b, int off, int len)
方法,让 JVM 加载上一步读取的.class
文件
import java.io.*;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.nio.file.Files;import java.nio.file.Path;import java.nio.file.Paths;public class CustomClassLoader extends ClassLoader { private String classpath; public CustomClassLoader(String classpath) { this.classpath = classpath; } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String classFilePath = getClassFilePath; byte[] classData = readClassFile(classFilePath); return defineClass(name, classData, 0, classData.length); } public String getClassFilePath(String name) { if (name.lastIndexOf(".") == -1) { return classpath + "/" + name + ".class"; } else { name = name.replace(".", "/"); return classpath + "/" + name + ".class"; } } public byte[] readClassFile(String filepath) { Path path = Paths.get; if (!Files.exists { return null; } try { return Files.readAllBytes; } catch (IOException e) { throw new RuntimeException("Can not read class file into byte array"); } } public static void main(String[] args) { CustomClassLoader loader = new CustomClassLoader("/Users/leon/Desktop/lib"); try { Class<?> clz = loader.loadClass("com.ganpengyu.demo.Person"); System.out.println(clz.getClassLoader().toString; Constructor<?> c = clz.getConstructor(String.class); Object instance = c.newInstance("Leon"); Method method = clz.getDeclaredMethod("say", null); method.invoke(instance, null); } catch (Exception e) { e.printStackTrace(); } }}
以身作则中我们透过承继 java.lang.ClassLoader
创设了二个自定义类加载器,通过构造方法钦定这几个类加载器的类路线(classpath)。重写
findClass(String name)
方法自定义类加载的艺术,在这之中
getClassFilePath(String filepath)
方法和
readClassFile(String filepath)
方法用于找到钦定的 .class
文件并加载成二个字符数组。最终调用
defineClass(String name, byte[] b, int off, int len)
方法成功类的加载。
在 main()
方法中大家测验加载了八个 Person 类,通过
loadClass(String name)
方法加载二个 Person 类。大家自定义的
findClass(String name)
方法,正是在那其间调用的,大家把这些措施轻松展示如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock { // 先检查是否已经加载过这个类 Class<?> c = findLoadedClass; if (c == null) { long t0 = System.nanoTime(); try { // 否则的话递归调用父加载器尝试加载 if (parent != null) { c = parent.loadClass(name, false); } else { // 所有父加载器都无法加载,使用根加载器尝试加载 c = findBootstrapClassOrNull; } } catch (ClassNotFoundException e) {} if (c == null) { // 所有父加载器和根加载器都无法加载 // 使用自定义的 findClass() 方法查找 .class 文件 c = findClass; } } return c; }}
能够观望 loadClass(String name)
方法内部是比照「双亲委派」机制来实现类的加载。在「双亲」都未能成功加载类的景色下才调用大家自定义的
findClass(String name)
方法寻觅目的类执行加载。
理论上来讲,当图中负有的政工都做完,大家就能够以为系统是一个实在的高可用系统。但正是那样吧?
5.常用classLoader(书本此处其实是对tom加载servlet使用的classLoader深入分析)
干什么须要自定义类加载器
自定义类加载器的用处有看不完,这里大致列举部分广阔的光景。
- 从随飞机地点置加载类。JVM 预约义的四个类加载器都被限定了自个儿的类路线,大家能够经过自定义类加载器去加载其余随便地点的类。
- 解密类文件。比方大家能够对编写翻译后的类公事实行加密,然后经过自定义类加载器进行解密。当然这种格局其实并未太大的用处,因为自定义的类加载器也得以被反编写翻译。
- 支撑越来越灵敏的内部存款和储蓄器管理。大家能够利用自定义类加载器在运维时卸载已加载的类,进而更迅捷的选用内部存款和储蓄器。
那么故障演习平台就喜庆进场了。当上述的高可用实施都做完,利用故障练习平台做三遍真正的故障练习,在系统运转期动态地注入一些故障,进而来评释下系统是还是不是比照故障预案去施行相应的降级恐怕熔断计谋。
(1)AppClassLoader:
加载jvm的classpath中的类和tomcat的主旨类
就这么啊
类加载器是 Java 中格外宗旨的技术,本文仅对类加载器进行了比较通俗的深入分析,如若需求深切更底层则要求大家张开JVM 的源码举行研读。「Java 有路勤为径,JVM 无涯苦作舟」,与君共勉。
三、故障演习平台
(2)StandardClassLoader:
加载tomcat容器的classLoader,另外webAppClassLoader在loadclass时,发掘类不在JVM的classPath下,在PackageTriggers(是一个字符串数组,包蕴一组无法利用webAppClassLoader加载的类的包名字符串)下的话,将由该加载器加载(注意:StandardClassLoader并从未覆盖loadclass方法,所以其加载的类和AppClassLoader加载没什么分别,并且选取getClassLoader重回的也是AppClassLoader)(别的,要是web应用直接放在tomcat的webapp目录下该应用就能够因而StandardClassLoader加载,测度是因为webapp目录在PackageTriggers中?)
本文由巴黎人-智能硬件发布,转载请注明来源:如果你想要提升开发效率,通常是jvm要加载一个