#
為了使web應用能使用saas模式的大規(guī)模訪問,必須實現(xiàn)應用的集群部署.要實現(xiàn)集群部署主要需要實現(xiàn)session共享機制,使得多臺應用服務器之間會話統(tǒng)一, tomcat等多數(shù)服務都采用了session復制技術(shù)實現(xiàn)session的共享.
session復制技術(shù)的問題:
(1)技術(shù)復雜,必須在同一種中間件之間完成(如:tomcat-tomcat之間).
(2)在節(jié)點持續(xù)增多的情況下,session復制帶來的性能損失會快速增加.特別是當session中保存了較大的對象,而且對象變化較快時,性能下降更加顯著.這種特性使得web應用的水平擴展受到了限制.
session共享的另一種思路就是把session集中起來管理,首先想到的是采用數(shù)據(jù)庫來集中存儲session,但數(shù)據(jù)庫是文件存儲相對內(nèi)存慢了一個數(shù)量級,同時這勢必加大數(shù)據(jù)庫系統(tǒng)的負擔.所以需要一種既速度快又能遠程集中存儲的服務,所以就想到了memcached.
memcached是什么?
memcached是由Danga Interactive開發(fā)的,高性能的,分布式的內(nèi)存對象緩存系統(tǒng),用于在動態(tài)應用中減少數(shù)據(jù)庫負載,提升訪問速度。
memcached能緩存什么?
通過在內(nèi)存里維護一個統(tǒng)一的巨大的hash表,Memcached能夠用來存儲各種格式的數(shù)據(jù),包括圖像、視頻、文件以及數(shù)據(jù)庫檢索的結(jié)果等。
memcached快么?
非常快。memcached使用了libevent(如果可以的話,在linux下使用epoll)來均衡任何數(shù)量的打開鏈接,使用非阻塞的網(wǎng)絡I/O,對內(nèi)部對象實現(xiàn)引用計數(shù)(因此,針對多樣的客戶端,對象可以處在多樣的狀態(tài)), 使用自己的頁塊分配器和哈希表, 因此虛擬內(nèi)存不會產(chǎn)生碎片并且虛擬內(nèi)存分配的時間復雜度可以保證為O(1).。
Danga Interactive為提升Danga Interactive的速度研發(fā)了memcached。目前,LiveJournal.com每天已經(jīng)在向一百萬用戶提供多達兩千萬次的頁面訪問。而這些,是由一個由web服務器和數(shù)據(jù)庫服務器組成的集群完成的。memcached幾乎完全放棄了任何數(shù)據(jù)都從數(shù)據(jù)庫讀取的方式,同時,它還縮短了用戶查看頁面的速度、更好的資源分配方式,以及memcache失效時對數(shù)據(jù)庫的訪問速度。
memcached的特點
memcached的緩存是一種分布式的,可以讓不同主機上的多個用戶同時訪問, 因此解決了共享內(nèi)存只能單機應用的局限,更不會出現(xiàn)使用數(shù)據(jù)庫做類似事情的時候,磁盤開銷和阻塞的發(fā)生。
使用memcached來存儲session有兩種方案:
(1)直接通過tomcat6的擴展機制實現(xiàn).
參考: http://www.javaeye.com/topic/81641
(2)通過自己編寫filter實現(xiàn).
考慮到系統(tǒng)的擴展,我們采用這種方案.這樣可以使session共享機制和中間件脫鉤.
參考: http://www.javaeye.com/topic/82565
主要思路:
(1)繼承重構(gòu)HttpServletRequestWrapper,HttpSessionWrapper類,覆蓋原來和session存取相關的方法呢,都通過SessionService類來實現(xiàn).
(2)使用filter攔截cookie中的sessionId,通過sessionId構(gòu)造新的HttpServletRequestWrapper對象,傳給后面的應用.
(3)SessionService連接memcached服務,以sessionId作為key,存取的對象是一個map.map的內(nèi)容即為session的內(nèi)容.
使用過程注意幾個問題和改進思路:
1、memcache的內(nèi)存應該足夠大,這樣不會出現(xiàn)用戶session從Cache中被清除的問題(可以關閉memcached的對象退出機制)。
2、如果session的讀取比寫入要多很多,可以在memcache前再加一個Oscache等本地緩存,減少對memcache的讀操作,從而減小網(wǎng)絡開銷,提高性能。
3、如果用戶非常多,可以使用memcached組,通過set方法中帶hashCode,插入到某個memcached服務器
對于session的清除有幾種方案:
(1)可以在凌晨人最少的時候,對memcached做一次清空。(簡單)
(2)保存在緩存中的對象設置一個失效時間,通過過濾器獲取sessionId的值,定期刷新memcached中的對象.長時間沒有被刷新的對象自動被清除.(相對復雜,消耗資源)
提出問題:為啥要有雙緩沖隊列?
引用09年9月《程序員》上的一句話:雙緩沖隊列就是沖著同步/互斥的開銷來的。我們知道,在多個線程并發(fā)訪問同一個資源的時候,需要特別注意線程的同步問題。稍稍不注意,哦活,程序結(jié)果不正確了。最經(jīng)典的就是“銀行取錢”的例子,想想,都跟現(xiàn)金掛上鉤了,看來這真不容忽視。
今天我們要談的不是如何去給資源加鎖解鎖來解決同步問題,今天的重點在于,如何將線程同步的開銷降低到我們力所能及的程度。如果你覺得,你可以通過增加硬件資源來彌補程序開銷,那么,你將不可能成為一個優(yōu)秀的程序員。
進入正題,先引入一個例子,兩個實體:一個是玩具工廠,它的工作就是不停地生產(chǎn)玩具;另外一個實體就是小孩,它的工作就是不停地從工廠拿玩具。小孩不可能直接到工廠去“拿”玩具吧?呵呵,媽媽是絕對不會放心的。所以,我們有一個“搬運工”,搬運工自然要具備“存放”的功能,不然他怎么將玩具帶給小孩呢,是吧。所以,我們先將搬運工定義為一個List,用來存放工廠生產(chǎn)出來的玩具。
代碼如下
玩具類,定義一個玩具實體
public class Toy {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
接下來是玩具工廠,它得“不停地”生產(chǎn),所以,把它設計成一個線程類
玩具工廠,將玩具對象,放到list中
public class Factory extends Thread{
public void run(){
while(true){
Toy t = new Toy ();
t.setName("玩具");
synchronized (Tools.lT){
Tools.lT.add(t);
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
注意到,在上面這段代碼當中,必須得用synchronized (Tools.lT)將Tools.lT給加鎖。不然會出現(xiàn)線程同步問題(開銷就在這里)。
再接下來,看看小孩是怎么“玩”玩具的:
小孩類,從list中取出玩具對象
public class Kid extends Thread {
long time1 = System.currentTimeMillis();
int count = 0;
public void run() {
while(true){
synchronized(Tools.lT){
if(Tools.lT.size()!=0)
Tools.lT.remove(0);
}
count++;
if(count==100000){
javax.swing.JOptionPane.showMessageDialog(null, "用時間: "+(System.currentTimeMillis()-time1));
System.exit(0);
}
}
}
}
當list不為空的時候,將list中的玩具對象,一個一個取出來,玩完!這個小孩可真夠厲害的,呵呵。可以想象為,該類的工作就是不停地向list中取出玩具對象。OK,再來編寫方法,如下
主方法
public class MainTest {
/**
* @param args
*/
public static void main(String[] args) {
Factory f = new Factory();
f.start();
Kid k = new Kid();
k.start();
}
}
最后是Tools類,他里面有個靜態(tài)的List對象:
Tools類
public class Tools {
public static List<Toy>lT = new ArrayList<Toy>(10000);
}
這樣,我們運行一下主方法,看看我們這位“天才”玩完100000個玩具,得花銷多少的時間。

好,31毫秒。
這是我們的第一種解決方法,下面再來看第二種解決方法:
其實我們在Factory類和Kid類中都進行了同步處理,這樣一來,浪費了很多時間,到底是不是這樣的呢?我們可不可以直接用一個不用處理線程同步的容器來放Toy類對象呢?這樣以來是不是就可以節(jié)省很多開銷了?這個想法是有道理的,但是,事實是不是這樣的呢?馬上實踐!
代碼就不具體貼出來了,只是我們在Tools類中用到的是一個如下的對象
Public static LinkedBlockingQueue<Toy> lT= new LinkedBlockingQueue<Toy>(1000);
對,阻塞隊列,這樣我們就只管往里面取,從里面拿了,不用自己考慮同步問題,F(xiàn)actory類和Kid類中也不同特意去加關鍵字進行同步了。
那么這種方案的結(jié)果是多少呢?同樣是100000個玩具,看結(jié)果

哦哦,變成16毫秒了,著實提高了不少效果呢。看來,在處理同步的時候擠時間,是有發(fā)展空間的,呵呵。
等等,有人要發(fā)話了,你在這磨嘰了半天,還是沒有說什么是雙緩沖啊,對!有了前面的兩種方案,我們再來看看“雙緩沖隊列”。
所謂雙緩沖隊列,自然起碼要有兩個隊列吧,呵呵,在這個例子中,我們可以設計兩個List來存放工廠生產(chǎn)出來的玩具對象。
下面分析一下:
兩個List,一個用來存,一個用來取。有點迷糊?就是有一個listP從工廠那里得到玩具對象,另外一個listT就專門把它得到的玩具對象送去給 Kid類處理。當listT變成空的了以后,再將listP中在這段時間內(nèi)取到的所有玩具對象放到listT中,好,這完了之后,他們兩個就又各自干各自的去了:listP再去取,listT再去送。這樣是不是就減少了很多次的線程同步呢?至少,在它們交換之前,listP是完全被工廠類線程占有,listT是完全被Kid類線程占有的,不用處理同步。只有在listT放完了,沒得給了,再去跟ListP換過來,這個時候就要處理同步了。
跟實際聯(lián)系一下,有兩個搬運工A,B,A在工廠,專門從工廠取玩具;B在小孩子身邊,專門送玩具給小孩玩。當B身上沒有玩具了,自然要回A那里,把A身上的玩具全部拿過來,再來送給小孩玩。在A還有玩具的時候,A和B是在各自的線程里被處理的,即A在拿,B在給。不用擔心同步問題。
這樣以來,處理同步問題的次數(shù)是不是大大減少了呢?沒錯,就是這樣的。那么怎么跟代碼結(jié)合呢?
我們要設計一個監(jiān)視線程,監(jiān)視listP是不是空了,要是空了,把它同步起來,把listT也同步起來,讓他們交換。完了就各自干各自的了。
我們來看看這個監(jiān)視類:
public class DoubleBufferList {
private List<Object> lP;
private List<Object> lT;
private int gap;
/**
* 構(gòu)造方法
*
* @param lP
* 用來存放對象的隊列
* @param lT
* 用來取對象的隊列
* @param gap
* 交換的間隔
*/
public DoubleBufferList(List lP, List lT, int gap) {
this.lP = lP;
this.lT = lT;
this.gap = gap;
}
public void check() {
Runnable runner = new Runnable() {
public void run() {
while (true) {
if (lT.size() == 0) {
synchronized (lT) {
synchronized (lP) {
lT.addAll(lP);
}
lP.clear();
}
}
try {
Thread.sleep(gap);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runner);
thread.start();
}
}
這個類中的線程方法就是用來交換的,目的就是為了減少處理同步的次數(shù)。這種方案中,F(xiàn)acotry類和Kid類跟前面單個List的情況差不多,就不再給出了。只是有一點要注意,F(xiàn)acory類中將玩具對象是放到了lP中,而Kid類中,也只是從lT中去取對象。看看Tools類中,多了一個變量:
Tools類,聲明兩個隊列
public static List<Toy>lT = new ArrayList<Toy>(10000);
public static List<Toy>lP = new ArrayList<Toy>(10000);
同樣是讓我們的“天才”玩完100000個玩具,來看看運行需要的時間:

哈哈,似乎跟我們上面的第二種方案,單阻塞隊列,沒有太大的差異。怎么解釋呢?
不用著急,來,我將額定的玩具量后多加個“0”,讓他玩完1000000個!改一下單阻塞隊列方案的輸出結(jié)果,給他們一個標記。再來看看結(jié)果:


效果出來了吧,我們再加大量,讓他們同時處理10000000個玩具對象:


充分說明,使用雙緩沖隊列,比單緩沖阻塞隊列的效果要好,更別說單緩沖隊列了。
總結(jié):
從上面的分析,我們可以得知,在處理線程同步的時候,是要花費我們的時間的,雖然在有些時候,這樣的花費是我們可以接受的,但是在很多情況下,如果我們能注意到這樣的浪費,并且及時地完善我們的程序,這樣可以更大限度地提高我們程序的運行效率。尤其是在大的程序里面,這樣的效果體現(xiàn)得更明顯。而往往越大的系統(tǒng),對性能的要求也就越高。
android在處理一寫圖片資源的時候,會進行一些類型的轉(zhuǎn)換,現(xiàn)在有空整理一下:
1、Drawable → Bitmap 的簡單方法
((BitmapDrawable)res.getDrawable(R.drawable.youricon)).getBitmap();
2、Drawable → Bitmap
Java代碼
public static Bitmap drawableToBitmap(Drawable drawable) {
Bitmap bitmap = Bitmap
.createBitmap(
drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(),
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565);
Canvas canvas = new Canvas(bitmap);
//canvas.setBitmap(bitmap);
drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
drawable.draw(canvas);
return bitmap;
}
3.Bitmap→Drawable 的簡單方法
BitmapDrawable bitmapDrawable = (BitmapDrawable)bitmap;
Drawable drawable = (Drawable)bitmapDrawable;
Bitmap bitmap = new Bitmap (...);
Drawable drawable = new BitmapDrawable(bitmap);
3、從資源中獲取Bitmap
Java代碼
Resources res=getResources();
Bitmap bmp=BitmapFactory.decodeResource(res, R.drawable.pic);
4、Bitmap → byte[]
Java代碼
private byte[] Bitmap2Bytes(Bitmap bm){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
5、 byte[] → Bitmap
Java代碼
private Bitmap Bytes2Bimap(byte[] b){
if(b.length!=0){
return BitmapFactory.decodeByteArray(b, 0, b.length);
}
else {
return null;
}
}
修改TabHost默認樣式
TabHost是Android提供的一個容器組件,利用它可以輕松地實現(xiàn)TAB界面,如下圖所示:

但很多時候,默認的TAB樣式并不符合軟件的整體風格,這時候該怎么辦呢?其實,我們可以編寫XML對其樣式進行修改。下面修改后的效果圖:

1. TabHost布局文件 main.xml
<TabHost
android:id="@+id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="30dip"
android:background="#a0a0a0"
android:layout_weight="0" />
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1">
<ListView
android:id="@+id/user_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@drawable/divider_line"
android:cacheColorHint="#00000000" />
<ListView
android:id="@+id/article_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@drawable/divider_line"
android:cacheColorHint="#00000000" />
<ListView
android:id="@+id/feed_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@drawable/divider_line"
android:cacheColorHint="#00000000" />
<ListView
android:id="@+id/book_list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:divider="@drawable/divider_line"
android:cacheColorHint="#00000000" />
</FrameLayout>
</LinearLayout>
</TabHost>
FrameLayout里有四個ListView 分別對應用戶、文章、頻道、圖書。
TabWidget和FrameLayout的ID不能自己定義修改。
2. Activity后臺代碼
RelativeLayout articleTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
TextView articleTabLabel = (TextView) articleTab.findViewById(R.id.tab_label);
articleTabLabel.setText("文章");
RelativeLayout feedTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
TextView feedTabLabel = (TextView) feedTab.findViewById(R.id.tab_label);
feedTabLabel.setText("頻道");
RelativeLayout bookTab = (RelativeLayout) LayoutInflater.from(this).inflate(R.layout.minitab, null);
TextView bookTabLabel = (TextView) bookTab.findViewById(R.id.tab_label);
bookTabLabel.setText("圖書");
TabHost tabHost = (TabHost) findViewById(R.id.tabhost);
tabHost.setup();
tabHost.addTab(tabHost.newTabSpec("user").setIndicator(userTab).setContent(R.id.user_list));
tabHost.addTab(tabHost.newTabSpec("article").setIndicator(articleTab).setContent(R.id.article_list));
tabHost.addTab(tabHost.newTabSpec("feed").setIndicator(feedTab).setContent(R.id.feed_list));
tabHost.addTab(tabHost.newTabSpec("book").setIndicator(bookTab).setContent(R.id.book_list));
|
TabHost創(chuàng)建出來以后,必須先setup一下,tabHost.setup();
setIndicator方法設置的View其實就對應了TAB中的一個個選項卡,它們都是通過一個叫minitab的布局文件inflate出來的
3. 選項卡布局文件minitab.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingLeft="5dip"
android:paddingRight="5dip">
<TextView android:id="@+id/tab_label"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center"
android:textColor="#000000"
android:textStyle="bold"
android:background="@drawable/minitab" />
</RelativeLayout>
drawable/minitab是一個selector,指定了Tab選項卡的背景顏色。
4. selector文件 minitab.xml
<?xml version="1.0" encoding="utf-8"?>
<selector
xmlns:android="http://schemas.android.com/apk/res/android"
>
<item
android:state_selected="false"
android:drawable="@drawable/minitab_unselected"
>
</item>
<item
android:state_selected="true"
android:drawable="@drawable/minitab_default"
>
</item>
</selector>
minitab_unselected是一淺藍色背景圖片
minitab_default是一白色背景圖片
摘 要 Java規(guī)則引擎是一種嵌入在Java程序中的組件,它的任務是把當前提交給引擎的Java數(shù)據(jù)對象與加載在引擎中的業(yè)務規(guī)則進行測試和比對,激活那些符合當前數(shù)據(jù)狀態(tài)下的業(yè)務規(guī)則,根據(jù)業(yè)務規(guī)則中聲明的執(zhí)行邏輯,觸發(fā)應用程序中對應的操作。
引言
目前,Java社區(qū)推動并發(fā)展了一種引人注目的新技術(shù)——Java規(guī)則引擎(Rule Engine)。利用它就可以在應用系統(tǒng)中分離商業(yè)決策者的商業(yè)決策邏輯和應用開發(fā)者的技術(shù)決策,并把這些商業(yè)決策放在中心數(shù)據(jù)庫或其他統(tǒng)一的地方,讓它們能在運行時可以動態(tài)地管理和修改,從而為企業(yè)保持靈活性和競爭力提供有效的技術(shù)支持。
規(guī)則引擎的原理
1、基于規(guī)則的專家系統(tǒng)(RBES)簡介
Java規(guī)則引擎起源于基于規(guī)則的專家系統(tǒng),而基于規(guī)則的專家系統(tǒng)又是專家系統(tǒng)的其中一個分支。專家系統(tǒng)屬于人工智能的范疇,它模仿人類的推理方式,使用試探性的方法進行推理,并使用人類能理解的術(shù)語解釋和證明它的推理結(jié)論。為了更深入地了解Java規(guī)則引擎,下面簡要地介紹基于規(guī)則的專家系統(tǒng)。RBES包括三部分:Rule Base(knowledge base)、Working Memory(fact base)和Inference Engine。它們的結(jié)構(gòu)如下系統(tǒng)所示:
圖1 基于規(guī)則的專家系統(tǒng)構(gòu)成
如圖1所示,推理引擎包括三部分:模式匹配器(Pattern Matcher)、議程(Agenda)和執(zhí)行引擎(Execution Engine)。推理引擎通過決定哪些規(guī)則滿足事實或目標,并授予規(guī)則優(yōu)先級,滿足事實或目標的規(guī)則被加入議程。模式匹配器決定選擇執(zhí)行哪個規(guī)則,何時執(zhí)行規(guī)則;議程管理模式匹配器挑選出來的規(guī)則的執(zhí)行次序;執(zhí)行引擎負責執(zhí)行規(guī)則和其他動作。
和人類的思維相對應,推理引擎存在兩者推理方式:演繹法(Forward-Chaining)和歸納法(Backward-Chaining)。演繹法從一個初始的事實出發(fā),不斷地應用規(guī)則得出結(jié)論(或執(zhí)行指定的動作)。而歸納法則是根據(jù)假設,不斷地尋找符合假設的事實。Rete算法是目前效率最高的一個Forward-Chaining推理算法,許多Java規(guī)則引擎都是基于Rete算法來進行推理計算的。
推理引擎的推理步驟如下:
(1)將初始數(shù)據(jù)(fact)輸入Working Memory。
(2)使用Pattern Matcher比較規(guī)則庫(rule base)中的規(guī)則(rule)和數(shù)據(jù)(fact)。
(3)如果執(zhí)行規(guī)則存在沖突(conflict),即同時激活了多個規(guī)則,將沖突的規(guī)則放入沖突集合。
(4)解決沖突,將激活的規(guī)則按順序放入Agenda。
(5)使用執(zhí)行引擎執(zhí)行Agenda中的規(guī)則。重復步驟2至5,直到執(zhí)行完畢所有Agenda中的規(guī)則。
上述即是規(guī)則引擎的原始架構(gòu),Java規(guī)則引擎就是從這一原始架構(gòu)演變而來的。
2、規(guī)則引擎相關構(gòu)件
規(guī)則引擎是一種根據(jù)規(guī)則中包含的指定過濾條件,判斷其能否匹配運行時刻的實時條件來執(zhí)行規(guī)則中所規(guī)定的動作的引擎。與規(guī)則引擎相關的有四個基本概念,為更好地理解規(guī)則引擎的工作原理,下面將對這些概念進行逐一介紹。
1)信息元(Information Unit)
信息元是規(guī)則引擎的基本建筑塊,它是一個包含了特定事件的所有信息的對象。這些信息包括:消息、產(chǎn)生事件的應用程序標識、事件產(chǎn)生事件、信息元類型、相關規(guī)則集、通用方法、通用屬性以及一些系統(tǒng)相關信息等等。
2)信息服務(Information Services)
信息服務產(chǎn)生信息元對象。每個信息服務產(chǎn)生它自己類型相對應的信息元對象。即特定信息服務根據(jù)信息元所產(chǎn)生每個信息元對象有相同的格式,但可以有不同的屬性和規(guī)則集。需要注意的是,在一臺機器上可以運行許多不同的信息服務,還可以運行同一信息服務的不同實例。但無論如何,每個信息服務只產(chǎn)生它自己類型相對應的信息元。
3)規(guī)則集(Rule Set)
顧名思義,規(guī)則集就是許多規(guī)則的集合。每條規(guī)則包含一個條件過濾器和多個動作。一個條件過濾器可以包含多個過濾條件。條件過濾器是多個布爾表達式的組合,其組合結(jié)果仍然是一個布爾類型的。在程序運行時,動作將會在條件過濾器值為真的情況下執(zhí)行。除了一般的執(zhí)行動作,還有三類比較特別的動作,它們分別是:放棄動作(Discard Action)、包含動作(Include Action)和使信息元對象內(nèi)容持久化的動作。前兩種動作類型的區(qū)別將在2.3規(guī)則引擎工作機制小節(jié)介紹。
4)隊列管理器(Queue Manager)
隊列管理器用來管理來自不同信息服務的信息元對象的隊列。
下面將研究規(guī)則引擎的這些相關構(gòu)件是如何協(xié)同工作的。
如圖2所示,處理過程分為四個階段進行:信息服務接受事件并將其轉(zhuǎn)化為信息元,然后這些信息元被傳給隊列管理器,最后規(guī)則引擎接收這些信息元并應用它們自身攜帶的規(guī)則加以執(zhí)行,直到隊列管理器中不再有信息元。
圖2 處理過程協(xié)作圖
3、規(guī)則引擎的工作機制
下面專門研究規(guī)則引擎的內(nèi)部處理過程。如圖3所示,規(guī)則引擎從隊列管理器中依次接收信息元,然后依規(guī)則的定義順序檢查信息元所帶規(guī)則集中的規(guī)則。如圖所示,規(guī)則引擎檢查第一個規(guī)則并對其條件過濾器求值,如果值為假,所有與此規(guī)則相關的動作皆被忽略并繼續(xù)執(zhí)行下一條規(guī)則。如果第二條規(guī)則的過濾器值為真,所有與此規(guī)則相關的動作皆依定義順序執(zhí)行,執(zhí)行完畢繼續(xù)下一條規(guī)則。該信息元中的所有規(guī)則執(zhí)行完畢后,信息元將被銷毀,然后從隊列管理器接收下一個信息元。在這個過程中并未考慮兩個特殊動作:放棄動作(Discard Action)和包含動作(Include Action)。放棄動作如果被執(zhí)行,將會跳過其所在信息元中接下來的所有規(guī)則,并銷毀所在信息元,規(guī)則引擎繼續(xù)接收隊列管理器中的下一個信息元。包含動作其實就是動作中包含其它現(xiàn)存規(guī)則集的動作。包含動作如果被執(zhí)行,規(guī)則引擎將暫停并進入被包含的規(guī)則集,執(zhí)行完畢后,規(guī)則引擎還會返回原來暫停的地方繼續(xù)執(zhí)行。這一過程將遞歸進行。
圖3 規(guī)則引擎工作機制
Java規(guī)則引擎的工作機制與上述規(guī)則引擎機制十分類似,只不過對上述概念進行了重新包裝組合。Java規(guī)則引擎對提交給引擎的Java數(shù)據(jù)對象進行檢索,根據(jù)這些對象的當前屬性值和它們之間的關系,從加載到引擎的規(guī)則集中發(fā)現(xiàn)符合條件的規(guī)則,創(chuàng)建這些規(guī)則的執(zhí)行實例。這些實例將在引擎接到執(zhí)行指令時、依照某種優(yōu)先序依次執(zhí)行。一般來講,Java規(guī)則引擎內(nèi)部由下面幾個部分構(gòu)成:工作內(nèi)存(Working Memory)即工作區(qū),用于存放被引擎引用的數(shù)據(jù)對象集合;規(guī)則執(zhí)行隊列,用于存放被激活的規(guī)則執(zhí)行實例;靜態(tài)規(guī)則區(qū),用于存放所有被加載的業(yè)務規(guī)則,這些規(guī)則將按照某種數(shù)據(jù)結(jié)構(gòu)組織,當工作區(qū)中的數(shù)據(jù)發(fā)生改變后,引擎需要迅速根據(jù)工作區(qū)中的對象現(xiàn)狀,調(diào)整規(guī)則執(zhí)行隊列中的規(guī)則執(zhí)行實例。Java規(guī)則引擎的結(jié)構(gòu)示意圖如圖4所示。
圖4 Java規(guī)則引擎工作機制
當引擎執(zhí)行時,會根據(jù)規(guī)則執(zhí)行隊列中的優(yōu)先順序逐條執(zhí)行規(guī)則執(zhí)行實例,由于規(guī)則的執(zhí)行部分可能會改變工作區(qū)的數(shù)據(jù)對象,從而會使隊列中的某些規(guī)則執(zhí)行實例因為條件改變而失效,必須從隊列中撤銷,也可能會激活原來不滿足條件的規(guī)則,生成新的規(guī)則執(zhí)行實例進入隊列。于是就產(chǎn)生了一種“動態(tài)”的規(guī)則執(zhí)行鏈,形成規(guī)則的推理機制。這種規(guī)則的“鏈式”反應完全是由工作區(qū)中的數(shù)據(jù)驅(qū)動的。
任何一個規(guī)則引擎都需要很好地解決規(guī)則的推理機制和規(guī)則條件匹配的效率問題。規(guī)則條件匹配的效率決定了引擎的性能,引擎需要迅速測試工作區(qū)中的數(shù)據(jù)對象,從加載的規(guī)則集中發(fā)現(xiàn)符合條件的規(guī)則,生成規(guī)則執(zhí)行實例。1982年美國卡耐基·梅隆大學的Charles L. Forgy發(fā)明了一種叫Rete算法,很好地解決了這方面的問題。目前世界頂尖的商用業(yè)務規(guī)則引擎產(chǎn)品基本上都使用Rete算法。
Java規(guī)則引擎API——JSR-94
為了使規(guī)則引擎技術(shù)標準化,Java社區(qū)制定了Java規(guī)則引擎API(JSR94)規(guī)范。它為Java平臺訪問規(guī)則引擎定義了一些簡單的API。
Java規(guī)則引擎API在javax.rules包中定義,是訪問規(guī)則引擎的標準企業(yè)級API。Java規(guī)則引擎API允許客戶程序使用統(tǒng)一的方式和不同廠商的規(guī)則引擎產(chǎn)品交互,就如同使用JDBC編寫獨立于廠商訪問不同的數(shù)據(jù)庫產(chǎn)品一樣。Java規(guī)則引擎API包括創(chuàng)建和管理規(guī)則集合的機制,在工作區(qū)中添加,刪除和修改對象的機制,以及初始化,重置和執(zhí)行規(guī)則引擎的機制。
1、Java規(guī)則引擎API體系結(jié)構(gòu)
Java規(guī)則引擎API主要由兩大類API組成: 規(guī)則管理API(The Rules Administrator API)和運行時客戶API(The Runtime Client API)。
1)規(guī)則管理API
規(guī)則管理API在javax.rules.admin中定義,包含裝載規(guī)則以及與規(guī)則對應的動作(執(zhí)行集 execution sets)以及實例化規(guī)則引擎。規(guī)則可以從外部資源中裝載,比如URI,Input streams, XML streams和readers等等。同時規(guī)則管理API還提供了注冊和取消注冊執(zhí)行集以及對執(zhí)行集進行維護的機制。使用admin包定義規(guī)則有助于對客戶訪問運行規(guī)則進行控制管理,它通過在執(zhí)行集上定義許可權(quán)使得未經(jīng)授權(quán)的用戶無法訪問受控規(guī)則。
規(guī)則管理API使用類RuleServiceProvider來獲得規(guī)則管理器(RuleAdministrator)接口的實例。該接口提供方法注冊和取消注冊執(zhí)行集。規(guī)則管理器提供了本地和遠程的RuleExecutionSetProvider,它負責創(chuàng)建規(guī)則執(zhí)行集(RuleExecutionSet)。規(guī)則執(zhí)行集可以從如XML streams, binary streams等來源中創(chuàng)建。這些數(shù)據(jù)來源及其內(nèi)容經(jīng)匯集和序列化后傳送到遠程的運行規(guī)則引擎的服務器上。在大多數(shù)應用程序中,遠程規(guī)則引擎或遠程規(guī)則數(shù)據(jù)來源的情況并不多。為了避免這些情況中的網(wǎng)絡開銷,API規(guī)定了可以從運行在同一JVM中規(guī)則庫中讀取數(shù)據(jù)的本地RuleExecutionSetProvider。規(guī)則執(zhí)行集接口除了擁有能夠獲得有關規(guī)則執(zhí)行集的方法,還有能夠檢索在規(guī)則執(zhí)行集中定義的所有規(guī)則對象。這使得客戶能夠知道規(guī)則集中的規(guī)則對象并且按照自己需要來使用它們。
2)運行時客戶API
運行時API在javax.rules包中定義,為規(guī)則引擎用戶運行規(guī)則獲得結(jié)果提供了類和方法。運行時客戶只能訪問那些使用規(guī)則管理API注冊過的規(guī)則,運行時API幫助用戶獲得規(guī)則會話,并在這個會話中執(zhí)行規(guī)則。
運行時API提供了對廠商規(guī)則引擎API的訪問方法,這類似于JDBC。類RuleServiceProvider提供了對具體規(guī)則引擎實現(xiàn)的運行時和管理API的訪問,規(guī)則引擎廠商通過該類將其規(guī)則引擎實現(xiàn)提供給客戶,并獲得RuleServiceProvider唯一標識規(guī)則引擎的URL。此URL的標準用法是使用類似于“com.mycompany.myrulesengine.rules.RuleServiceProvider”這樣的Internet域名空間,這保證了訪問URL的唯一性。類RuleServiceProvider內(nèi)部實現(xiàn)了規(guī)則管理和運行時訪問所需的接口。所有的RuleServiceProvider要想被客戶所訪問都必須用RuleServiceProviderManager進行注冊,注冊方式類似于JDBC API的DriverManager和Driver。
運行時接口是運行時API的關鍵部分。運行時接口提供了用于創(chuàng)建規(guī)則會話(RuleSession)的方法,規(guī)則會話是用來運行規(guī)則的。運行時API同時也提供了訪問在service provider注冊過的所有規(guī)則執(zhí)行集(RuleExecutionSets)。規(guī)則會話接口定義了客戶使用的會話的類型,客戶根據(jù)自己運行規(guī)則的方式可以選擇使用有狀態(tài)會話或者無狀態(tài)會話。無狀態(tài)會話的工作方式就像一個無狀態(tài)會話bean。客戶可以發(fā)送單個輸入對象或一列對象來獲得輸出對象。當客戶需要一個與規(guī)則引擎間的專用會話時,有狀態(tài)會話就很有用。輸入的對象通過addObject() 方法可以加入到會話當中。同一個會話當中可以加入多個對象。對話中已有對象可以通過使用updateObject()方法得到更新。只要客戶與規(guī)則引擎間的會話依然存在,會話中的對象就不會丟失。
RuleExecutionSetMetaData接口提供給客戶讓其查找規(guī)則執(zhí)行集的元數(shù)據(jù)(metadata)。元數(shù)據(jù)通過規(guī)則會話接口(RuleSession Interface)提供給用戶。
2、Java規(guī)則引擎API安全問題
規(guī)則引擎API將管理API和運行時API加以分開,從而為這些包提供了較好粒度的安全控制。規(guī)則引擎API并沒有提供明顯的安全機制,它可以和J2EE規(guī)范中定義的標準安全API聯(lián)合使用。安全可以由以下機制提供,如Java 認證和授權(quán)服務 (JAAS),Java加密擴展(JCE),Java安全套接字擴展(JSSE),或者其它定制的安全API。使用JAAS可以定義規(guī)則執(zhí)行集的許可權(quán)限,從而只有授權(quán)用戶才能訪問。
3、異常與日志
規(guī)則引擎API定義了javax.rules.RuleException作為規(guī)則引擎異常層次的根類。所有其它異常都繼承于這個根類。規(guī)則引擎中定義的異常都是受控制的異常(checked exceptions),所以捕獲異常的任務就交給了規(guī)則引擎。規(guī)則引擎API沒有提供明確的日志機制,但是它建議將Java Logging API用于規(guī)則引擎API。
JSR 94 為規(guī)則引擎提供了公用標準API,僅僅為實現(xiàn)規(guī)則管理API和運行時API提供了指導規(guī)范,并沒有提供規(guī)則和動作該如何定義以及該用什么語言定義規(guī)則,也沒有為規(guī)則引擎如何讀和評價規(guī)則提供技術(shù)性指導。
結(jié)束語
規(guī)則引擎技術(shù)為管理多變的業(yè)務邏輯提供了一種解決方案。規(guī)則引擎既可以管理應用層的業(yè)務邏輯又可以使表示層的頁面流程可訂制。這就給軟件架構(gòu)師設計大型信息系統(tǒng)提供了一項新的選擇。而Java規(guī)則引擎在Java社區(qū)制定標準規(guī)范以后必將獲得更大發(fā)展。
Drools規(guī)則引擎初學入門實例HelloWorld
(1)下載eclipse(www.eclipse.org),如果是一般的java開發(fā),下載Eclipse IDE for Java Developers就行了,解壓后即可使用;
(2)下載Drools(http://jboss.org/drools/downloads.html),目前最新版本是Drools 4.0.7 Binaries,下載后解壓即可;
(3)之后下載eclipse的Drools插件,版本跟eclipse對應,目前有Drools 4.0.7 Eclipse 3.2 Workbench和Drools 4.0.7 Eclipse Europa 3.3 Workbench兩種。
Drools插件解壓后,將里面的org.drools.eclipse_4.0.7.jar文件copy到eclipse的plugins目錄中,重啟eclipse,在工具欄可以看到一個 圖標,這就是Drools的工作臺,之后就可通過這個按鈕創(chuàng)建Drools resource文件了。
(4)開始Hello World
Java文件:DroolsTest.java
package com.sample;
import java.io.InputStreamReader;
import java.io.Reader;
import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.WorkingMemory;
import org.drools.compiler.PackageBuilder;
import org.drools.rule.Package;
/**
* This is a sample file to launch a rule package from a rule source file.
*/
public class DroolsTest {
public static final void main(String[] args) {
try {
//load up the rulebase
RuleBase ruleBase = readRule();
WorkingMemory workingMemory = ruleBase.newStatefulSession();
//go !
Message message = new Message();
message.setMessage( "Hello World" );
message.setStatus( Message.HELLO );
workingMemory.insert( message );
workingMemory.fireAllRules();
} catch (Throwable t) {
t.printStackTrace();
}
}
/**
* Please note that this is the "low level" rule assembly API.
*/
private static RuleBase readRule() throws Exception {
//read in the source
Reader source = new InputStreamReader( DroolsTest.class.getResourceAsStream( "/Sample.drl" ) );
//optionally read in the DSL (if you are using it).
//Reader dsl = new InputStreamReader( DroolsTest.class.getResourceAsStream( "/mylang.dsl" ) );
//Use package builder to build up a rule package.
//An alternative lower level class called "DrlParser" can also be used...
PackageBuilder builder = new PackageBuilder();
//this wil parse and compile in one step
//NOTE: There are 2 methods here, the one argument one is for normal DRL.
builder.addPackageFromDrl( source );
//Use the following instead of above if you are using a DSL:
//builder.addPackageFromDrl( source, dsl );
//get the compiled package (which is serializable)
Package pkg = builder.getPackage();
//add the package to a rulebase (deploy the rule package).
RuleBase ruleBase = RuleBaseFactory.newRuleBase();
ruleBase.addPackage( pkg );
return ruleBase;
}
public static class Message {
public static final int HELLO = 0;
public static final int GOODBYE = 1;
public static final int GAME_OVER = 2;
private String message;
private int status;
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
public int getStatus() {
return this.status;
}
public void setStatus( int status ) {
this.status = status;
}
}
}
選擇插件Drools按鈕里的"New Rule resource",建立規(guī)則(rule)文件:Sample.drl
package com.sample
import com.sample.DroolsTest.Message;
rule "Hello World"
when
m : Message( status == Message.HELLO, message : message )
then
System.out.println( message );
m.setMessage( "Goodbye cruel world" );
m.setStatus( Message.GOODBYE );
update( m );
end
rule "GoodBye"
no-loop true
when
m : Message( status == Message.GOODBYE, message : message )
then
System.out.println( message );
m.setStatus(Message.GAME_OVER);
m.setMessage("game over now!");
update( m );
end
rule "game over"
when
m : Message( status == Message.GAME_OVER)
then
System.out.println( m.getMessage() );
end
注意:文件要放在相應的包里,然后編譯—執(zhí)行,當時出現(xiàn)了錯誤,查找資料,還需要加載包,包括:
<1> Drools 4.0.7目錄下的drools-core-4.0.7.jar,drools-compiler-4.0.7.jar
<2> Drools 4.0.7\lib目錄下的antlr-runtime-3.0.jar,mvel-1.3.1-java1.4.jar
<3>以及eclipse\plugins目錄下的org.eclipse.jdt.core_3.2.3.v_686_R32x.jar(不同版本,包名會稍有不同)。
重新運行,應該就不會有錯了。執(zhí)行結(jié)果如下:
Hello World
Goodbye cruel world
game over now!
Oracle JOB 間隔時間詳解
INTERVAL參數(shù)設置:
每天運行一次 'SYSDATE + 1'
每小時運行一次 'SYSDATE + 1/24'
每10分鐘運行一次 'SYSDATE + 10/(60*24)'
每30秒運行一次 'SYSDATE + 30/(60*24*60)'
每隔一星期運行一次 'SYSDATE + 7'
每個月最后一天運行一次 'TRUNC(LAST_DAY(ADD_MONTHS(SYSDATE,1))) + 23/24'
每年1月1號零時 'TRUNC(LAST_DAY(TO_DATE(EXTRACT(YEAR from SYSDATE)||'12'||'01','YYYY-MM-DD'))+1)'
每天午夜12點 'TRUNC(SYSDATE + 1)'
每天早上8點30分 'TRUNC(SYSDATE + 1) + (8*60+30)/(24*60)'
每星期二中午12點 'NEXT_DAY(TRUNC(SYSDATE ), ''TUESDAY'' ) + 12/24'
每個月第一天的午夜12點 'TRUNC(LAST_DAY(SYSDATE ) + 1)'
每個月最后一天的23點 'TRUNC (LAST_DAY (SYSDATE)) + 23 / 24'
每個季度最后一天的晚上11點 'TRUNC(ADD_MONTHS(SYSDATE + 2/24, 3 ), 'Q' ) -1/24'
每星期六和日早上6點10分 'TRUNC(LEAST(NEXT_DAY(SYSDATE, ''S ......
- 堆大小設置
JVM 中最大堆大小有三方面限制:相關操作系統(tǒng)的數(shù)據(jù)模型(32-bt還是64-bit)限制;系統(tǒng)的可用虛擬內(nèi)存限制;系統(tǒng)的可用物理內(nèi)存限制。32位系統(tǒng)下,一般限制在1.5G~2G;64為操作系統(tǒng)對內(nèi)存無限制。我在Windows Server 2003 系統(tǒng),3.5G物理內(nèi)存,JDK5.0下測試,最大可設置為1478m。
典型設置:
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m:設置JVM最大可用內(nèi)存為3550M。
-Xms3550m:設置JVM促使內(nèi)存為3550m。此值可以設置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內(nèi)存。
-Xmn2g:設置年輕代大小為2G。整個JVM內(nèi)存大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代后,將會減小年老代大小。此值對系統(tǒng)性能影響較大,Sun官方推薦配置為整個堆的3/8。
-Xss128k:設置每個線程的堆棧大小。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。更具應用的線程所需內(nèi)存大小進行調(diào)整。在相同物理內(nèi)存下,減小這個值能生成更多的線程。但是操作系統(tǒng)對一個進程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗值在3000~5000左右。
- java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:設置年輕代(包括Eden和兩個Survivor區(qū))與年老代的比值(除去持久代)。設置為4,則年輕代與年老代所占比值為1:4,年輕代占整個堆棧的1/5
-XX:SurvivorRatio=4:設置年輕代中Eden區(qū)與Survivor區(qū)的大小比值。設置為4,則兩個Survivor區(qū)與一個Eden區(qū)的比值為2:4,一個Survivor區(qū)占整個年輕代的1/6
-XX:MaxPermSize=16m:設置持久代大小為16m。
-XX:MaxTenuringThreshold=0:設置垃圾最大年齡。如果設置為0的話,則年輕代對象不經(jīng)過Survivor區(qū),直接進入年老代。對于年老代比較多的應用,可以提高效率。如果將此值設置為一個較大值,則年輕代對象會在Survivor區(qū)進行多次復制,這樣可以增加對象再年輕代的存活時間,增加在年輕代即被回收的概論。
- 回收器選擇
JVM給了三種選擇:串行收集器、并行收集器、并發(fā)收集器,但是串行收集器只適用于小數(shù)據(jù)量的情況,所以這里的選擇主要針對并行收集器和并發(fā)收集器。默認情況下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在啟動時加入相應參數(shù)。JDK5.0以后,JVM會根據(jù)當前系統(tǒng)配置進行判斷。
- 吞吐量優(yōu)先的并行收集器
如上文所述,并行收集器主要以到達一定的吞吐量為目標,適用于科學技術(shù)和后臺處理等。
典型配置:
- java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC:選擇垃圾收集器為并行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用并發(fā)收集,而年老代仍舊使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的線程數(shù),即:同時多少個線程一起進行垃圾回收。此值最好配置與處理器數(shù)目相等。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+UseParallelOldGC
-XX:+UseParallelOldGC:配置年老代垃圾收集方式為并行收集。JDK6.0支持對年老代并行收集。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100
-XX:MaxGCPauseMillis=100:設置每次年輕代垃圾回收的最長時間,如果無法滿足此時間,JVM會自動調(diào)整年輕代大小,以滿足此值。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:設置此選項后,并行收集器會自動選擇年輕代區(qū)大小和相應的Survivor區(qū)比例,以達到目標系統(tǒng)規(guī)定的最低相應時間或者收集頻率等,此值建議使用并行收集器時,一直打開。
- 響應時間優(yōu)先的并發(fā)收集器
如上文所述,并發(fā)收集器主要是保證系統(tǒng)的響應時間,減少垃圾收集時的停頓時間。適用于應用服務器、電信領域等。
典型配置:
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:設置年老代為并發(fā)收集。測試中配置這個以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此時年輕代大小最好用-Xmn設置。
-XX:+UseParNewGC:設置年輕代為并行收集。可與CMS收集同時使用。JDK5.0以上,JVM會根據(jù)系統(tǒng)配置自行設置,所以無需再設置此值。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并發(fā)收集器不對內(nèi)存空間進行壓縮、整理,所以運行一段時間以后會產(chǎn)生“碎片”,使得運行效率降低。此值設置運行多少次GC以后對內(nèi)存空間進行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮。可能會影響性能,但是可以消除碎片
- 輔助信息
JVM提供了大量命令行參數(shù),打印信息,供調(diào)試使用。主要有以下一些:
- -XX:+PrintGC
輸出形式:[GC 118250K->113543K(130112K), 0.0094143 secs]
[Full GC 121376K->10414K(130112K), 0.0650971 secs]
- -XX:+PrintGCDetails
輸出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs]
[GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs]
- -XX:+PrintGCTimeStamps -XX:+PrintGC:PrintGCTimeStamps可與上面兩個混合使用
輸出形式:11.851: [GC 98328K->93620K(130112K), 0.0082960 secs]
- -XX:+PrintGCApplicationConcurrentTime:打印每次垃圾回收前,程序未中斷的執(zhí)行時間。可與上面混合使用
輸出形式:Application time: 0.5291524 seconds
- -XX:+PrintGCApplicationStoppedTime:打印垃圾回收期間程序暫停的時間。可與上面混合使用
輸出形式:Total time for which application threads were stopped: 0.0468229 seconds
- -XX:PrintHeapAtGC:打印GC前后的詳細堆棧信息
輸出形式:
34.702: [GC {Heap before gc invocations=7:
def new generation total 55296K, used 52568K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 99% used [0x1ebd0000, 0x21bce430, 0x21bd0000)
from space 6144K, 55% used [0x221d0000, 0x22527e10, 0x227d0000)
to space 6144K, 0% used [0x21bd0000, 0x21bd0000, 0x221d0000)
tenured generation total 69632K, used 2696K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 3% used [0x227d0000, 0x22a720f8, 0x22a72200, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
34.735: [DefNew: 52568K->3433K(55296K), 0.0072126 secs] 55264K->6615K(124928K)Heap after gc invocations=8:
def new generation total 55296K, used 3433K [0x1ebd0000, 0x227d0000, 0x227d0000)
eden space 49152K, 0% used [0x1ebd0000, 0x1ebd0000, 0x21bd0000)
from space 6144K, 55% used [0x21bd0000, 0x21f2a5e8, 0x221d0000)
to space 6144K, 0% used [0x221d0000, 0x221d0000, 0x227d0000)
tenured generation total 69632K, used 3182K [0x227d0000, 0x26bd0000, 0x26bd0000)
the space 69632K, 4% used [0x227d0000, 0x22aeb958, 0x22aeba00, 0x26bd0000)
compacting perm gen total 8192K, used 2898K [0x26bd0000, 0x273d0000, 0x2abd0000)
the space 8192K, 35% used [0x26bd0000, 0x26ea4ba8, 0x26ea4c00, 0x273d0000)
ro space 8192K, 66% used [0x2abd0000, 0x2b12bcc0, 0x2b12be00, 0x2b3d0000)
rw space 12288K, 46% used [0x2b3d0000, 0x2b972060, 0x2b972200, 0x2bfd0000)
}
, 0.0757599 secs]
- -Xloggc:filename:與上面幾個配合使用,把相關日志信息記錄到文件以便分析。
- 常見配置匯總
- 堆設置
- -Xms:初始堆大小
- -Xmx:最大堆大小
- -XX:NewSize=n:設置年輕代大小
- -XX:NewRatio=n:設置年輕代和年老代的比值。如:為3,表示年輕代與年老代比值為1:3,年輕代占整個年輕代年老代和的1/4
- -XX:SurvivorRatio=n:年輕代中Eden區(qū)與兩個Survivor區(qū)的比值。注意Survivor區(qū)有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區(qū)占整個年輕代的1/5
- -XX:MaxPermSize=n:設置持久代大小
- 收集器設置
- -XX:+UseSerialGC:設置串行收集器
- -XX:+UseParallelGC:設置并行收集器
- -XX:+UseParalledlOldGC:設置并行年老代收集器
- -XX:+UseConcMarkSweepGC:設置并發(fā)收集器
- 垃圾回收統(tǒng)計信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
- 并行收集器設置
- -XX:ParallelGCThreads=n:設置并行收集器收集時使用的CPU數(shù)。并行收集線程數(shù)。
- -XX:MaxGCPauseMillis=n:設置并行收集最大暫停時間
- -XX:GCTimeRatio=n:設置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)
- 并發(fā)收集器設置
- -XX:+CMSIncrementalMode:設置為增量模式。適用于單CPU情況。
- -XX:ParallelGCThreads=n:設置并發(fā)收集器年輕代收集方式為并行收集時,使用的CPU數(shù)。并行收集線程數(shù)。
四、調(diào)優(yōu)總結(jié)
- 年輕代大小選擇
- 響應時間優(yōu)先的應用:盡可能設大,直到接近系統(tǒng)的最低響應時間限制(根據(jù)實際情況選擇)。在此種情況下,年輕代收集發(fā)生的頻率也是最小的。同時,減少到達年老代的對象。
- 吞吐量優(yōu)先的應用:盡可能的設置大,可能到達Gbit的程度。因為對響應時間沒有要求,垃圾收集可以并行進行,一般適合8CPU以上的應用。
- 年老代大小選擇
- 響應時間優(yōu)先的應用:年老代使用并發(fā)收集器,所以其大小需要小心設置,一般要考慮并發(fā)會話率和會話持續(xù)時間等一些參數(shù)。如果堆設置小了,可以會造成內(nèi)存碎片、高回收頻率以及應用暫停而使用傳統(tǒng)的標記清除方式;如果堆大了,則需要較長的收集時間。最優(yōu)化的方案,一般需要參考以下數(shù)據(jù)獲得:
- 并發(fā)垃圾收集信息
- 持久代并發(fā)收集次數(shù)
- 傳統(tǒng)GC信息
- 花在年輕代和年老代回收上的時間比例
減少年輕代和年老代花費的時間,一般會提高應用的效率
- 吞吐量優(yōu)先的應用:一般吞吐量優(yōu)先的應用都有一個很大的年輕代和一個較小的年老代。原因是,這樣可以盡可能回收掉大部分短期對象,減少中期的對象,而年老代盡存放長期存活對象。
- 較小堆引起的碎片問題
因為年老代的并發(fā)收集器使用標記、清除算法,所以不會對堆進行壓縮。當收集器回收時,他會把相鄰的空間進行合并,這樣可以分配給較大的對象。但是,當堆空間較小時,運行一段時間以后,就會出現(xiàn)“碎片”,如果并發(fā)收集器找不到足夠的空間,那么并發(fā)收集器將會停止,然后使用傳統(tǒng)的標記、清除方式進行回收。如果出現(xiàn)“碎片”,可能需要進行如下配置:
- -XX:+UseCMSCompactAtFullCollection:使用并發(fā)收集器時,開啟對年老代的壓縮。
- -XX:CMSFullGCsBeforeCompaction=0:上面配置開啟的情況下,這里設置多少次Full GC后,對年老代進行壓縮
數(shù)據(jù)類型
Java虛擬機中,數(shù)據(jù)類型可以分為兩類:基本類型和引用類型。基本類型的變量保存原始值,即:他代表的值就是數(shù)值本身;而引用類型的變量保存引用值。“引用值”代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的地址的位置。
基本類型包括:byte,short,int,long,char,float,double,Boolean,returnAddress
引用類型包括:類類型,接口類型和數(shù)組。
堆與棧
堆和棧是程序運行的關鍵,很有必要把他們的關系說清楚。

棧是運行時的單位,而堆是存儲的單位。
棧解決程序的運行問題,即程序如何執(zhí)行,或者說如何處理數(shù)據(jù);堆解決的是數(shù)據(jù)存儲的問題,即數(shù)據(jù)怎么放、放在哪兒。
在Java中一個線程就會相應有一個線程棧與之對應,這點很容易理解,因為不同的線程執(zhí)行邏輯有所不同,因此需要一個獨立的線程棧。而堆則是所有線程共享的。棧因為是運行單位,因此里面存儲的信息都是跟當前線程(或程序)相關信息的。包括局部變量、程序運行狀態(tài)、方法返回值等等;而堆只負責存儲對象信息。
為什么要把堆和棧區(qū)分出來呢?棧中不是也可以存儲數(shù)據(jù)嗎?
第一,從軟件設計的角度看,棧代表了處理邏輯,而堆代表了數(shù)據(jù)。這樣分開,使得處理邏輯更為清晰。分而治之的思想。這種隔離、模塊化的思想在軟件設計的方方面面都有體現(xiàn)。
第二,堆與棧的分離,使得堆中的內(nèi)容可以被多個棧共享(也可以理解為多個線程訪問同一個對象)。這種共享的收益是很多的。一方面這種共享提供了一種有效的數(shù)據(jù)交互方式(如:共享內(nèi)存),另一方面,堆中的共享常量和緩存可以被所有棧訪問,節(jié)省了空間。
第三,棧因為運行時的需要,比如保存系統(tǒng)運行的上下文,需要進行地址段的劃分。由于棧只能向上增長,因此就會限制住棧存儲內(nèi)容的能力。而堆不同,堆中的對象是可以根據(jù)需要動態(tài)增長的,因此棧和堆的拆分,使得動態(tài)增長成為可能,相應棧中只需記錄堆中的一個地址即可。
第四,面向?qū)ο缶褪嵌押蜅5耐昝澜Y(jié)合。其實,面向?qū)ο蠓绞降某绦蚺c以前結(jié)構(gòu)化的程序在執(zhí)行上沒有任何區(qū)別。但是,面向?qū)ο蟮囊耄沟脤Υ龁栴}的思考方式發(fā)生了改變,而更接近于自然方式的思考。當我們把對象拆開,你會發(fā)現(xiàn),對象的屬性其實就是數(shù)據(jù),存放在堆中;而對象的行為(方法),就是運行邏輯,放在棧中。我們在編寫對象的時候,其實即編寫了數(shù)據(jù)結(jié)構(gòu),也編寫的處理數(shù)據(jù)的邏輯。不得不承認,面向?qū)ο蟮脑O計,確實很美。
在Java中,Main函數(shù)就是棧的起始點,也是程序的起始點。
程序要運行總是有一個起點的。同C語言一樣,java中的Main就是那個起點。無論什么java程序,找到main就找到了程序執(zhí)行的入口:)
堆中存什么?棧中存什么?
堆中存的是對象。棧中存的是基本數(shù)據(jù)類型和堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動態(tài)變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處:))。
為什么不把基本類型放堆中呢?因為其占用的空間一般是1~8個字節(jié)——需要空間比較少,而且因為是基本類型,所以不會出現(xiàn)動態(tài)增長的情況——長度固定,因此棧中存儲就夠了,如果把他存在堆中是沒有什么意義的(還會浪費空間,后面說明)。可以這么說,基本類型和對象的引用都是存放在棧中,而且都是幾個字節(jié)的一個數(shù),因此在程序運行時,他們的處理方式是統(tǒng)一的。但是基本類型、對象引用和對象本身就有所區(qū)別了,因為一個是棧中的數(shù)據(jù)一個是堆中的數(shù)據(jù)。最常見的一個問題就是,Java中參數(shù)傳遞時的問題。
Java中的參數(shù)傳遞時傳值呢?還是傳引用?
要說明這個問題,先要明確兩點:
1. 不要試圖與C進行類比,Java中沒有指針的概念
2. 程序運行永遠都是在棧中進行的,因而參數(shù)傳遞時,只存在傳遞基本類型和對象引用的問題。不會直接傳對象本身。
明確以上兩點后。Java在方法調(diào)用傳遞參數(shù)時,因為沒有指針,所以它都是進行傳值調(diào)用(這點可以參考C的傳值調(diào)用)。因此,很多書里面都說Java是進行傳值調(diào)用,這點沒有問題,而且也簡化的C中復雜性。
但是傳引用的錯覺是如何造成的呢?在運行棧中,基本類型和引用的處理是一樣的,都是傳值,所以,如果是傳引用的方法調(diào)用,也同時可以理解為“傳引用值”的傳值調(diào)用,即引用的處理跟基本類型是完全一樣的。但是當進入被調(diào)用方法時,被傳遞的這個引用的值,被程序解釋(或者查找)到堆中的對象,這個時候才對應到真正的對象。如果此時進行修改,修改的是引用對應的對象,而不是引用本身,即:修改的是堆中的數(shù)據(jù)。所以這個修改是可以保持的了。
對象,從某種意義上說,是由基本類型組成的。可以把一個對象看作為一棵樹,對象的屬性如果還是對象,則還是一顆樹(即非葉子節(jié)點),基本類型則為樹的葉子節(jié)點。程序參數(shù)傳遞時,被傳遞的值本身都是不能進行修改的,但是,如果這個值是一個非葉子節(jié)點(即一個對象引用),則可以修改這個節(jié)點下面的所有內(nèi)容。
堆和棧中,棧是程序運行最根本的東西。程序運行可以沒有堆,但是不能沒有棧。而堆是為棧進行數(shù)據(jù)存儲服務,說白了堆就是一塊共享的內(nèi)存。不過,正是因為堆和棧的分離的思想,才使得Java的垃圾回收成為可能。
Java中,棧的大小通過-Xss來設置,當棧中存儲數(shù)據(jù)比較多時,需要適當調(diào)大這個值,否則會出現(xiàn)java.lang.StackOverflowError異常。常見的出現(xiàn)這個異常的是無法返回的遞歸,因為此時棧中保存的信息都是方法返回的記錄點。
1.對查詢進行優(yōu)化,應盡量避免全表掃描,首先應考慮在 where 及 order by 涉及的列上建立索引。
2.應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
select id from t where num is null
可以在num上設置默認值0,確保表中num列沒有null值,然后這樣查詢:
select id from t where num=0
3.應盡量避免在 where 子句中使用!=或<>操作符,否則將引擎放棄使用索引而進行全表掃描。
4.應盡量避免在 where 子句中使用 or 來連接條件,否則將導致引擎放棄使用索引而進行全表掃描,如:
select id from t where num=10 or num=20
可以這樣查詢:
select id from t where num=10
union all
select id from t where num=20
5.in 和 not in 也要慎用,否則會導致全表掃描,如:
select id from t where num in(1,2,3)
對于連續(xù)的數(shù)值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
6.下面的查詢也將導致全表掃描:
select id from t where name like '%abc%'
若要提高效率,可以考慮全文檢索。
7.如果在 where 子句中使用參數(shù),也會導致全表掃描。因為SQL只有在運行時才會解析局部變量,但優(yōu)化程序不能將訪問計劃的選擇推遲到運行時;它必須在編譯時進行選擇。然而,如果在編譯時建立訪問計劃,變量的值還是未知的,因而無法作為索引選擇的輸入項。如下面語句將進行全表掃描:
select id from t where num=@num
可以改為強制查詢使用索引:
select id from t with(index(索引名)) where num=@num
8.應盡量避免在 where 子句中對字段進行表達式操作,這將導致引擎放棄使用索引而進行全表掃描。如:
select id from t where num/2=100
應改為:
select id from t where num=100*2
9.應盡量避免在where子句中對字段進行函數(shù)操作,這將導致引擎放棄使用索引而進行全表掃描。如:
select id from t where substring(name,1,3)='abc'--name以abc開頭的id
select id from t where datediff(day,createdate,'2005-11-30')=0--‘2005-11-30’生成的id
應改為:
select id from t where name like 'abc%'
select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'
10.不要在 where 子句中的“=”左邊進行函數(shù)、算術(shù)運算或其他表達式運算,否則系統(tǒng)將可能無法正確使用索引。
11.在使用索引字段作為條件時,如果該索引是復合索引,那么必須使用到該索引中的第一個字段作為條件時才能保證系統(tǒng)使用該索引,否則該索引將不會被使用,并且應盡可能的讓字段順序與索引順序相一致。
12.不要寫一些沒有意義的查詢,如需要生成一個空表結(jié)構(gòu):
select col1,col2 into #t from t where 1=0
這類代碼不會返回任何結(jié)果集,但是會消耗系統(tǒng)資源的,應改成這樣:
create table #t(...)
13.很多時候用 exists 代替 in 是一個好的選擇:
select num from a where num in(select num from b)
用下面的語句替換:
select num from a where exists(select 1 from b where num=a.num)
14.并不是所有索引對查詢都有效,SQL是根據(jù)表中數(shù)據(jù)來進行查詢優(yōu)化的,當索引列有大量數(shù)據(jù)重復時,SQL查詢可能不會去利用索引,如一表中有字段sex,male、female幾乎各一半,那么即使在sex上建了索引也對查詢效率起不了作用。
15.索引并不是越多越好,索引固然可以提高相應的 select 的效率,但同時也降低了 insert 及 update 的效率,因為 insert 或 update 時有可能會重建索引,所以怎樣建索引需要慎重考慮,視具體情況而定。一個表的索引數(shù)最好不要超過6個,若太多則應考慮一些不常使用到的列上建的索引是否有必要。
16.應盡可能的避免更新 clustered 索引數(shù)據(jù)列,因為 clustered 索引數(shù)據(jù)列的順序就是表記錄的物理存儲順序,一旦該列值改變將導致整個表記錄的順序的調(diào)整,會耗費相當大的資源。若應用系統(tǒng)需要頻繁更新 clustered 索引數(shù)據(jù)列,那么需要考慮是否應將該索引建為 clustered 索引。
17.盡量使用數(shù)字型字段,若只含數(shù)值信息的字段盡量不要設計為字符型,這會降低查詢和連接的性能,并會增加存儲開銷。這是因為引擎在處理查詢和連接時會逐個比較字符串中每一個字符,而對于數(shù)字型而言只需要比較一次就夠了。
18.盡可能的使用 varchar/nvarchar 代替 char/nchar ,因為首先變長字段存儲空間小,可以節(jié)省存儲空間,其次對于查詢來說,在一個相對較小的字段內(nèi)搜索效率顯然要高些。
19.任何地方都不要使用 select * from t ,用具體的字段列表代替“*”,不要返回用不到的任何字段。
20.盡量使用表變量來代替臨時表。如果表變量包含大量數(shù)據(jù),請注意索引非常有限(只有主鍵索引)。
21.避免頻繁創(chuàng)建和刪除臨時表,以減少系統(tǒng)表資源的消耗。
22.臨時表并不是不可使用,適當?shù)厥褂盟鼈兛梢允鼓承├谈行В纾斝枰貜鸵么笮捅砘虺S帽碇械哪硞€數(shù)據(jù)集時。但是,對于一次性事件,最好使用導出表。
23.在新建臨時表時,如果一次性插入數(shù)據(jù)量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果數(shù)據(jù)量不大,為了緩和系統(tǒng)表的資源,應先create table,然后insert。
24.如果使用到了臨時表,在存儲過程的最后務必將所有的臨時表顯式刪除,先 truncate table ,然后 drop table ,這樣可以避免系統(tǒng)表的較長時間鎖定。
25.盡量避免使用游標,因為游標的效率較差,如果游標操作的數(shù)據(jù)超過1萬行,那么就應該考慮改寫。
26.使用基于游標的方法或臨時表方法之前,應先尋找基于集的解決方案來解決問題,基于集的方法通常更有效。
27.與臨時表一樣,游標并不是不可使用。對小型數(shù)據(jù)集使用 FAST_FORWARD 游標通常要優(yōu)于其他逐行處理方法,尤其是在必須引用幾個表才能獲得所需的數(shù)據(jù)時。在結(jié)果集中包括“合計”的例程通常要比使用游標執(zhí)行的速度快。如果開發(fā)時間允許,基于游標的方法和基于集的方法都可以嘗試一下,看哪一種方法的效果更好。
28.在所有的存儲過程和觸發(fā)器的開始處設置 SET NOCOUNT ON ,在結(jié)束時設置 SET NOCOUNT OFF 。無需在執(zhí)行存儲過程和觸發(fā)器的每個語句后向客戶端發(fā)送 DONE_IN_PROC 消息。
29.盡量避免大事務操作,提高系統(tǒng)并發(fā)能力。
30.盡量避免向客戶端返回大數(shù)據(jù)量,若數(shù)據(jù)量過大,應該考慮相應需求是否合理。