1. 程式人生 > >記一次 JVM 原始碼分析(4.直譯器與方法執行)

記一次 JVM 原始碼分析(4.直譯器與方法執行)

簡介

miniJVM 作為一個 mini 的 Java VM,實現了 Switch 直譯器,並不支援主流 JVM 的 JIT 或者更為複雜的 AOT。但這樣對於我們瞭解位元組碼的執行已經足夠了。

位元組碼指令

基於堆疊

位元組碼指令類似於彙編指令,但是不同的是:

  • 一行彙編程式碼的格式一般都是 - opcode 運算元1 運算元2
  • 然而位元組碼指令格式是 opcode + 棧

位元組碼的所有運算元都存在執行棧中,又叫運算元棧,所以可以看到位元組碼中存在大量的入棧出棧操作。這樣做的好處在於更強的跨平臺可能性,畢竟你不知道目標平臺的暫存器狀態或者數量。但是其缺點也是相當明顯的:

比如一條 a + b

指令:

  1. 基於暫存器:
add a, b
  1. 基於堆疊:
load a
load b
add

這樣別人一條指令就能做完的操作,基於堆疊需要3條,前兩條都是引數入棧操作

執行時的情況

由於此類知識網上已有很多,所以我圖省事找了一個現成的例子:

  • Java Code
4. public static int add(int a, int b) {
5.  int c = 0;
6.  c = a + b;
7.  return c;
8. }
  • 位元組碼
public static int add(int, int);
   descriptor: (II)I                    //描述方法引數為兩個int型別的變數和方法的返回型別是int的
   flags: ACC_PUBLIC, ACC_STATIC        //修飾方法public和static
   Code:
     stack=2, locals=3, args_size=2     //運算元棧深度為2,本地變量表容量為3,引數個數為2
        0: iconst_0    //將int值0壓棧
        1: istore_2    //將int值0出棧,儲存到第三個區域性變數(slot)中
        2: iload_0     //將區域性變量表中第一個變數10壓棧
        3: iload_1     //將區域性變量表中第一個變數20壓棧
        4: iadd        //將運算元棧頂兩個int數彈出,相加後再壓入棧中
        5: istore_2    //將棧頂的int數(30)彈出,儲存到第三個區域性變數(slot)中
        6: iload_2     //將區域性變量表中第三個變數壓棧
        7: ireturn     //返回棧中數字30
     LineNumberTable:
       line 5: 0       //程式碼第5行對應位元組碼第0行
       line 6: 2       //程式碼第6行對應位元組碼第2行
       line 7: 6       //程式碼第7行對應位元組碼第6行
     LocalVariableTable:
       Start  Length  Slot  Name   Si
           0       8     0     a   I    //a佔用第1個solt
           0       8     1     b   I    //b佔用第2個solt
           2       6     2     c   I    //c佔用第3個solt

在這裡插入圖片描述
從以上可以總結位元組碼在解釋執行的時候幾個重要的資料結構

  • 區域性變數
  • 運算元棧
  • PC 指標
  • 行號表
  • 指令序列
  • 常量池

資料結構

方法棧

方法棧是方法執行的最基本資料結構

  • 在 native 程式碼其實是一個棧幀,用於儲存所有本地變數,部分方法引數以及方法跳轉時儲存暫存器的值
  • 但是在 java 世界中,方法棧雖然也會儲存類似的資料,但遠不止這些

miniJVM 中方法棧叫做 Runtime

struct _Runtime {

    //方法結構體
    MethodInfo *method;
    //類結構體
    JClass *clazz;
    //pc 指標
    u8 *
pc; //方法位元組碼 CodeAttribute *ca;//method bytecode //當前執行緒資訊 JavaThreadInfo *threadInfo; //子方法 runtime,類似棧 Runtime *son;//sub method's runtime //父方法 runtime Runtime *parent;//father method's runtime //JVM 執行棧,用於基於棧實現的直譯器 RuntimeStack *stack; //方法本地變數 LocalVarItem *localvar; //Runtime 快取 union { Runtime *runtime_pool_header;// cache runtimes for performance Runtime *next; //for runtime pools linklist }; //JNI 結構體 JniEnv *jnienv; s16 localvar_count; s16 localvar_max; u8 wideMode; };
  • Runtime 初始化

Runtime 在一個執行緒中是一個連結串列,每跳轉到一個方法則往後連一個節點,執行緒的第一個 Runtime 額外持有當前執行執行緒的結構體和運算元棧。

/**
 * runtime 的建立和銷燬會極大影響效能,因此對其進行快取
 * @param parent runtime of parent
 * @return runtime
 */
static inline Runtime *runtime_create_inl(Runtime *parent) {
    Runtime *top_runtime = NULL;

    Runtime *runtime = NULL;
    if (parent) {
        top_runtime = parent->threadInfo->top_runtime;
    }

    if (top_runtime) {
        runtime = top_runtime->runtime_pool_header;
        if (runtime) {
            top_runtime->runtime_pool_header = runtime->next;
            runtime->next = NULL;
        }
    }
    if (runtime == NULL) {
        runtime = jvm_calloc(sizeof(Runtime));
        runtime->localvar = jvm_calloc(RUNTIME_LOCALVAR_SIZE * sizeof(LocalVarItem));
        runtime->localvar_max = RUNTIME_LOCALVAR_SIZE;
        runtime->jnienv = &jnienv;
        if (parent) {
            runtime->stack = parent->stack;
            runtime->threadInfo = parent->threadInfo;
        }
    }
    //如果是子方法
    if (parent != NULL) {
        runtime->parent = parent;
        parent->son = runtime;
    } else {
        //如果是根方法,所謂根方法,就是執行緒的第一個方法
        runtime->stack = stack_create(STACK_LENGHT);
        runtime->threadInfo = threadinfo_create();
        runtime->threadInfo->top_runtime = runtime;
    }
    return runtime;
}

區域性變數

區域性變數儲存了方法執行時所有的區域性變數,不僅服務於直譯器;也是 GC 的重要依據,用於判斷執行緒執行時持有了哪些引用。

這裡要注意的是:區域性變數的屬性和 index 資訊儲存在區域性變量表中,而執行時區域性變數真正的值儲存在一個區域性變數陣列結構中。兩者不要搞混

區域性變量表

區域性變量表在類載入中載入 Code 屬性的時候就已經被初始化
區域性變量表長度 = 方法引數數量 + 本地變數數量
方法引數數量和本地變數數量記錄在方法的 Code 屬性中:

 Code:
     stack=2, locals=3, args_size=2     //運算元棧深度為2,本地變量表容量為3,引數個數為2

需要注意的時這裡的 locals 已經等於引數 + 本地變數
回顧一下前面類載入的時候介紹的解析 Code 屬性的一段:

//本地變量表,決定方法棧大小
typedef struct _LocalVarTable {
    u16 start_pc;
    u16 length;
    u16 name_index;
    u16 descriptor_index;
    u16 index;
} LocalVarTable;

else if (utf8_equals_c(class_get_utf8_string(clazz, attribute_name_index), "LocalVariableTable")) {
            s2c.c1 = attr->info[info_p++];
            s2c.c0 = attr->info[info_p++];
            ca->local_var_table_length = (u16) s2c.s;
            ca->local_var_table = jvm_calloc(sizeof(LocalVarTable) * ca->local_var_table_length);
            s32 j;
            for (j = 0; j < ca->local_var_table_length; j++) {
                s2c.c1 = attr->info[info_p++];
                s2c.c0 = attr->info[info_p++];
                ca->local_var_table[j].start_pc = s2c.s;
                s2c.c1 = attr->info[info_p++];
                s2c.c0 = attr->info[info_p++];
                ca->local_var_table[j].length = s2c.s;
                s2c.c1 = attr->info[info_p++];
                s2c.c0 = attr->info[info_p++];
                ca->local_var_table[j].name_index = s2c.s;
                s2c.c1 = attr->info[info_p++];
                s2c.c0 = attr->info[info_p++];
                ca->local_var_table[j].descriptor_index = s2c.s;
                s2c.c1 = attr->info[info_p++];
                s2c.c0 = attr->info[info_p++];
                ca->local_var_table[j].index = s2c.s;
            }
        }

執行時區域性變數

執行時區域性變數時存放指令操作資料的重要地點,相關的有 xload_n,x_store_n 等操作區域性變數的指令。
一個方法的區域性變數陣列的長度 = 方法引數長度 + 方法本地變數長度

  • 一個區域性變數的資料結構

執行時區域性變數儲存了兩個東西:

  1. 變數的型別
  2. 變數的值,值型別的真實值或者時例項的引用
typedef struct _StackEntry {
    union {
        s64 lvalue;
        f64 dvalue;
        f32 fvalue;
        s32 ivalue;
        __refer rvalue;
        Instance *ins;
    };
    s32 type;
} StackEntry, LocalVarItem;
  • 初始化:
static inline s32 localvar_init(Runtime *runtime, s32 count) {
    if (count > runtime->localvar_max) {
        jvm_free(runtime->localvar);
        runtime->localvar = jvm_calloc(sizeof(LocalVarItem) * count);
        runtime->localvar_max = count;
    } else {
        memset(runtime->localvar, 0, count * sizeof(LocalVarItem));
    }
    runtime->localvar_count = count;
    return 0;
}
  • 將引數值寫入區域性變數

在方法的第一行 Code 執行之前,直譯器需要把傳入的方法引數值寫到區域性變數中
也就是說方法執行初期,區域性變數中只有方法引數的值,而且該值在陣列的頭部。

/**
* 把堆疊中的方法呼叫引數存入方法本地變數
* 呼叫方法前,父程式把函式引數推入堆疊,方法呼叫時,需要把堆疊中的引數存到本地變數
* @param method  method
* @param father  runtime of father
* @param son     runtime of son
*/
static inline void _stack2localvar(MethodInfo *method, LocalVarItem *localvar, RuntimeStack *stack) {

    s32 i_local = method->para_slots;
    //    memcpy(localvar, &stack->store[stack->size - i_local], i_local * sizeof(StackEntry));
    StackEntry *store = stack->store;
    s32 i;
    for (i = 0; i < i_local; i++) {
        localvar[i].lvalue = store[stack->size - (i_local - i)].lvalue;
        localvar[i].type = store[stack->size - (i_local - i)].type;
    }
    stack->size -= i_local;
}

運算元棧

前面說過運算元棧是 JVM 用於代替暫存器的機制,裡面儲存了 JVM 指令的運算元,比如在執行 iadd (int 值二元加法)指令前,需要將兩個待加 int 值先入運算元棧。

  • 結構體

和上文字地變數一樣

RuntimeStack
struct _StackFrame {
    StackEntry *store;
    s32 size;
    s32 max_size;
};
typedef struct _StackEntry {
    union {
        s64 lvalue;
        f64 dvalue;
        f32 fvalue;
        s32 ivalue;
        __refer rvalue;
        Instance *ins;
    };
    s32 type;
} StackEntry, LocalVarItem;

這裡要注意的是,一個執行緒只需要一個運算元棧

 //如果是根方法,所謂根方法,就是執行緒的第一個方法
        runtime->stack = stack_create(STACK_LENGHT);
        runtime->threadInfo = threadinfo_create();
        runtime->threadInfo->top_runtime = runtime;

PC 指標

PC 指標指向當前方法中執行的 Code 行號
主要服務於一些非順序跳轉指令:

  • 條件語句的分支跳轉
  • 迴圈語句的跳轉
  • 異常分支的跳轉
  • debug 行號控制

行號表

行號表記錄了行號和程式碼 PC 指標的對應關係
主要服務於:

  • 異常丟擲程式碼的定位
  • debug 單步除錯的定位
//行號
typedef struct _line_number {
    u16 start_pc;
    u16 line_number;
} LineNumberTable;

指令序列

指令序列在一個方法中是一個順序排列的指令集合
直譯器從指令序列中取址執行。

方法執行流程

準備工作

//準備方法棧
    Runtime *runtime = runtime_create_inl(pruntime);

    runtime->method = method;
    runtime->clazz = clazz;
    while (clazz->status < CLASS_STATUS_CLINITING) {
        class_clinit(clazz, runtime);
    }
    s32 method_sync = method->access_flags & ACC_SYNCHRONIZED;
    //    if (utf8_equals_c(method->name, "getMethod")) {
    //        s32 debug = 1;
    //    }
    //運算元棧
    RuntimeStack *stack = runtime->stack;

    if (!(method->access_flags & ACC_NATIVE)) {
        //拿出 Code
        CodeAttribute *ca = method->converted_code;
        if (ca) {
            //初始化本地變數
            localvar_init(runtime, ca->max_locals);
            LocalVarItem *localvar = runtime->localvar;
            //方法引數進入本地變數
            _stack2localvar(method, localvar, stack);
            s32 stackSize = stack->size;

            //如果方法是同步的,加鎖
            if (method_sync)_synchronized_lock_method(method, runtime);

            u8 *opCode = ca->code;
            runtime->ca = ca;
            JavaThreadInfo *threadInfo = runtime->threadInfo;
            
            //除錯相關
            do {
                runtime->pc = opCode;
                u8 cur_inst = *opCode;
                if (java_debug) {
                    //breakpoint
                    if (method->breakpoint) {
                        jdwp_check_breakpoint(runtime);
                    }
                    //debug step
                    if (threadInfo->jdwp_step.active) {//單步狀態
                        threadInfo->jdwp_step.bytecode_count++;
                        jdwp_check_debug_step(runtime);

                    }
                }
                //process thread suspend
                if (threadInfo->suspend_count) {
                    if (threadInfo->is_interrupt) {
                        ret = RUNTIME_STATUS_INTERRUPT;
                        break;
                    }
                    check_suspend_and_pause(runtime);
                }

取指執行

這個 opCode 就是 pc 指標
這裡用 Switch 分發,因為 Switch 直接使用 CPU 指令 跳轉效率高,因此被稱為 Switch 直譯器。

  /* ==================================opcode start =============================*/
#ifdef __JVM_DEBUG__
                s64 inst_pc = runtime->pc - ca->code;
#endif
                JUMP_TO_IP(cur_inst);
                switch (cur_inst) {
                    label_nop:
                    case op_nop: {
#if _JVM_DEBUG_BYTECODE_DETAIL > 5
                        invoke_deepth(runtime);
                    jvm_printf("nop\n");
#endif
                        opCode += 1;

                        break;
                    }
                    label_aconst_null:
                    case op_aconst_null: {}
                    case op_xxxxx:{}
                    .........

Native 方法

如果待執行的是一個 native 方法
具體會在 JNI 篇詳細描述

//本地方法
        localvar_init(runtime, method->para_slots);//可能有非靜態本地方法呼叫,因此+1
        _stack2localvar(method, runtime->localvar, stack);
        //快取呼叫本地方法
        if (!method->native_func) { //把本地方法找出來快取
            java_native_method *native = find_native_method(utf8_cstr(clazz->name), utf8_cstr(method->name),
                                                            utf8_cstr(method->descriptor));
            if (!native) {
                Instance *exception = exception_create_str(JVM_EXCEPTION_NOSUCHMETHOD, runtime,
                                                           utf8_cstr(method->name));
                push_ref(stack, (__refer) exception);
                ret = RUNTIME_STATUS_EXCEPTION;
            } else {
                method->native_func = native->func_pointer;
            }
        }

        if (method->native_func) {
            if (method_sync)_synchronized_lock_method(method, runtime);
            ret = method->native_func(runtime, clazz);
            if (method_sync)_synchronized_unlock_method(method, runtime);
        }
        //        if (utf8_equals_c(method->name, "nvgTextGlyphPositionsJni")) {
        //            int debug = 1;
        //        }
        localvar_dispose(runtime);

JVM 指令

JVM 每一個指令基本都有幾個類似的指令,比如像iconst、lconst、fconst、dconst 這些主要是針對不同的型別(int、long、float、double),將對應型別的值push到棧頂,其他指令類似。
JVM 指令大約可以分為 9 種:

  • 本地變數操作指令
  • 棧操作指令
  • 常量操作指令
  • 算術和邏輯操作指令
  • 轉換指令
  • 物件,欄位,方法操作指令
  • 陣列操作指令
  • 跳轉指令
  • return

基本指令

x 有 i,l,f,d, a 代表(int、long、float、double、引用)

指令 描述
xconst_n x 型常量值n進棧
bipush 將一個byte型常量值推送至棧頂
xload_n 第n個x型區域性變數進棧
xstore_n 將棧頂x型數值存入第n個區域性變數
xadd 棧頂兩x型數值相加,並且結果進棧
return 當前方法返回void
getstatic 獲取指定類的靜態域,並將其值壓入棧頂
putstatic 為指定的類的靜態域賦值
invokevirtual 呼叫例項方法
invokespecial 呼叫超類構造方法、例項初始化方法、私有方法
invokestatic 呼叫靜態方法
invokeinterface 呼叫介面方法
new 建立一個物件,並且其引用進棧
newarray 建立一個基本型別陣列,並且其引用進棧

本地變數操作指令

該指令負責運算元棧和本地變量表的資料互動工作,主要是

  • 從本地變量表中取出某值壓入運算元棧(只是複製,不會清空本地變量表中的值)
  • 從運算元棧中彈出值到本地變量表中(會清空運算元棧中該值)

這裡舉個常見的例子:
依然是 c = a + b

  1. 首先 a 和 b 的值在本地變量表中
  2. 第一步用 load 指令將 a 和 b 從本地變數中壓入運算元棧
  3. 執行 add 指令,add 指令將運算元棧的棧頂兩個值相加並清空這兩個運算元,產生的結果壓入運算元棧頂
  4. 最後用 store 指令將運算結果存到本地變量表的 c 中

和上面一樣,為了區分運算元型別,指令也根據不同型別開頭
以 load 為例:
xload_n(n = 0~3)
x 有 i,l,f,d, a 代表(int、long、float、double、引用)
n 代表區域性變量表中第 n 槽的值,這裡取 0-3 ,這樣就可以節省很多運算元所佔用的位元組碼空間。
當 n 超過 3 時,則使用 xload n 這種指令 + 一元運算元的方式。

  • VM 程式碼
static inline u8 *_op_ifload_n(u8 *opCode, RuntimeStack *stack, LocalVarItem *localvar, Runtime *runtime, s32 i) {
    Int2Float i2f;
    //從本地變數中 get 到
    i2f.i = localvar_getInt(localvar, i);
#if _JVM_DEBUG_BYTECODE_DETAIL > 5
    invoke_deepth(runtime);
    jvm_printf("if_load_%d: push localvar(%d)= [%x]/%d/%f  \n", i, i, i2f.i, i2f.i, i2f.f);
#endif
	//push 到運算元棧
    push_int(stack, i2f.i);
    opCode += 1;
    return opCode;
}

棧操作指令

該指令主要是對運算元棧內的一些操作

  • 彈出某些值
  • 複製棧中值到棧內
  • 棧內某些值的交換

這裡以複製指令 dup 為例,引用 new 物件的一個經典案例:

  • Java Code
A a = new A();
  • Byte Code
// operand stack:
                               // ...
new A                       // ..., ref
dup                            // ..., ref, ref
invokespecial A.<init>()V   // ..., ref
astore_0

這裡 dup 的必要性就體現出來了
當 new 完 A 後,new 指令將例項引用壓入棧頂
緊接著就會呼叫 A 的無參建構函式,而 invokespecial 會清空棧頂的引用,這樣的話接下來將 A 例項存到本地變數 a 的操作將無法完成,所以在呼叫 invokespecial 之前需要將例項引用複製一份

  • VM 程式碼
case op_dup: {
                        StackEntry entry;
                        //取得運算元棧棧頂的值
                        peek_entry(stack, &entry, stack->size - 1);
						//將該值再壓入運算元棧
                        push_entry(stack, &entry);

#if _JVM_DEBUG_BYTECODE_DETAIL > 5
                        invoke_deepth(runtime);
                    jvm_printf("dup\n");
#endif
                        opCode += 1;

                        break;
                    }

常量操作指令

該指令和簡單,就是將我們程式中定義的各種常量入運算元棧已準備接下來的運算而已,和前面一樣也需要區分常量的型別以及值的範圍
以 int 為例:
當int取值-1~5採用 iconst 指令,取值-128~127採用bipush指令,取值-32768!32767採用sipush指令,取值-2147483648~2147483647採用 ldc 指令。

  • VM 程式碼
case op_bipush: {
                        //此行 code 的第二個元素就是常量運算元
                        s32 value = (s8) opCode[1];
                        //常量入棧
#if _JVM_DEBUG_BYTECODE_DETAIL > 5
                        invoke_deepth(runtime);
                    jvm_printf("bipush a byte %d onto the stack \n", value);
#endif
                        opCode += 2;

                        break;
                    }

算術和邏輯操作指令

該指令用於運算子運算和邏輯操作

  • 加減乘除
  • 與或操作
  • 移位操作
  • 大小相等比較等

與前面類似,不同資料型別也有不同的指令
以加法 IADD 為例:

彈出運算元棧頂兩個運算元,相加後壓入運算元棧頂

case op_iadd: {
                        s32 value1 = pop_int(stack);
                        s32 value2 = pop_int(stack);
                        s32 result = value1 + value2;
#if _JVM_DEBUG_BYTECODE_DETAIL > 5
                        invoke_deepth(runtime);
                    jvm_printf("iadd: %d + %d = %d\n", value1, value2, result);
#endif
                        push_int(stack, result);
                        opCode 
            
           

相關推薦

JVM 原始碼分析(4.直譯器方法執行)

簡介 miniJVM 作為一個 mini 的 Java VM,實現了 Switch 直譯器,並不支援主流 JVM 的 JIT 或者更為複雜的 AOT。但這樣對於我們瞭解位元組碼的執行已經足夠了。 位元組碼指令 基於堆疊 位元組碼指令類似於彙編指令,但是不同的是:

JVM 原始碼分析(3.記憶體管理GC)

簡介 miniJVM 的記憶體管理的實現較為簡單 記憶體分配使用了開源的 ltalloc 庫 GC就是經典的 Mark-Sweep GC 物件分配 物件分配要關注的就兩個過程 New 一個 Java 物件的過程 記憶體塊在堆上分配的過程 物件在 JVM

JVM 原始碼分析(5.異常處理)

異常列印 Java 如果發生異常,通常會呼叫 Throwable.printStackTrace 去列印堆疊資訊。 堆疊資訊包括完整類名,方法名,java 檔名,行號 而這樣的資訊根據發生 Crash 執行緒所經歷的n個方法會打印出n行。 整個過程被稱為棧回朔

JVM Metaspace溢出排查

jvm參數 oom task visualvm map 排除 創建 thread 類裝載 多圖預警! 環境:系統測試(Windows Server/JRE8/tomcat7) 現象:應用運行幾天後,出現訪問超時,服務器cpu利用率居高不下 問題日誌:OutOfMemory

網上流量分析

words itl word 很多 release sctf 數據包 base 沒有 這是捕獲的黑客攻擊數據包,LateRain用戶的密碼在此次攻擊中泄露了,你能找到嗎? FLAG格式:SCTF{LateRain的明文密碼} LINK: http://pan.baidu.c

源碼分析

環境 space () esp clu pac 返回對象 對象 單步調試 首先分析一段很短的代碼 #include<iostream> #include<vector> using namespace std; vector<int>

dubbo的請求原始碼分析

呼叫某個服務首先會進入到動態代理。 InvokerInvocationHandler#invoke(Object proxy, Method method, Object[] args) public Object invoke(Object proxy, Method method,

解Bug之路——JVM堆外記憶體洩露Bug的查詢

前言JVM的堆外記憶體洩露的定位一直是個比較棘手的問題。此次的Bug查詢從堆內記憶體的洩露反推出堆外記憶體,同時對實體記憶體的使用做了定量的分析,從而實錘了Bug的源頭。筆者將此Bug分析的過程寫成部落格,以饗讀者。由於實體記憶體定量分析部分用到了linux kernel虛擬

linux程序和執行緒排查 · JVM CPU高負載的排查辦法

前言通過本文,你將學會:1、linux上程序及程序中執行緒排查的基本方法,如檢視程序中的執行緒數此文中的執行緒一般指輕量級程序。檢視所有程序資訊 top -H 加上-H這個選項啟動top,top一行顯示一個執行緒(指的是(輕量級)程序? )。否則,它一行顯示一個程序。先輸入

spring5原始碼完整編譯過程

學習java已有3年之久,spring一直停留在應用階段,兩次面試阿里的經歷讓我深感學習spring原始碼的重要性,廢話不多說,開搞! 1、環境: jdk1.8+spring5+gradle4.7+eclipse4.6 如果要參考該教程,環境最好一樣(eclipse除外

ThinkPHP原始碼審計

一、寫在前面 週末閒得蛋疼,審了一套朋友給的系統,過程挺有意思的,開始的時候覺得基於TP3.0二次開發的系統應該是蠻簡單的,畢竟TP爆了很多漏洞。後來發現開發做了不少安全措施。因此記錄一下這次審計,當時自己學習的記錄吧。 二、後臺注入 最開始的時候,因為快吃飯了,就用RIPS掃了掃(事實證明,沒有什麼毛用)

JVM調優(Permanent Generation)

最近在一次搭建公司系統執行環境的時候tomcat總是報出 java.lang.OutOfMemoryError PermGen space 的問題,即記憶體溢位 在解決這個問題的時候 一.問題解決 此處用到一個命令:jmap 顯示java堆中的詳

jvm crash

昨天下午4:30,系統發生了有史以來最刺激的崩潰,一開始是2臺掛了(線上共5臺),這個基本上很少見,一般情況下都是偶爾一臺掛掉。趕緊手動重啟,搞笑的是重啟起來的機器裡馬也掛了,結果就出現了一種尷尬的局面,我們不停的重啟,機器不斷地報警,不斷地掛,整個過程持續了20多分鐘。其實

解Bug之路-JVM堆外記憶體洩露Bug的查詢

# 解Bug之路-記一次JVM堆外記憶體洩露Bug的查詢 ## 前言 JVM的堆外記憶體洩露的定位一直是個比較棘手的問題。此次的Bug查詢從堆內記憶體的洩露反推出堆外記憶體,同時對實體記憶體的使用做了定量的分析,從而實錘了Bug的源頭。筆者將此Bug分析的過程寫成部落格,以饗讀者。 由於實體記憶體定量分

SqlServer大表查詢語句優化和執行計劃分析

資料庫: sqlserver2008r2  表: device_data 資料量:2000w行左右 表結構 CREATE TABLE [dbo].[device_data]( [Id] [int] IDENTITY(1,1) NOT NULL, [DeviceId] [nvarc

nginx504閘道器超時解決方法

問題發生的背景: 合作方請求某個介面,由於處理時間較長,導致了閘道器超時. 問題分析: 1.可能是php程式超時報錯; 2.php-fpm處理請求超時; 3.nginx伺服器超時. 問題解決: 1.php超時設定: 檢查了php超時的配置,在php.ini

資料庫事務問題的定位解決

技術背景:SpringBoot用SpringMVC + + +的MyBatis的ActiveMQ 問題描述:在服務層中根據主鍵對某表中資料記錄的部分欄位進行更新,更新之後使用ActiveMQ的對剛才更新資料的中另外一個欄位進行更新,訊息佇列的更新是後加的,加上之後導致先前的更新失效; &nb

【報錯記錄】Springboot 打包jar後放在伺服器上執行失敗的排錯

使用mvn package -DSkipTests打包成jar包,然後上傳到伺服器。執行java -jar XXX.jar --env=pro後丟擲: [localhost-startStop-1] ERROR o.s.boot.web.embedded.tomcat.TomcatStart

JUnit4.8.2原始碼分析-4 RunNotifierRunListener

JUnit4執行過程中,org.junit.runner.notification. RunListener和RunNotifier運用了觀察者模式。 1.觀察者 觀察者Observer/Listener主要作用是分析各種事件並定義相應的回撥介面。例如JDK中MouseLi

【運維】上線前的緊急定位修復-獻上九條小經驗

1 簡介 本文介紹了作者所在團隊在某次上線前測試發現問題、定位問題並修復上線的過程,最後給出幾點經驗總結,希望對大家有用。 2 過程 (1)今天需要上線,但昨晚才合併了所有分支,時間很緊迫。不幸的是,打包測試後發現有一個Springboot應用(模組R)啟動失敗,但程序沒有死,一直在輸出報錯日誌。 (2)Go