您的位置:澳门皇冠844网站 > 澳门皇冠金沙网站 > 去哪儿系统高可用之法,网络性能监控

去哪儿系统高可用之法,网络性能监控

发布时间:2019-12-15 19:54编辑:澳门皇冠金沙网站浏览(155)

    原标题:去何地系统高可用之法:搭建故障演习平台

    涉嫌知识点:APM, java Agent, plugin, bytecode, asm, InvocationHandler, smail

    Classloader担任将Class加载到JVM中,何况明确由非常ClassLoader来加载(父优先的级差加运载飞机制)。还会有三个职分正是将Class字节码重新讲解为JVM统意气风发必要的格式

    在面向对象编制程序施行中,我们通过重重的类来组织二个复杂的种类,这一个类之间交互作用关联、调用使他们的涉嫌产生了二个长短不一紧凑的互连网。当系统运营时,出于质量、财富使用多地点的构思,大家不也许要求JVM 一遍性将总体的类都加载成功,而是只加载能够匡助系统顺遂运维和周转的类和能源就可以。那么在系统运营进程中风度翩翩经急需选用未在运转时加载的类或财富时该怎么做呢?那将在靠类加载器来成功了。

    小编介绍

    APM : 应用程序品质管理。 二零一三年时海外的APM行当 NewRelic 和 APPDynamics 已经在该领域拔得头筹,本国近来来也现身实形势部APM厂家,如: 听云, OneAPM, 博睿 云智慧,Ali百川码力。 (据深入分析,本国android端方案都以抄袭NewRelic集团的,由于该铺面包车型地铁sdk未混淆,产业界良心卡塔尔国

    1.Classloader类构造拆解深入分析

    什么是类加载器

    类加载器(ClassLoader)正是在系统运作进程中动态的将字节码文件加载到 JVM 中的工具,基于那一个工具的一切类加载流程,大家称作类加运载飞机制。大家在 IDE 中编辑的都以源代码文件,现在缀名 .java 的公文形式存在于磁盘上,通过编写翻译后生成后缀名 .class 的字节码文件,ClassLoader 加载的便是那一个字节码文件。

    王鹏,前年加入去何方机票工作部,重要从事后端研发专门的学问,目前在机票工作部担负行程单和故障演习平台以致公共服务ES、数据同步中间件等辅车相依的研究开发工作。

    能做什么样: crash监察和控制,卡顿监察和控制,内部存储器监控,扩展trace,网络品质监察和控制,app页面自动埋点,等。

    (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,依次前者分别是前边二个的「父加载器」。父加载器不是「父类」,三者之间未有继续关系,只是因为类加载的流程使三者之间产生了父子关系,下文少禽详细陈诉。

    去哪个地方网二零零五年构建到现在,随着系统规模的渐渐扩张,已经有非常多少个应用系统,那个系统里面包车型客车耦合度和链路的复杂度不断压实,对于我们营造遍布式高可用的系统结构具有一点都不小挑战。我们须要叁个平台在运维期自动注入故障,核算故障预案是不是起效——故障演习平台。

    质量监察和控制其实正是hook 代码到项目代码中,进而成就种种监督。常规手腕都以在品种中追加代码,但什么做到非侵入式的,即三个sdk就能够。

    (2卡塔尔(قطر‎落成自定义ClassLoader经常会持续U福睿斯LClassLoader类,因为那一个类实现了大部分艺术。

    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 那一个根加载器大家供给明白三点:

    1. 根加载器使用 C/C++ 编写,大家无法在 Java 中获取其实例
    2. 根加载器暗中认可加载系统变量 sun.boot.class.path 钦定的类库
    3. 能够行使 -Xbootclasspath/a 参数追加根加载器的暗中同意加载类库

    一、背景

    1. 如何hook

    断面编制程序-- AOP。大家的方案是AOP的一种,通过改革app class字节码的款型将大家项指标class文件实行改换,进而成就放松权利大家的监督代码。

    图片 1androidbuilder.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就出台了,参谋首节。

    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 那个扩充类加载器大家须要驾驭两点:

    1. 扩张类加载器是应用 Java 落成的类加载器,大家得以在前后相继中获得它的实例并利用。
    2. 日常不建议匡正java.ext.dirs 参数的值来修正默许加载目录,如有需求,能够就要加载的类库放到那么些私下认可目录下。

    那是有些事业部的系统拓扑图:

    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卡塔尔(英语:State of Qatar)

    有了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。
    • 小而快、非常可信赖。
    • 享有巨大的顾客社区,可感到新客商ﰁ供扶助。
    • 源许可开放,差不离允许私行使用。

    图片 2ASM_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的第四个参数是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(卡塔尔(英语:State of Qatar)获得。不过到了此间大家的程序恐怕还是无法正确退换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讲明就可以。

    (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,是继续自U讴歌MDXLClassLoader或其子类,其父加载器(注意:父加载器与父类的分别)都以AppClassLoader,因为不管调用哪个父类的布局器,最后都将调用getSystemClassLoader作为父加载器,而该方法重回的难为AppClassLoader。(当应用程序中尚无任何自定义的classLoader,那么除了System.getProperty(“java.ext.dirs”)目录中的类,其余类都由AppClassLoader加载)

    AppClassLoader

    AppClassLoader 也叫「应用类加载器」,它和 ExtClassLoader 相仿,也是行使 Java 完成的类加载器(sun.misc.Launcher.AppClassLoader)。它的成效是加载应用程序 classpath 下全部的类库。那是我们最常打交道的类加载器,大家在程序中调用的超级多 getClassLoader() 方法再次来到的都是它的实例。在咱们自定义类加载器时生龙活虎旦未有特地钦赐,那么大家自定义的类加载器的暗中同意父加载器也是其一应用类加载器。同理可得,对于 AppClassLoader 那些应用类加载器我们必要通晓两点:

    1. 应用类加载器是运用 Java 完结的类加载器,担任加载应用程序 classpath 下的类库。
    2. 应用类加载器是和大家最常打交道的类加载器。
    3. 未曾刻意钦点的状态下,自定义类加载器的父加载器正是应用类加载器。

    图片 3

    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;
    

    翻开伪代码, 契合预期结果。验证完成。

    (2卡塔尔国Jvm加载class文件到内装有二种艺术,隐式加载和出示加载,平时那三种方式是混合使用的
    • <1>隐式加载:是经过JVM来自动加载需求的类到内部存款和储蓄器的主意,当有个别类被使用时,JVM开掘此类不在内部存款和储蓄器中,那么它就能够活动加载该类到内部存款和储蓄器

    • <2>展现加载:通过调用this.getClasss.getClassLoader.loadClass(卡塔尔,Class.forName,自身实现的ClassLoader的findClass方法

    自定义类加载器

    除了这些之外上述二种 Java 暗中同意提供的类加载器外,大家还足以由此世襲 java.lang.ClassLoader 来自定义叁个类加载器。尽管在开立自定义类加载器时未有一些名父加载器,那么私下认可使用 AppClassLoader 作为父加载器。关于自定义类加载器的创办和利用,我们会在后头的章节详细解说。

    系统里头的正视特别复杂、调用链路很深、服务时期向来不分支。在这里种复杂的看重性下,系统发生了几起故障:

    (3卡塔尔国上级委托机制:当一个加载器加载类字时,先委托其父加载器加载,若加载成功则反映给该加载器,若父加载器不可能加载,则由该加载器加载

    类加载器的启航顺序

    上文已经涉及过 BootStrapClassLoader 是八个使用 C/C++ 编写的类加载器,它曾经放手到了 JVM 的根底之中。当 JVM 运维时,BootStrapClassLoader 也会跟着运维并加载焦点类库。当宗旨类库加载成功后,BootStrapClassLoader 会创设 ExtClassLoader 和 AppClassLoader 的实例,五个 Java 达成的类加载器将会加载本人担任路线下的类库,这一个进程大家得以在 sun.misc.Launcher 中窥见。

    • 弱信赖挂掉,主流程挂掉,校正报销凭据的支付景况,下单主流程战败;
    • 主导服务调用量陡增,某服务超时引起相关联的享有服务“雪崩”;
    • 机房网络可能有个别机器挂掉,不能够提供基本服务。

    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。

    四个故障原因:

    (1卡塔尔(قطر‎加载字节码到内存:(这一步经常经过findclass(卡塔尔(英语:State of Qatar)方法完成)

    以UGL450LClassLoader为例:该类的布局函数返现必需制订叁个U奇骏L数据技巧创造该对象,该类中隐含叁个UCRUISERLClassPath对象,UCR-VLClass帕特h会判别传过来的UPAJEROL是文件或然Jar包,创造相应的FileLoader恐怕JarLoader也许暗中认可加载器,当jvm调用findclass时,这个加载器将class文件的字节码加载到内部存储器中

    AppClassLoader 的创设进度

    理清了 ExtClassLoader 的开创进度,大家来看 AppClassLoader 的创造进度就明明白白非常多了。追踪 getAppClassLoader() 方法的调用进程,能够见见这么些格局本身将 ExtClassLoader 的实例作为参数字传送入,最后仍然调用了 java.net.URLClassLoader 的构造方法,将 ExtClassLoader 的实例作为父布局器 parent 参数值传入。所以这里大家又足以明确,AppClassLoader 的父布局器就是ExtClassLoader。

    • 系统强弱信任混乱、弱注重无降级;
    • 系统流量大幅度增加,系统体积不足,未有限流熔断机制;
    • 硬件能源网络现身难点影响系统运行,未有高可用的网络构造。
    (2卡塔尔Linking:验证与剖析,满含3步:
    • <1>字节码验证

    • <2>类构思:计划代表种种类中定义的字段、方法和落到实处接口所需的数据布局

    • <3>拆解解析:这些等第类装入器转入类所利用的别样类

    怎么加载五个类

    将一个 .class 字节码文件加载到 JVM 中变为三个 java.lang.Class 实例须要加载这一个类的类加载器及其全数的父级加载器协作参与形成,那关键是比照「双亲委派原则」。

    丰富多彩的标题,在这里种复杂的依靠构造下被放大,三个借助贰二十个SOA服务的系统,每一种服务99.99%可用。99.99%的34次方≈99.7%。0.3%意味风流洒脱亿次呼吁会有3,000,00次退步,换算成时间差相当少每月有2个小时服务不平稳。随着服务信赖数量的变多,服务不牢固的概率会呈指数性进步,这一个难点最后都会转接为故障表现出来。

    (3卡塔尔(英语:State of Qatar)起初化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 就能咬定他们是莫衷一是的门类。

    图片 4

    (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 还允许我们自定义类加载器以让我们系统的类加载情势更加灵活。要自定义类加载器特别轻易,常常只供给四个步骤:

    1. 继承 java.lang.ClassLoader 类,让 JVM 知道那是一个类加载器
    2. 重写 findClass(String name) 方法,告诉 JVM 在使用这些类加载器时应当按什么办法去寻觅 .class 文件
    3. 调用 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剖析)

    怎么须要自定义类加载器

    自定义类加载器的用项有成千上万,这里大约列举部分科学普及的场景。

    1. 从随飞机地点置加载类。JVM 预约义的几个类加载器都被界定了本人的类路线,我们得以由此自定义类加载器去加载别的随便地点的类。
    2. 解密类文件。比方大家得以对编写翻译后的类公事实行加密,然后经过自定义类加载器进行解密。当然这种方法其实并从未太大的用场,因为自定义的类加载器也得以被反编写翻译。
    3. 支撑越来越灵活的内存管理。大家得以选取自定义类加载器在运维时卸载已加载的类,进而更飞速的利用内部存储器。

    那么故障练习平台就震耳欲聋进场了。当上述的高可用实施都做完,利用故障练习平台做壹次真正的故障练习,在系统运转期动态地流入一些故障,进而来证实下系统是还是不是比照故障预案去推行相应的降级也许熔断计策。

    (1)AppClassLoader:

    加载jvm的classpath中的类和tomcat的宗旨类

    就这么吧

    类加载器是 Java 中足够主旨的技巧,本文仅对类加载器实行了比较通俗的深入深入分析,若是要求深刻更底层则必要大家开辟JVM 的源码举办研读。「Java 有路勤为径,JVM 无涯苦作舟」,与君共勉。

    三、故障演习平台

    (2)StandardClassLoader:

    加载tomcat容器的classLoader,其它webAppClassLoader在loadclass时,开采类不在JVM的classPath下,在PackageTriggers(是贰个字符串数组,富含后生可畏组不能够运用webAppClassLoader加载的类的包名字符串卡塔尔(قطر‎下的话,将由该加载器加载(注意:StandardClassLoader并从未覆盖loadclass方法,所以其加载的类和AppClassLoader加载没什么分别,并且应用getClassLoader重回的也是AppClassLoader卡塔尔(英语:State of Qatar)(其余,要是web应用直接放在tomcat的webapp目录下该使用就可以经过斯坦dardClassLoader加载,预计是因为webapp目录在PackageTriggers中?卡塔尔国

    故障演习平台:查查故障预案是或不是真的的起成效的阳台。

    (3)webAppClassLoader如:

    Servlet等web应用中的类的加载(loadclass方法的规行矩步详见P169)

    故障类型:首要不外乎运转期相当、超时等等。通过对系统有个别服务动态地流入运行期相当来达到模拟故障的目标,系统依据预案实行相应的攻略验证系统是或不是是真正的高可用。

    6.自定义的classloader

    1、故障练习平台的总体结构

    (1卡塔尔须求采取自定义classloader的场所
    • <1>不在System.getProperty("java.class.path"卡塔尔中的类公事不得以被AppClassLoader找到(LoaderClass方法只会去classpath下加载特定类名的类),当class文件的字节码不在ClassPath就须求自定义classloader

    • <2>对加载的某个类必要作极其处理

    • <3>定义类的时效机制,对已经校正的类重新加载,达成热布署

    故障练习平台构造首要分为四片段:

    (2卡塔尔加载自定义路线中的class文件
    • <1>加载特定来源的一点类:重写find方法,使特定类或许特定来源的字节码 通过defineClass获得class类并回到(应该适合jvm的类加载标准,别的类仍利用父加载器加载卡塔尔(英语:State of Qatar)

    • <2>加载自顶三个是的class文件(如通过网络盛传的经过加密的class文件字节码卡塔尔国:findclass中加密后再加载

    本文由澳门皇冠844网站发布于澳门皇冠金沙网站,转载请注明出处:去哪儿系统高可用之法,网络性能监控

    关键词: