1. 程式人生 > >深入位元組碼 -- 計算方法執行時間(ONE APM基礎原理窺探)

深入位元組碼 -- 計算方法執行時間(ONE APM基礎原理窺探)

市面上有聽雲、oneapm等效能分析工具,通過對使用的APK反編譯分析,他們提供的PLUGIN都做了很重要的一件事情,就是在class檔案進行了程式碼打點,

也就是在實際的程式碼上做了他們自己的一些程式碼以便於用於效能分析。

詳細的技術參考:http://www.tuicool.com/articles/7zYR3aU

什麼是位元組碼?

java 程式通過 javac 編譯之後生成檔案 .class 就是位元組碼集合,正是有這樣一種 中間碼(位元組碼) ,使得 scala/groovy/clojure 等函式語言只用實現一個編譯器即可執行在 JVM 上。 看看一段簡單程式碼。

    public
long getExclusiveTime()
{ long startTime = System.currentTimeMillis(); System.out.printf("exclusive code"); long endTime = System.currentTimeMillis(); return endTime - startTime; } public class com.blueware.agent.StartAgent {

編譯後通過命令( javap -c com.blueware.agent.StartAgent

)檢視,具體含義請參考 oracle

    public com.blueware.agent.StartAgent();
        Code:
           0: aload_0
           1: invokespecial #1  // Method java/lang/Object."<init>":()V
           4: return

      public long getExclusiveTime();
        Code:
           0: invokestatic  #2                  // Method java/lang/System.currentTimeMillis:()J
3: lstore_1 4: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 7: ldc #4 // String exclusive code 9: iconst_0 10: anewarray #5 // class java/lang/Object 13: invokevirtual #6 // Method java/io/PrintStream.printf:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/io/PrintStream; 16: pop 17: invokestatic #2 // Method java/lang/System.currentTimeMillis:()J 20: lstore_3 21: lload_3 22: lload_1 23: lsub 24: lreturn }

為什麼要學習位元組碼?

  • 能瞭解技術背後的原理,更容易寫出高質量程式碼;
  • 位元組碼設計非常優秀,發展十幾年只僅僅刪除和增加幾個指令,學懂之後長期受益高,如果懂位元組碼再學習 scala/groovy/clojure 會容易很多;
  • 開發框架、監控系統、中介軟體、語言位元組碼技術都是必殺技;

位元組碼框架( ASM/Javassist )

操作位元組碼框架有很多,具體可以參考 博文 ,下面對比 ASM/Javassist

|選項 | 優點 |缺點 | |--------------|----------|-------------| | ASM |速度快、程式碼量小、功能強大|要寫位元組碼、學習曲線高| | Javassist |學習簡單,不用寫位元組碼|比 ASM 慢,功能少|

Java Instrumentation 介紹

指的是可以用獨立於應用程式之外的代理( agent )程式, agent 程式通過增強位元組碼動態修改或者新增類,利用這樣特性可以設計出更通用的監控、框架、中介軟體程式,在 JVM 啟動引數加 –javaagent:agent_jar_path/agent.jar 即可執行(在 JDK5 及其後續版本才可以),更多關於 Instrumentation 知識請參考 博文

計算方法執行時間方式

  • 直接在程式碼開始和結束出列印當前時間,相減即可得到;
  • 實現一個動態代理,或者藉助 Spring/AspectJ 等框架;
  • 上面兩種實現方式都需要修改程式碼或者配置檔案,下面我要介紹方式不僅不需要修改程式碼,而且效率高;

具體實現方式

1.StartAgent 類必須提供 premain 方法,程式碼如下:

    public class StartAgent {
        //代理程式入口函式
        public static void premain(String args, Instrumentation inst) {
            System.out.println("agent begin");
            //新增位元組碼轉換器
            inst.addTransformer(new PrintTimeTransformer());
            System.out.println("agent end");
        }
    }

2.PrintTimeTransformer 實現一個轉換器,程式碼如下:

        //位元組碼轉化器類
    public class PrintTimeTransformer implements ClassFileTransformer {

        //實現位元組碼轉化介面,一個小技巧建議實現介面方法時寫@Override,方便重構
        //loader:定義要轉換的類載入器,如果是引導載入器,則為 null(在這個小demo暫時還用不到)
        //className:完全限定類內部形式的類名稱和中定義的介面名稱,例如"java.lang.instrument.ClassFileTransformer"
        //classBeingRedefined:如果是被重定義或重轉換觸發,則為重定義或重轉換的類;如果是類載入,則為 null
        //protectionDomain:要定義或重定義的類的保護域
        //classfileBuffer:類檔案格式的輸入位元組緩衝區(不得修改)
        //一個格式良好的類檔案緩衝區(轉換的結果),如果未執行轉換,則返回 null。
        @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)
                throws IllegalClassFormatException {
            //簡化測試demo,直接寫待修改的類(com/blueware/agent/TestTime)
            if (className != null && className.equals("com/blueware/agent/TestTime")) {
                //讀取類的位元組碼流
                ClassReader reader = new ClassReader(classfileBuffer);
                //建立操作位元組流值物件,ClassWriter.COMPUTE_MAXS:表示自動計算棧大小
                ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_MAXS);
                //接受一個ClassVisitor子類進行位元組碼修改
                reader.accept(new TimeClassVisitor(writer, className), 8);
                //返回修改後的位元組碼流
                return writer.toByteArray();
            }
            return null;
        }
    }

3.TimeClassVisitor 類訪問器,實現位元組碼修改,程式碼如下:

        //定義掃描待修改class的visitor,visitor就是訪問者模式
    public class TimeClassVisitor extends ClassVisitor {
        private String className;

        public TimeClassVisitor(ClassVisitor cv, String className) {
            super(Opcodes.ASM5, cv);
            this.className = className;
        }

        //掃描到每個方法都會進入,引數詳情下一篇博文詳細分析
        @Override public MethodVisitor visitMethod(int access, final String name, final String desc, String signature, String[] exceptions) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            final String key = className + name + desc;
            //過來待修改類的建構函式
            if (!name.equals("<init>") && mv != null) {
                mv = new AdviceAdapter(Opcodes.ASM5, mv, access, name, desc) {
                    //方法進入時獲取開始時間
                    @Override public void onMethodEnter() {
                        //相當於com.blueware.agent.TimeUtil.setStartTime("key");
                        this.visitLdcInsn(key);
                        this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "setStartTime", "(Ljava/lang/String;)V", false);
                    }

                    //方法退出時獲取結束時間並計算執行時間
                    @Override public void onMethodExit(int opcode) {
                        //相當於com.blueware.agent.TimeUtil.setEndTime("key");
                        this.visitLdcInsn(key);
                        this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "setEndTime", "(Ljava/lang/String;)V", false);
                        //向棧中壓入類名稱
                        this.visitLdcInsn(className);
                        //向棧中壓入方法名
                        this.visitLdcInsn(name);
                        //向棧中壓入方法描述
                        this.visitLdcInsn(desc);
                        //相當於com.blueware.agent.TimeUtil.getExclusiveTime("com/blueware/agent/TestTime","testTime");
                        this.visitMethodInsn(Opcodes.INVOKESTATIC, "com/blueware/agent/TimeUtil", "getExclusiveTime", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", false);
                    }
                };
            }
            return mv;
        }
    }

4.TimeClassVisitor 記錄時間幫助類,程式碼如下:

  public class TimeUtil {
        private static Map<String, Long> startTimes = new HashMap<String, Long>();
        private static Map<String, Long> endTimes   = new HashMap<String, Long>();

        private TimeUtil() {
        }

        public static long getStartTime(String key) {
            return startTimes.get(key);
        }

        public static void setStartTime(String key) {
            startTimes.put(key, System.currentTimeMillis());
        }

        public static long getEndTime(String key) {
            return endTimes.get(key);
        }

        public static void setEndTime(String key) {
            endTimes.put(key, System.currentTimeMillis());
        }

        public static long getExclusiveTime(String className, String methodName, String methodDesc) {
            String key = className + methodName + methodDesc;
            long exclusive = getEndTime(key) - getStartTime(key);
            System.out.println(className.replace("/", ".") + "." + methodName + " exclusive:" + exclusive);
            return exclusive;
        }
    }

題記

  • 上面的程式碼難免有 bug ,如果你發現程式碼寫的有問題,請你幫忙指出,讓我們一起進步,讓程式碼變的更漂亮和健壯;
  • 順便打點廣告,如果看後對位元組碼技術感興趣,歡迎加入我們oneapm,一起做點有意思事情,可直接聯絡我;
  • 下一篇結合 demo 再深入研究 ClassVisitor

    OneAPM 為您提供端到端的Java 應用效能解決方案,我們支援所有常見的 Java 框架及應用伺服器,助您快速發現系統瓶頸,定位異常根本原因。分鐘級部署,即刻體驗,Java 監控從來沒有如此簡單。想閱讀更多技術文章,請訪問OneAPM 官方技術部落格,還可以掃碼關注下方的Java程式效能優化公眾號。


相關推薦

深入位元組 -- 計算方法執行時間ONE APM基礎原理窺探

市面上有聽雲、oneapm等效能分析工具,通過對使用的APK反編譯分析,他們提供的PLUGIN都做了很重要的一件事情,就是在class檔案進行了程式碼打點, 也就是在實際的程式碼上做了他們自己的一些程式碼以便於用於效能分析。 詳細的技術參考:http://www.tuico

計算方法執行時間

system action on() blog class 幫助 cos clas nbsp /// <summary> /// 方法幫助類 /// </summary> public class ActionHelper

OpenCV 計算執行時間us,ms,s

1. cvGetTickCount()和cvGetTickFrequency()計時,得到的單位是us級的統計時間: double start = static_cast<double>(cvGetTickCount()); double time = ((double)cvGe

python兩個關於計算方法執行時間的修飾器

import time #import sys #def wrapper( func ): # start = time.time() # func( 50 ) # end = time.time() # print

計算火車執行時間c語言

本題要求根據火車的出發時間和達到時間,編寫程式計算整個旅途所用的時間。 輸入格式: 輸入在一行中給出2個4位正整數,其間以空格分隔,分別表示火車的出發時間和到達時間。每個時間的格式為2位小時數(00-23)和2位分鐘數(00-59),假設出發和到達在同一天內。 輸出格式: 在一行輸出該旅途所用的時間,格式為

C#下的時間測試(用於計算方法執行時間)

image sub [] 圖片 display nbsp mes during code 1 public class Timing 2 { 3 private TimeSpan m_StartTime; 4 5

非常精確的測試執行時間比clock()更精確些

// timerTest.cpp : 定義控制檯應用程式的入口點。 // #include "stdafx.h" #include <Windows.h> int _tmain(int argc, _TCHAR* argv[]) {     /*     Que

測試一段C程式碼的執行時間windows系統和ubuntu系統

//測試一段C程式碼的執行時間 #include <stdio.h> //注意這裡標頭檔案有所不同 #include "time.h" int main() // 主函式的形式有所不同 {

int與long 兩種資料型別有什麼區別?|__int64固定大小為8位元組,不受執行環境的CPU和作業系統位數影響

筆記原創: 蘭特 聯絡郵件: [email protected] 系統平臺:linux平臺,gcc 有這樣的一個程式,是關於使用隨機函式rand()的: #include <stdio.h> #include <stdlib.h> #include <time.h> int

springboot 框架計算每個方法執行時間,顯示在日誌中

加入aop的jar <dependency>     <groupId>org.springframework.boot</groupId>     <artifactId>spring-boot-s

C語言 計算/測程式執行時間精確到微秒

 平臺:VS2010 #include<stdio.h> #include <Windows.h> int main() { int a[10002]; int i = 0; double run_time; LARGE_INTEGER time_sta

C/C++中計算函式執行時間的兩種方法

       在寫程式碼中,有時候我們需要評估某段程式碼或者函式的執行時間;方法就是在該段程式碼或者函式前面,記錄一個時間T1,在程式碼段或函式後面記錄時間T2,那其執行時間就是T2-T1,下面看看具體

python+opencv計算程式碼執行時間:time庫和opencv自帶方法getTickCount

import cv2 import time ############################## 利用opencv的兩個函式進行時間耗費計算 # cv2.getTickCount()記錄當前

c/c++測試函式的執行時間八種方法

目前,存在著各種計時函式,一般的處理都是先呼叫計時函式,記下當前時間tstart,然後處理一段程式,再呼叫計時函式,記下處理後的時間tend,再tend和tstart做差,就可以得到程式的執行時間,但是各種計時函式的精度不一樣.下面對各種計時函式,做些簡單記錄.

linux/ubuntu計算程式執行時間方法

C/C++中的計時函式是clock(),而與其相關的資料型別是clock_t。在MSDN中,查得對clock函式定義如下: clock_t clock( void ); 這個函式返回從“開啟這個程式程序”到“程式中呼叫clock()函式”時之間的CPU時鐘計時單元(

c++中常用的計算程式執行時間方法

方法1: 計時函式是clock(),而與其相關的資料型別是clock_t(標頭檔案是time.h)。函式定義原型為:clock_t clock(void); 這個函式返回從“開啟這個程式程序”到“程式中呼叫clock()函式”時之間的CPU時鐘計時單元(clock t

網路層-network layer:網路互連、子網掩計算方法、Ipv4報頭解析

# 第五章 網路層-Network Layer(下) 上一章講了網路層的任務、提供的兩種服務、五個重要的路由演算法、以及網路層的擁塞控制和服務質量問題。這一部分主要講一講網路互連問題和Internet的網路層。(包括IP協議、ip地址、ip報頭格式等等問題) # 5.5 網路互連 在這一部分,我們將主要

子網掩計算方法

位與 地址 局域網 tex -i sub 轉化 tcp/ip 位數 一、子網掩碼的概述及作用 子網掩碼是一個應用於TCP/IP網絡的32位二進制值,每節8位,必須結合IP地址對應使用。 子網掩碼32位都與IP地址32位對應,如果某位是網絡地址,則子網掩碼為1,否則為0。

UITableView!別再用代計算行高了

dev count layout 們的 -o @property 感覺 ref 還在 你還在用代碼去計算行高嗎?你不感覺那種方式很low嗎?從今天起,試著做些改變吧! 別給我講你喜歡寫代碼的感覺,你就是要用代碼去計算行高,那我這篇文章不適合你。 在講解復雜內容之前,還是先學

好代是管出來的——淺談.Net Core的代管理方法與落地更新中...

cor 分支 TP 功能 更新 ims 代碼規範 pull nbsp   軟件開發的目的是在規定成本和時間前提下,開發出具有適用性、有效性、可修改性、可靠性、可理解性、可維護性、可重用性、可移植性、可追蹤性、可互操作性和滿足用戶需求的軟件產品。   而對於整個開發過程來說