代碼反混淆(deobfuscation)和代碼混淆(obfuscation)對應(yīng),是其逆過程。維基百科將代碼混淆定義為故意生成人類難以理解的源代碼或機器碼的過程("In software development, obfuscation is the deliberate act of creating source or machine code that is difficult for humans to understand.")。代碼反混淆可以理解為將原本人類難以理解的代碼轉(zhuǎn)化為簡單的、可理解的、直觀的代碼的過程。
創(chuàng)新互聯(lián)專注于鳳臺網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗。 熱誠為您提供鳳臺營銷型網(wǎng)站建設(shè),鳳臺網(wǎng)站制作、鳳臺網(wǎng)頁設(shè)計、鳳臺網(wǎng)站官網(wǎng)定制、微信小程序開發(fā)服務(wù),打造鳳臺網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供鳳臺網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。
這篇文章主要介紹一下
"Big Code" 在代碼反混淆領(lǐng)域的應(yīng)用。更具體一點就是介紹一下提出 "JSNice" 和 "Deguard"
的兩篇文章,這兩篇文章雖然已經(jīng)發(fā)表快五年了,但至今沒有文章Follow這兩份工作,因為文章已經(jīng)將使用 "Big Code"
做代碼命名反混淆做到了極致。后來的人無法在這個問題上推陳出新,脫穎而出。
"Big Code": 代碼托管網(wǎng)站如GitHub上的大量免費可用的高質(zhì)量代碼被稱為 "Big Code" ,這些數(shù)據(jù)結(jié)合統(tǒng)計推理或深度學(xué)習(xí)為新興的開發(fā)工具的出現(xiàn)提供了契機。
概率圖模型:概率圖模型是用圖來表示變量概率依賴關(guān)系的理論,結(jié)合概率論與圖論的知識,利用圖來表示與模型有關(guān)的變量的聯(lián)合概率分布。
問題
為了項目的安全,開發(fā)者在打包發(fā)布項目時會對代碼進行混淆加密,包括但不限于用無意義的短變量去重命名類、變量、方法,以免代碼被輕易破解泄露。另外由于JS腳本主要用于Web開發(fā),對其進行混淆還能壓縮腳本的大小,使得瀏覽器下載、加載更加快速,提升用戶的瀏覽體驗。
這一類通過對類、變量、方法重命名的混淆方案確實能加大其他開發(fā)者對代碼的理解難度。其他開發(fā)者不干了,為了能方便理解他人混淆后的代碼,學(xué)習(xí)(抄襲)他人的經(jīng)驗,針對這一類混淆方法的反混淆方法也應(yīng)運而生。
下面先展示一下安卓APP的代碼混淆技術(shù):
圖1. Java程序的代碼混淆過程
經(jīng)過混淆的代碼在功能上是沒有變化的,但是去掉了部分名稱中的語義信息。因為種種限制,這類混淆也不可能對所有的名稱都進行替換。上圖中的SQLiteHelper、SQLiteDatabase和Cursor就是一個證明,這些名稱都是安卓API,如果將這些類名混淆會影響代碼的功能。
理論上一個有經(jīng)驗的安卓開發(fā)者可以在這些有限的提示下為所有的名稱找到富含語義的表示,所以反混淆只需要一個有經(jīng)驗的開發(fā)者(有經(jīng)驗的開發(fā)者:???我做錯了什么)。退一步,找不到有經(jīng)驗的開發(fā)者怎么辦,沒關(guān)系,GitHub有高質(zhì)量的各種項目,現(xiàn)訓(xùn)練一個有經(jīng)驗的開發(fā)者也行。不過為了人道主義,消滅996剝削,程序員表示可以用程序代替人,正好可以用GitHub數(shù)據(jù)訓(xùn)練一個程序做這個反混淆嘛!理論存在,實踐開始。
JSNice[1]
用程序代替人其實并不簡單,針對上圖中的反混淆問題,程序需要具有“聯(lián)想”和“推理”能力,比如從a
extends
SQLiteHelper這一句中,a應(yīng)該很可能也是Helper類,結(jié)合類中有SQLiteDatabase實例推理出比較符合a的語義的類名是DBHelper。
針對以上兩點,程序需要先有關(guān)系的概念,能從一個詞“聯(lián)想”到另一個詞,然后還要有推理的能力,能通過約束從幾個候選詞中找到最符合的那個詞。怎么做這個事呢?雖然有很多的未混淆的JS腳本供程序?qū)W習(xí),但在反混淆JS腳本時,程序無法理解復(fù)雜的JS腳本,所以需要將JS腳本表示成一種可以利用已知屬性推理未知屬性的結(jié)構(gòu),JSNice采用了概率圖模型。
概率圖模型相比其他學(xué)習(xí)算法的優(yōu)勢在于可以利用圖結(jié)構(gòu)將已知信息帶入知識網(wǎng)絡(luò),在使用概率圖模型之前,往往要求圖結(jié)構(gòu)是已知的?,F(xiàn)實中我們沒有這些先驗知識,但是有大量的樣本(未混淆代碼)。通過樣本學(xué)習(xí)出未混淆JS腳本的概率圖就是JSNice的核心。
具體到JSNice,這個工具想要做的是為JS腳本推測名稱(name)和類型(type)。先通過一張圖看看JSNice的推理過程。
圖2. 一段推理出名稱和類型的JavaScript程序及推理名稱的推理過程
由于JSNice推理名稱和推理類型的過程類似,本文就只闡述推理名稱的過程。
1. 確定已知和未知屬性
JS腳本中包含了各類代碼元素(elements)比如變量,常量,類,方法名,域等。對推理名稱問題,元素的屬性即帶有語義的名稱,一個需要推理名稱的元素,稱其屬性未知。不需要推理名稱的元素其屬性已知。首先需要確定JS腳本中的元素屬性是否已知。很容易看出圖2(a)代碼中屬性已知的元素包括:常量0和[]、feild名稱length和方法名稱push,其他的局部變量如e,t,n,r和i的屬性都是未知的。
將問題泛化,如何判斷任意的JS腳本中的任意元素的屬性是否已知是需要解決的第一個問題。JSNice采用程序分析和人工指定的方式確定元素屬性是否已知,簡單來說,JSNice認(rèn)為JS腳本中的常量(constants)、對象屬性名(objects
properties)、方法名(methods)和全局變量名(global
variables)都是屬性已知的元素,而所有的局部變量的屬性都是未知的。值得注意的是JSNice將對象屬性名稱和API名稱直接看做是常量。這個劃分是否合理暫不去討論,但是其確實適用與大部分的JS腳本。有興趣的讀者可以自行研究。
2. 建立依賴網(wǎng)絡(luò)
第一步獲取了JS腳本中的所有元素(屬性已知或未知),接下來需要建立元素之間的關(guān)系,好方便后續(xù)的推理;圖2(c)中簡單的給出了一些關(guān)系,比如"var r = e.length"中可以得到(r, length, L=_.R)的關(guān)系。
JSNice實際考慮的元素關(guān)系十分復(fù)雜,主要有三類,這里簡單的進行描述:
句法關(guān)系,這一類關(guān)系主要通過AST得到,例子如下:
圖3. (a) 語句i + j amp;amp;amp;amp;amp;lt; k的AST. (b) 根據(jù)AST建立用于名稱預(yù)測的依賴網(wǎng)絡(luò). (c) 用于類型預(yù)測的網(wǎng)絡(luò).
這類關(guān)系的形式化定義如下
別名關(guān)系,這類關(guān)系通過standard alias analysis得到,將方法調(diào)用時傳入的arguments和方法聲明時的parameters進行關(guān)聯(lián),形成ARG_TO_PM關(guān)系。
函數(shù)名稱關(guān)系,由于JS的語言特性,很多的局部變量本身是方法定義,MAY_CALL和MAY_ACCESS表示一個方法的可能調(diào)用方法關(guān)系和可能訪問對象域關(guān)系,這類關(guān)系可以通過program analysis得到。
類型推理采用的關(guān)系有所不同,但這里也不再詳述,有興趣的讀者直接移步原文。
3. 訓(xùn)練和推理
現(xiàn)在整理一下對某個JS腳本x進行上述兩步分析能得到什么?得到了一個依賴網(wǎng)絡(luò) ?,其中 ?為節(jié)點集,分別表示未知(unknow)屬性元素和已知屬性元素。 ?為邊集,表示兩個程序元素之間的關(guān)系以及關(guān)系類型。
現(xiàn)在不去考慮訓(xùn)練的過程,直接看一下推理過程,JSNice采用貪婪算法,對未知屬性元素遍歷其所有的屬性可能,尋找到使score最大的屬性作為結(jié)果。
圖4. 建立網(wǎng)絡(luò)尋找未知屬性元素y的最佳屬性的模式
具體的算法如下:
JSNice在具體實現(xiàn)時對算法有所優(yōu)化,但是其基本思想和主要框架都沒變。其實對比前文提到人進行反混淆時需要的“聯(lián)想”和“推理”兩種能力,candidates函數(shù)擔(dān)負(fù)了“聯(lián)想”的重任,利用scoreEdges函數(shù)對不同的候選屬性計算score并選擇最大score對應(yīng)屬性的過程就是”推理“。
將圖2的推理部分摘出來看:
r的candidates有l(wèi)en和length,t的candidates有step、j和q,i的candidates只有i。推理得到的(r、i、t)的屬性是(len、i、step)而不是(length、i、step);是因為前者的綜合score是0.4+0.8+0.5=1.7,而后者的綜合score只有0.5+0.6+0.5=1.6。
那么怎么得到scoreEdges和candidates函數(shù)呢?
JSNice定義了一個條件隨機場:
x為給定某個JS腳本,y為未知屬性元素(復(fù)數(shù))的任意分配屬性,score為指示屬性y和腳本x的匹配程度的函數(shù),其返回值為實數(shù),值越大越匹配。
Z是對應(yīng)JS腳本x的一個懲罰系數(shù),用來保證其Pr和為1
將score定義為k個特征函數(shù)的加權(quán)平均,得到
最終條件隨機場的表示形式為:
寫到這里,出現(xiàn)了第一個問題,score為k個特征函數(shù)的加權(quán)平均,如何確定k呢?
JSNice是在訓(xùn)練階段的預(yù)處理步驟得到k的,實際上不僅僅這一步不僅獲得了k,還直接定義了k個 pairwise feature functions ?。
往前回一步,本文前面一直說GitHub有未混淆的代碼可供概率圖模型學(xué)習(xí),這里定義訓(xùn)練集 ?由t個未混淆的JS腳本組成。對 ?中的任意 ?元組,JSNice定義其特征的集合為
整個訓(xùn)練集的所有特征集為
JSNice直接定義pairwise feature functions為每個特征三元組
的指示函數(shù):
所以訓(xùn)練集有多少特征三元組,k的值就有多大。
但說了這么多,還是沒有提到scoreEdges和candidates。別急,直接定義
如此把前面的公式都串起來了,整個公式組其實只有 ?是未知,條件隨機場的訓(xùn)練過程其實就是計算 ?的過程。
請點擊輸入圖片描述
至于candidates,假設(shè)現(xiàn)在概率圖模型中的 ?已經(jīng)訓(xùn)練完成,根據(jù)前面的定義, ?和 ?其實是一一對應(yīng)的, ?本身是特征三元組的指示函數(shù),也和三元組一一對應(yīng),所以可以使用權(quán)重 ?直接限制節(jié)點 ?的可能取值。定義 ?函數(shù)對輸入的特征三元組集合基于此權(quán)重返回top s的三元組。定義 ?。定義如下輔助函數(shù):
最后:
candidates函數(shù)其實就是先在 ?中找到和 ?有 ?關(guān)系的 ?,然后利用 ?和 ?在訓(xùn)練集中找到和 ?最相似的詞作為候選。比較方便的是輔助函數(shù)其實可以在預(yù)測之前提前計算并緩存下來。
由于筆者本身沒有不研究概率圖模型,所以訓(xùn)練模型得到 ?的內(nèi)容就省略了,有興趣的讀者可以閱讀原文[1],本文只簡單的描述:
JSNice采用判別式訓(xùn)練,由于最大似然需要計算 ?,JSNice采用max-margin training,使用Structured Support Vector Machine (SSVM)并用scalable subgradient descent algorithm優(yōu)化。
請點擊輸入圖片描述
Deguard[2]
相對JSNice做的對JS腳本進行反混淆,Deguard對安卓APK做反混淆的難度要大了很多,放在眼前的一個問題就是項目規(guī)模,JSNice的應(yīng)用scope其實是Web上的JS腳本,考慮網(wǎng)站的加載等限制,單個JS腳本必不會太大,而安卓APK不同,由于安卓本身事件驅(qū)動的編程方式,一個簡單的安卓APK的復(fù)雜度可能就能比得上十分復(fù)雜的JS腳本。并且安卓APK的大小一般也沒有限制。
還有一些安卓或者說Java需要的約束是建模時需要考慮的,比如一個Java類中的feilds名稱必須不一樣,一個package中的classes名稱必須不一樣。不滿足這些約束,對APK進行反混淆的結(jié)果就失去了其實用性。
考慮到安卓application的復(fù)雜性,選取合適的粒度建模是首要的問題,關(guān)系過于復(fù)雜不利于概率圖模型的學(xué)習(xí),關(guān)系過于簡略可能導(dǎo)致無法預(yù)測準(zhǔn)確的屬性,必須有一個權(quán)衡。
1. 確定程序元素(圖的節(jié)點 ?)
要為安卓APK定義一個依賴圖,首先確定圖上的節(jié)點,Deguard考慮了APK中的如下元素:
Types,不管是基本類型(int, long, float),引用類型(Object, ArrayList),還是數(shù)組類型(int[], Object[])都能做為節(jié)點
Feilds,APK中的任意類中定義的任意Feild都能做為節(jié)點
Packages,APK中的任意package能做為節(jié)點,像package a.b就可以做為兩個節(jié)點,a和a.b。
Methods,APK中的任意方法都能做為節(jié)點,但是如果有overrides關(guān)系,那么子類和父類的method公用一個節(jié)點表示。
Constant values and null
Access Modifiers,比如static,synchronized
Operations
這里沒有考慮Java語言中的泛型機制是因為在編譯過程中會消除泛型,APK本身就是編譯過后的文件。另外和JSNice不一樣的是,Deguard沒有考慮局部變量名和參數(shù)名,但考慮了局部變量的類型和參數(shù)的類型,一方面減少規(guī)模,另一方面就是變量名和參數(shù)名本身不在APK中。
2. 確定元素屬性是否已知
這里將元素屬性定義為節(jié)點是否被混淆,屬性已知說明節(jié)點名稱未被混淆,不需要預(yù)測名稱,屬性未知說明節(jié)點名稱已被混淆,需要預(yù)測名稱。
已知屬性元素包括
節(jié)點如果代表pakages,classes,methods和feilds且在安卓API中,那么這些節(jié)點是已知的,因為基于名稱的混淆工具并不會混淆這些名稱
構(gòu)造函數(shù)名稱都是已知的
節(jié)點代表對安卓API方法重寫的方法也是已知的,因為前文已經(jīng)提過,重寫關(guān)系其實是用一個節(jié)點表示。
剩下的元素都是未知的,需要預(yù)測名稱
3. 構(gòu)建依賴網(wǎng)絡(luò)
Deguard用一張圖詳細(xì)描述了其用于構(gòu)建依賴網(wǎng)絡(luò)的關(guān)系
圖5. 安卓application采用的關(guān)聯(lián)程序元素的關(guān)系,第二列定義了邊的類型,如果滿足第三列提出的條件,一個關(guān)系就成立
相對JSNice中大部分的關(guān)系來自AST,Deguard選擇的關(guān)系明顯融合進了人的經(jīng)驗,更加的抽象。實際上本文的精華也是這一張圖,某種程度上這圖中展現(xiàn)出來了人類對具體問題具體分析的思考,而不是僅僅簡單的復(fù)用已有工作提出的各種關(guān)系。
剩下的內(nèi)容比如模型的訓(xùn)練和推理其實和JSNice差不多,這里不會重復(fù)一遍,后續(xù)會講不一樣的地方,也就是Deguard如何處理Java程序帶來的關(guān)于各種名稱的約束。
Java中的名稱約束還是比較復(fù)雜的,這里拿一個例子講一下:
圖6. 展示方法命名顯示的例子
A.a(A)沒有任何約束,因為A作為參數(shù)的方法只有它一個
A.b(Object)不能被重命名為equals,因為這樣會override java.lang#
B.g()和B.h()方法重命名名稱必須不一樣,因為這兩方法的特征一樣
B.g()和A.c()方法重命名名稱必須不一樣,如果一樣就是隱式的繼承
B.h()和A.c()方法重命名名稱必須不一樣,也是由于method overriding的原因
C.x()和B.h()卻沒有沖突,因為B.h()是private的
相等約束(繼承重寫機制)可通過共用節(jié)點表示,不等約束也需要明確表示,所以Deguard提出了一個檢測方法名稱不等約束的算法
其他元素,比如類名,F(xiàn)eilds名稱的不等約束比較簡單,直接處理就行。
所有不等約束以集合 ?表示, ?, ?中任意兩個節(jié)點的名稱必須不一樣。
注意這個約束只用與預(yù)測階段,因為訓(xùn)練數(shù)據(jù)(未混淆)本身滿足這些約束。很容易可以把這些約束結(jié)合到JSNice的算法1中。
Deguard的概率圖優(yōu)化算法和JSNice也不一樣,采用的是pseudo likelihood estimation。具體闡述推薦閱讀文章[3]。
值得注意的是,為什么JSNice就沒有Deguard中提到的相等約束和不等約束,筆者個人認(rèn)為還是由問題和語言特性共同決定,JSNice的名稱預(yù)測其實只預(yù)測了局部變量,而JS的語言特性導(dǎo)致其本身不需要檢測局部變量的名稱沖突,只有執(zhí)行結(jié)果報錯才會說明程序出錯。也就是說其實JS本身語言特性就沒有這類約束,自然不需要建模。
1. 盡量在合適的場合使用單例
使用單例可以減輕加載的負(fù)擔(dān),縮短加載的時間,提高加載的效率,但并不是所有地方都適用于單例,簡單來說,單例主要適用于以下三個方面:
第一,控制資源的使用,通過線程同步來控制資源的并發(fā)訪問;
第二,控制實例的產(chǎn)生,以達到節(jié)約資源的目的;
第三,控制數(shù)據(jù)共享,在不建立直接關(guān)聯(lián)的條件下,讓多個不相關(guān)的進程或線程之間實現(xiàn)通信。
2. 盡量避免隨意使用靜態(tài)變量
要知道,當(dāng)某個對象被定義為stataic變量所引用,那么gc通常是不會回收這個對象所占有的內(nèi)存
3. 盡量避免過多過常的創(chuàng)建Java對象
盡量避免在經(jīng)常調(diào)用的方法,循環(huán)中new對象,由于系統(tǒng)不僅要花費時間來創(chuàng)建對象,而且還要花時間對這些對象進行垃圾回收和處理,在我們可以控制的范圍內(nèi),最大限度的重用對象,最好能用基本的數(shù)據(jù)類型或數(shù)組來替代對象。
4. 盡量使用final修飾符
帶有final修飾符的類是不可派生的。在Java核心API中,有許多應(yīng)用final的例子,例如java.lang.String.為String類指定final防止了使用者覆蓋length()方法。另外,如果一個類是final的,則該類所有方法都是final的。Java編譯器會尋找機會內(nèi)聯(lián)(inline)所有的final方法(這和具體的編譯器實現(xiàn)有關(guān))。此舉能夠使性能平均提高50%.
5. 盡量使用局部變量
調(diào)用方法時傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時變量都保存在棧(Stack)中,速度較快。其他變量,如靜態(tài)變量、實例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。
6. 盡量處理好包裝類型和基本類型兩者的使用場所
雖然包裝類型和基本類型在使用過程中是可以相互轉(zhuǎn)換,但它們兩者所產(chǎn)生的內(nèi)存區(qū)域是完全不同的,基本類型數(shù)據(jù)產(chǎn)生和處理都在棧中處理,包裝類型是對象,是在堆中產(chǎn)生實例。
在集合類對象,有對象方面需要的處理適用包裝類型,其他的處理提倡使用基本類型。
7. 慎用synchronized,盡量減小synchronize的方法
都知道,實現(xiàn)同步是要很大的系統(tǒng)開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。synchronize方法被調(diào)用時,直接會把當(dāng)前對象鎖 了,在方法執(zhí)行完之前其他線程無法調(diào)用當(dāng)前對象的其他方法。所以synchronize的方法盡量小,并且應(yīng)盡量使用方法同步代替代碼塊同步。
8. 盡量使用StringBuilder和StringBuffer進行字符串連接
這個就不多講了。
9. 盡量不要使用finalize方法
實際上,將資源清理放在finalize方法中完成是非常不好的選擇,由于GC的工作量很大,尤其是回收Young代內(nèi)存時,大都會引起應(yīng)用程序暫停,所以再選擇使用finalize方法進行資源清理,會導(dǎo)致GC負(fù)擔(dān)更大,程序運行效率更差。
10. 盡量使用基本數(shù)據(jù)類型代替對象
String str = "hello";
上面這種方式會創(chuàng)建一個"hello"字符串,而且JVM的字符緩存池還會緩存這個字符串;
String str = new String("hello");
此時程序除創(chuàng)建字符串外,str所引用的String對象底層還包含一個char[]數(shù)組,這個char[]數(shù)組依次存放了h,e,l,l,o
11. 單線程應(yīng)盡量使用HashMap、ArrayList
HashTable、Vector等使用了同步機制,降低了性能。
12. 盡量合理的創(chuàng)建HashMap
當(dāng)你要創(chuàng)建一個比較大的hashMap時,充分利用另一個構(gòu)造函數(shù)
public HashMap(int initialCapacity, float loadFactor)
避免HashMap多次進行了hash重構(gòu),擴容是一件很耗費性能的事,在默認(rèn)中initialCapacity只有16,而loadFactor是 0.75,需要多大的容量,你最好能準(zhǔn)確的估計你所需要的最佳大小,同樣的Hashtable,Vectors也是一樣的道理。
13. 盡量減少對變量的重復(fù)計算
并且在循環(huán)中應(yīng)該避免使用復(fù)雜的表達式,在循環(huán)中,循環(huán)條件會被反復(fù)計算,如果不使用復(fù)雜表達式,而使循環(huán)條件值不變的話,程序?qū)\行的更快。
14. 盡量避免不必要的創(chuàng)建
15. 盡量在finally塊中釋放資源
程序中使用到的資源應(yīng)當(dāng)被釋放,以避免資源泄漏。這最好在finally塊中去做。不管程序執(zhí)行的結(jié)果如何,finally塊總是會執(zhí)行的,以確保資源的正確關(guān)閉。
16. 盡量使用移位來代替'a/b'的操作
"/"是一個代價很高的操作,使用移位的操作將會更快和更有效
17.盡量使用移位來代替'a*b'的操作
同樣的,對于'*'操作,使用移位的操作將會更快和更有效
18. 盡量確定StringBuffer的容量
StringBuffer 的構(gòu)造器會創(chuàng)建一個默認(rèn)大小(通常是16)的字符數(shù)組。在使用中,如果超出這個大小,就會重新分配內(nèi)存,創(chuàng)建一個更大的數(shù)組,并將原先的數(shù)組復(fù)制過來,再 丟棄舊的數(shù)組。在大多數(shù)情況下,你可以在創(chuàng)建 StringBuffer的時候指定大小,這樣就避免了在容量不夠的時候自動增長,以提高性能。
19. 盡量早釋放無用對象的引用
大部分時,方法局部引用變量所引用的對象 會隨著方法結(jié)束而變成垃圾,因此,大部分時候程序無需將局部,引用變量顯式設(shè)為null.
20. 盡量避免使用二維數(shù)組
二維數(shù)據(jù)占用的內(nèi)存空間比一維數(shù)組多得多,大概10倍以上。
21. 盡量避免使用split
除非是必須的,否則應(yīng)該避免使用split,split由于支持正則表達式,所以效率比較低,如果是頻繁的幾十,幾百萬的調(diào)用將會耗費大量資源,如果確實需 要頻繁的調(diào)用split,可以考慮使用apache的StringUtils.split(string,char),頻繁split的可以緩存結(jié)果。
22. ArrayList LinkedList
一 個是線性表,一個是鏈表,一句話,隨機查詢盡量使用ArrayList,ArrayList優(yōu)于LinkedList,LinkedList還要移動指 針,添加刪除的操作LinkedList優(yōu)于ArrayList,ArrayList還要移動數(shù)據(jù),不過這是理論性分析,事實未必如此,重要的是理解好2 者得數(shù)據(jù)結(jié)構(gòu),對癥下藥。
23. 盡量使用System.arraycopy ()代替通過來循環(huán)復(fù)制數(shù)組
System.arraycopy() 要比通過循環(huán)來復(fù)制數(shù)組快的多
24. 盡量緩存經(jīng)常使用的對象
盡可能將經(jīng)常使用的對象進行緩存,可以使用數(shù)組,或HashMap的容器來進行緩存,但這種方式可能導(dǎo)致系統(tǒng)占用過多的緩存,性能下降,推薦可以使用一些第三方的開源工具,如EhCache,Oscache進行緩存,他們基本都實現(xiàn)了FIFO/FLU等緩存算法。
25. 盡量避免非常大的內(nèi)存分配
有時候問題不是由當(dāng)時的堆狀態(tài)造成的,而是因為分配失敗造成的。分配的內(nèi)存塊都必須是連續(xù)的,而隨著堆越來越滿,找到較大的連續(xù)塊越來越困難。
26. 慎用異常
當(dāng)創(chuàng)建一個異常時,需要收集一個棧跟蹤(stack track),這個棧跟蹤用于描述異常是在何處創(chuàng)建的。構(gòu)建這些棧跟蹤時需要為運行時棧做一份快照,正是這一部分開銷很大。當(dāng)需要創(chuàng)建一個 Exception 時,JVM 不得不說:先別動,我想就您現(xiàn)在的樣子存一份快照,所以暫時停止入棧和出棧操作。棧跟蹤不只包含運行時棧中的一兩個元素,而是包含這個棧中的每一個元素。
如 果您創(chuàng)建一個 Exception ,就得付出代價。好在捕獲異常開銷不大,因此可以使用 try-catch 將核心內(nèi)容包起來。從技術(shù)上講,您甚至可以隨意地拋出異常,而不用花費很大的代價。招致性能損失的并不是 throw 操作--盡管在沒有預(yù)先創(chuàng)建異常的情況下就拋出異常是有點不尋常。真正要花代價的是創(chuàng)建異常。幸運的是,好的編程習(xí)慣已教會我們,不應(yīng)該不管三七二十一就 拋出異常。異常是為異常的情況而設(shè)計的,使用時也應(yīng)該牢記這一原則。
(1)。 用Boolean.valueOf(boolean b)代替new Boolean()
包裝類的內(nèi)存占用是很恐怖的,它是基本類型內(nèi)存占用的N倍(N2),同時new一個對象也是性能的消耗。
(2)。 用Integer.valueOf(int i)代替new Integer()
和Boolean類似,java開發(fā)中使用Integer封裝int的場合也非常多,并且通常用int表示的數(shù)值都非常小。SUN SDK中對Integer的實例化進行了優(yōu)化,Integer類緩存了-128到127這256個狀態(tài)的Integer,如果使用 Integer.valueOf(int i),傳入的int范圍正好在此內(nèi),就返回靜態(tài)實例。這樣如果我們使用Integer.valueOf代替new Integer的話也將大大降低內(nèi)存的占用。
(3)。 用StringBuffer的append方法代替"+"進行字符串相加。
這個已經(jīng)被N多人說過N次了,這個就不多說了。
(4)。 避免過深的類層次結(jié)構(gòu)和過深的方法調(diào)用。
因為這兩者都是非常占用內(nèi)存的(特別是方法調(diào)用更是堆棧空間的消耗大戶)。
(5)。 變量只有在用到它的時候才定義和實例化。
這是初學(xué)者最容易犯的錯,合理的使用變量,并且只有在用到它的時候才定義和實例化,能有效的避免內(nèi)存空間和執(zhí)行性能上的浪費,從而提高了代碼的效率。
(6)。 避免在循環(huán)體中聲明創(chuàng)建對象,即使該對象占用內(nèi)存空間不大。
這種情況在我們的實際應(yīng)用中經(jīng)常遇到,而且我們很容易犯類似的錯誤
采用上面的第二種編寫方式,僅在內(nèi)存中保存一份對該對象的引用,而不像上面的第一種編寫方式中代碼會在內(nèi)存中產(chǎn)生大量的對象引用,浪費大量的內(nèi)存空間,而且增大了垃圾回收的負(fù)荷。因此在循環(huán)體中聲明創(chuàng)建對象的編寫方式應(yīng)該盡量避免。
(7)。 如果if判斷中多個條件用'||'或者''連接,請將出現(xiàn)頻率最高的條件放在表達式最前面。
這個小技巧往往能有效的提高程序的性能,尤其是當(dāng)if判斷放在循環(huán)體里面時,效果更明顯。
1.JVM管理兩種類型的內(nèi)存:堆內(nèi)存(heap),棧內(nèi)存(stack),堆內(nèi)在主要用來存儲程序在運行時創(chuàng)建或?qū)嵗膶ο笈c變量。而棧內(nèi)存則是用來存儲程序代碼中聲明為靜態(tài)(static)(或非靜態(tài))的方法。
2.JVM中對象的生命周期,創(chuàng)建階段,應(yīng)用階段,不可視階段,不可到達階段,可收集階段,終結(jié)階段,釋放階段
3.避免在循環(huán)體中創(chuàng)建對象,即使該對象點用內(nèi)存空間不大。
4.軟引用的主要特點是具有較強的引用功能。只有當(dāng)內(nèi)存不夠的時候,才回收這類內(nèi)存,因此在內(nèi)存足夠的時候,它們通常不被回收。它可以用于實現(xiàn)一些常用資源的緩存,實現(xiàn)Cache的功能
5.弱引用對象與Soft引用對象最大不同就在于:GC在進行回收時,需要通過算法檢查是否回收Soft引用對象,而對于Weak引用對象,GC總是進行回收。
6.共享靜態(tài)變量存儲空間
7.有時候我們?yōu)榱颂岣呦到y(tǒng)性能,避免重復(fù)耗時的操作,希望能夠重用一些創(chuàng)建完成的對象,利用對象池實現(xiàn)。類似JDBC連接池。
8.瞬間值,序列化對象大變量時,如果此大變量又沒有用途,則使用transient聲明,不序列化此變量。同時網(wǎng)絡(luò)傳輸中也不傳輸。
9.不要提前創(chuàng)建對象
10 .(1)最基本的建議就是盡早釋放無用對象的引用
A a = new A();
a = null; //當(dāng)使用對象a之后主動將其設(shè)置為空
(2)盡量少用finalize函數(shù)。
(3) 如果需要使用經(jīng)常用到的圖片展,可以使用軟引用。
(4) 注意集合數(shù)據(jù)類型,包括數(shù)組,樹等數(shù)據(jù),這些數(shù)據(jù)結(jié)構(gòu)對GC來說,回收更為復(fù)雜,
(5) 盡量避免在類的默認(rèn)構(gòu)造器中創(chuàng)建,初始化大量的對象,防止在調(diào)用其自類的構(gòu)造器時造成不必要的內(nèi)存資源浪費。
(6) 盡量避免強制系統(tǒng)做垃圾內(nèi)存回收。
(7) 盡量避免顯式申請數(shù)組空間。
(8) 盡量在合適的場景下使用對象池技術(shù)以提高系統(tǒng)性能,縮減系統(tǒng)內(nèi)存開銷。
11.當(dāng)做數(shù)組拷貝操作時,采用System.arraycopy()方法完成拷貝操作要比采用循環(huán)的辦法完成數(shù)組拷貝操作效率高
12. 盡量避免在循環(huán)體中調(diào)用方法,因為方法調(diào)用是比較昂貴的。
13. 盡量避免在循環(huán)體中使用try-catch 塊,最好在循環(huán)體外使用try--catch塊以提高系統(tǒng)性能。
14. 在多重循環(huán)中,如果有可能,盡量將最長的循環(huán)放在最內(nèi)層,最短的循環(huán)放在最外層,以減少循環(huán)層間的變換次數(shù)。
15. 在需要線程安全的情況下,使用List list = Collections.synchronizedList(new ArrayList());
16. 如果預(yù)知長度,就設(shè)置ArrayList的長度。
17. ArrayList 與 LinkedList 選擇,熟悉底層的實現(xiàn)原理,選擇適當(dāng)?shù)娜萜鳌?/p>
18. 字符串累加采用StringBuffer.
19. 系統(tǒng)I/O優(yōu)化,采用緩沖和壓縮技術(shù)。優(yōu)化性能。
20. 避免在類在構(gòu)造器的初始化其他類
21 盡量避免在構(gòu)造中對靜態(tài)變量做賦值操作
22. 不要在類的構(gòu)造器中創(chuàng)建類的實例
23. 組合優(yōu)化繼承
24. 最好通過Class.forname() 動態(tài)的裝載類
25. JSP優(yōu)化,采用out 對象中的print方法代替println()方法
26 .采用ServletOutputStream 對象代替JSPWriter對象
27. 采用適當(dāng)?shù)闹党跏蓟痮ut 對象緩沖區(qū)的大小
28. 盡量采用forward()方法重定向新的JSP
29. 利用線程池技術(shù)處理客戶請求
30.Servlet優(yōu)化
(1) 通過init()方法來緩存一些靜態(tài)數(shù)據(jù)以提高應(yīng)用性能。
(2) 用print() 方法取代println()方法。
(3) 用ServletOutputStream 取代 PrintWriter.
(4) 盡量縮小同步代碼數(shù)量
31. 改善Servlet應(yīng)用性能的方法
(1)不要使用SingleThreadModel
(2)使用線程池ThreadPool
32. EJB優(yōu)化
實體EJB:
(1)實體EJB中常用數(shù)據(jù)緩存與釋放
(2)采用延遲加載的方式裝載關(guān)聯(lián)數(shù)據(jù)
(3)盡可能地應(yīng)用CMP類型實體EJB
(4)直接采用JDBC技術(shù)處理大型數(shù)據(jù)
33. 優(yōu)化JDBC連接
(1)設(shè)置合適的預(yù)取行值
(2)采用連接池技術(shù)
(3)全合理應(yīng)用事務(wù)
(4)選擇合適的事務(wù)隔離層與及時關(guān)閉連接對象
34. PreparedStatemetn只編譯解析一次,而Statement每次都編譯解析。
35. 盡可能地做批處理更新
36. 通過采用合適的getXXX方法提高系統(tǒng)性能
37. 采用設(shè)計模式。
條件隨機域(場)(conditional random fields,簡稱 CRF,或CRFs),是一種判別式概率模型,是隨機場的一種,常用于標(biāo)注或分析序列資料,如自然語言文字或是生物序列。
如同馬爾可夫隨機場,條件隨機場為具有無向的圖模型,圖中的頂點代表隨機變量,頂點間的連線代表隨機變量間的相依關(guān)系,在條件隨機場中,隨機變量 Y 的分布為條件機率,給定的觀察值則為隨機變量 X。原則上,條件隨機場的圖模型布局是可以任意給定的,一般常用的布局是鏈結(jié)式的架構(gòu),鏈結(jié)式架構(gòu)不論在訓(xùn)練(training)、推論(inference)、或是解碼(decoding)上,都存在效率較高的算法可供演算。
“條件隨機場”被用于中文分詞和詞性標(biāo)注等詞法分析工作,一般序列分類模型常常采用隱馬爾可夫模型(HMM),像基于類的中文分詞。但隱馬爾可夫模型中存在兩個假設(shè):輸出獨立性假設(shè)和馬爾可夫性假設(shè)。其中,輸出獨立性假設(shè)要求序列數(shù)據(jù)嚴(yán)格相互獨立才能保證推導(dǎo)的正確性,而事實上大多數(shù)序列數(shù)據(jù)不能被表示成一系列獨立事件。而條件隨機場則使用一種概率圖模型,具有表達長距離依賴性和交疊性特征的能力,能夠較好地解決標(biāo)注(分類)偏置等問題的優(yōu)點,而且所有特征可以進行全局歸一化,能夠求得全局的最優(yōu)解。
由上圖中可知, 1):貝葉斯模型(NB)和隱馬爾科夫模型(HMM)都屬于求取聯(lián)合概率的模型,而最大熵模型(ME)和條件隨機場模型(CRF)則是求取條件概率模型。2):貝葉斯模型和最大熵模型是針對單個標(biāo)簽輸出的模型,而隱馬爾科夫模型和CRF則是序列模型。
我們建模的目的是根據(jù)輸入的特征x,獲得最有可能的輸出標(biāo)簽y。
其中x代表輸入特征。
每個輸出標(biāo)簽y的概率值可簡單統(tǒng)計訓(xùn)練數(shù)據(jù)的頻率即可獲得。接下來最終我們需要計算的子項是P(y|x)
當(dāng)我們需要根據(jù)模型計算序列化標(biāo)簽時,可簡單改造貝葉斯模型,即
每個輸入x對應(yīng)一個輸出y,并且 序列輸出標(biāo)簽y之間保持獨立 ,這是一個較強的假設(shè),現(xiàn)實應(yīng)用中很難保證該假設(shè)。而假設(shè)時序標(biāo)簽y之間有時序上的依賴關(guān)系,這是一個很合理的假設(shè),因此有
由該公式,可導(dǎo)出HMM的公式為
由信息論中條件熵定義
最大熵模型的基本思想是尋找最大條件熵的同時,保持和訓(xùn)練數(shù)據(jù)信息一致。
其中p(x)由經(jīng)驗分布可近似為:
訓(xùn)練數(shù)據(jù)由特征進行表征,特征f_i的期望值由經(jīng)驗分布P(x,y)計算可得,經(jīng)驗分布概率可由變量不同值統(tǒng)計頻率計算而得。我們建模的希望能達到的是經(jīng)驗分布的期望值等于實際模型分布的期望值,即有
由約束條件
,根據(jù)經(jīng)典的解優(yōu)化方法,拉格朗日函數(shù)可得
求解拉格朗日等式可得
最大熵馬爾科夫模型是序列化的最大熵模型,最大熵模型(ME)以P(y|x)建模,單次輸入對應(yīng)單個輸出標(biāo)簽y。在序列標(biāo)簽預(yù)測任務(wù)時,基于最大熵模型,并考慮標(biāo)簽的位置信息,即得最大熵馬爾科夫模型(MEMM)。
由上式可以看出來, 模型采用局部歸一化,但是局部歸一化容易陷入局部最優(yōu),而得不到全局最優(yōu)解 。
概率無向圖模型,又稱為馬爾科夫隨機場。
由圖可知,最大團為(v1,v2,v4)和(v2,v3,v4)。
概率無向圖的聯(lián)合概率分布P(Y)可由所有最大團C上的勢函數(shù)的乘積表示
勢函數(shù)(potential functions)可以是任意函數(shù),因此勢函數(shù)不必是概率函數(shù),最終為了得到合適的概率度量,需要對最大團乘積進行歸一化。
最大熵模型條件概率為:
其勢函數(shù)為
加權(quán)特征的指數(shù)形式被廣泛采用,因為它滿足了勢函數(shù)嚴(yán)格為正的要求。
條件隨機場根據(jù)條件概率建模
由無向圖的定義,聯(lián)合概率分布P(Y)可由最大團C上的勢函數(shù)的乘積計算可得,因此
由概率無向圖的聯(lián)合概率定義可得,其勢函數(shù)為
最終
模型訓(xùn)練,由最大似然函數(shù)計算,有
CRF模型推理,1):前向-后向算法;2):維特比算法(viterbi)。
《Classical Probabilistic Models and Conditional Random Fields》
條件隨機場模型是Lafferty于2001年,在最大熵模型和隱馬爾科夫模型的基礎(chǔ)上,提出的一種判別式概率無向圖學(xué)習(xí)模型,是一種用于標(biāo)注和切分有序數(shù)據(jù)的條件概率模型;
條件隨機場模型作為一個整句聯(lián)合標(biāo)定的判別式概率模型,同時具有很強的特征融入能力,是目前解決自然語言序列標(biāo)注問題最好的統(tǒng)計模型之一。條件隨機場的缺點是訓(xùn)練的時間比較長。
條件隨機場定義
設(shè)G=(V,E)是一個無向圖,Y=(Yv),,Y表示圖中頂點的結(jié)合。如果在觀察變量X的條件下,在圖G中隨機變量Yv服從馬爾科夫?qū)傩?,即:表示在圖G中,v,w是鄰居,那么(X,Y)就表示一個條件隨機場。
即它是在給定需要標(biāo)記的觀察序列 X的條件下計算整個標(biāo)記序列 Y的聯(lián)合概率分布,而不是在給定當(dāng)前狀態(tài)條件下定義下一個狀態(tài)的分布。公式如下所示:
HMM.vs MEMM .vs CRF
隱馬爾可夫模型中存在兩個假設(shè):輸出獨立性假設(shè)和馬爾可夫性假設(shè)。其中,輸出獨立性假設(shè)要求序列數(shù)據(jù)嚴(yán)格相互獨立才能保證推導(dǎo)的正確性,而事實上大多數(shù)序列數(shù)據(jù)不能被表示成一系列獨立事件。而條件隨機場則使用一種概率圖模型,條件隨機場沒有隱馬爾可夫模型那樣嚴(yán)格的獨立性假設(shè)條件,因而可以容納任意的上下文信息,可以靈活地設(shè)計特征。同時,條件隨機場具有表達長距離依賴性和交疊性特征的能力,而且所有特征可以進行全局歸一化,能夠求得全局的最優(yōu)解,還克服了最大熵馬爾可夫模型標(biāo)記偏置的缺點。
下圖是用圖的形式描述HMM(左),MEMM(中),鏈?zhǔn)降腃RF(右)表示序列時的區(qū)別。注(空心圓表示此變量是不用于描述模型的)。其中HMM中,Yi只與Yi-1有關(guān),而Xi輸出只與Yi有關(guān)。在MEMM中,Yi?是由Xi和Yi-1確定的,在CRF中,確定某個Yi,它會考慮整個Y及Xi的。
希望我的回答可以幫到您哦