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

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

哦哦,變成16毫秒了,著實提高了不少效果呢。看來,在處理同步的時候擠時間,是有發展空間的,呵呵。
等等,有人要發話了,你在這磨嘰了半天,還是沒有說什么是雙緩沖啊,對!有了前面的兩種方案,我們再來看看“雙緩沖隊列”。
所謂雙緩沖隊列,自然起碼要有兩個隊列吧,呵呵,在這個例子中,我們可以設計兩個List來存放工廠生產出來的玩具對象。
下面分析一下:
兩個List,一個用來存,一個用來取。有點迷糊?就是有一個listP從工廠那里得到玩具對象,另外一個listT就專門把它得到的玩具對象送去給 Kid類處理。當listT變成空的了以后,再將listP中在這段時間內取到的所有玩具對象放到listT中,好,這完了之后,他們兩個就又各自干各自的去了:listP再去取,listT再去送。這樣是不是就減少了很多次的線程同步呢?至少,在它們交換之前,listP是完全被工廠類線程占有,listT是完全被Kid類線程占有的,不用處理同步。只有在listT放完了,沒得給了,再去跟ListP換過來,這個時候就要處理同步了。
跟實際聯系一下,有兩個搬運工A,B,A在工廠,專門從工廠取玩具;B在小孩子身邊,專門送玩具給小孩玩。當B身上沒有玩具了,自然要回A那里,把A身上的玩具全部拿過來,再來送給小孩玩。在A還有玩具的時候,A和B是在各自的線程里被處理的,即A在拿,B在給。不用擔心同步問題。
這樣以來,處理同步問題的次數是不是大大減少了呢?沒錯,就是這樣的。那么怎么跟代碼結合呢?
我們要設計一個監視線程,監視listP是不是空了,要是空了,把它同步起來,把listT也同步起來,讓他們交換。完了就各自干各自的了。
我們來看看這個監視類:
public class DoubleBufferList {
private List<Object> lP;
private List<Object> lT;
private int gap;
/**
* 構造方法
*
* @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();
}
}
這個類中的線程方法就是用來交換的,目的就是為了減少處理同步的次數。這種方案中,Facotry類和Kid類跟前面單個List的情況差不多,就不再給出了。只是有一點要注意,Facory類中將玩具對象是放到了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個!改一下單阻塞隊列方案的輸出結果,給他們一個標記。再來看看結果:


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


充分說明,使用雙緩沖隊列,比單緩沖阻塞隊列的效果要好,更別說單緩沖隊列了。
總結:
從上面的分析,我們可以得知,在處理線程同步的時候,是要花費我們的時間的,雖然在有些時候,這樣的花費是我們可以接受的,但是在很多情況下,如果我們能注意到這樣的浪費,并且及時地完善我們的程序,這樣可以更大限度地提高我們程序的運行效率。尤其是在大的程序里面,這樣的效果體現得更明顯。而往往越大的系統,對性能的要求也就越高。
android在處理一寫圖片資源的時候,會進行一些類型的轉換,現在有空整理一下:
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提供的一個容器組件,利用它可以輕松地實現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創建出來以后,必須先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規則引擎是一種嵌入在Java程序中的組件,它的任務是把當前提交給引擎的Java數據對象與加載在引擎中的業務規則進行測試和比對,激活那些符合當前數據狀態下的業務規則,根據業務規則中聲明的執行邏輯,觸發應用程序中對應的操作。
引言
目前,Java社區推動并發展了一種引人注目的新技術——Java規則引擎(Rule Engine)。利用它就可以在應用系統中分離商業決策者的商業決策邏輯和應用開發者的技術決策,并把這些商業決策放在中心數據庫或其他統一的地方,讓它們能在運行時可以動態地管理和修改,從而為企業保持靈活性和競爭力提供有效的技術支持。
規則引擎的原理
1、基于規則的專家系統(RBES)簡介
Java規則引擎起源于基于規則的專家系統,而基于規則的專家系統又是專家系統的其中一個分支。專家系統屬于人工智能的范疇,它模仿人類的推理方式,使用試探性的方法進行推理,并使用人類能理解的術語解釋和證明它的推理結論。為了更深入地了解Java規則引擎,下面簡要地介紹基于規則的專家系統。RBES包括三部分:Rule Base(knowledge base)、Working Memory(fact base)和Inference Engine。它們的結構如下系統所示:
圖1 基于規則的專家系統構成
如圖1所示,推理引擎包括三部分:模式匹配器(Pattern Matcher)、議程(Agenda)和執行引擎(Execution Engine)。推理引擎通過決定哪些規則滿足事實或目標,并授予規則優先級,滿足事實或目標的規則被加入議程。模式匹配器決定選擇執行哪個規則,何時執行規則;議程管理模式匹配器挑選出來的規則的執行次序;執行引擎負責執行規則和其他動作。
和人類的思維相對應,推理引擎存在兩者推理方式:演繹法(Forward-Chaining)和歸納法(Backward-Chaining)。演繹法從一個初始的事實出發,不斷地應用規則得出結論(或執行指定的動作)。而歸納法則是根據假設,不斷地尋找符合假設的事實。Rete算法是目前效率最高的一個Forward-Chaining推理算法,許多Java規則引擎都是基于Rete算法來進行推理計算的。
推理引擎的推理步驟如下:
(1)將初始數據(fact)輸入Working Memory。
(2)使用Pattern Matcher比較規則庫(rule base)中的規則(rule)和數據(fact)。
(3)如果執行規則存在沖突(conflict),即同時激活了多個規則,將沖突的規則放入沖突集合。
(4)解決沖突,將激活的規則按順序放入Agenda。
(5)使用執行引擎執行Agenda中的規則。重復步驟2至5,直到執行完畢所有Agenda中的規則。
上述即是規則引擎的原始架構,Java規則引擎就是從這一原始架構演變而來的。
2、規則引擎相關構件
規則引擎是一種根據規則中包含的指定過濾條件,判斷其能否匹配運行時刻的實時條件來執行規則中所規定的動作的引擎。與規則引擎相關的有四個基本概念,為更好地理解規則引擎的工作原理,下面將對這些概念進行逐一介紹。
1)信息元(Information Unit)
信息元是規則引擎的基本建筑塊,它是一個包含了特定事件的所有信息的對象。這些信息包括:消息、產生事件的應用程序標識、事件產生事件、信息元類型、相關規則集、通用方法、通用屬性以及一些系統相關信息等等。
2)信息服務(Information Services)
信息服務產生信息元對象。每個信息服務產生它自己類型相對應的信息元對象。即特定信息服務根據信息元所產生每個信息元對象有相同的格式,但可以有不同的屬性和規則集。需要注意的是,在一臺機器上可以運行許多不同的信息服務,還可以運行同一信息服務的不同實例。但無論如何,每個信息服務只產生它自己類型相對應的信息元。
3)規則集(Rule Set)
顧名思義,規則集就是許多規則的集合。每條規則包含一個條件過濾器和多個動作。一個條件過濾器可以包含多個過濾條件。條件過濾器是多個布爾表達式的組合,其組合結果仍然是一個布爾類型的。在程序運行時,動作將會在條件過濾器值為真的情況下執行。除了一般的執行動作,還有三類比較特別的動作,它們分別是:放棄動作(Discard Action)、包含動作(Include Action)和使信息元對象內容持久化的動作。前兩種動作類型的區別將在2.3規則引擎工作機制小節介紹。
4)隊列管理器(Queue Manager)
隊列管理器用來管理來自不同信息服務的信息元對象的隊列。
下面將研究規則引擎的這些相關構件是如何協同工作的。
如圖2所示,處理過程分為四個階段進行:信息服務接受事件并將其轉化為信息元,然后這些信息元被傳給隊列管理器,最后規則引擎接收這些信息元并應用它們自身攜帶的規則加以執行,直到隊列管理器中不再有信息元。
圖2 處理過程協作圖
3、規則引擎的工作機制
下面專門研究規則引擎的內部處理過程。如圖3所示,規則引擎從隊列管理器中依次接收信息元,然后依規則的定義順序檢查信息元所帶規則集中的規則。如圖所示,規則引擎檢查第一個規則并對其條件過濾器求值,如果值為假,所有與此規則相關的動作皆被忽略并繼續執行下一條規則。如果第二條規則的過濾器值為真,所有與此規則相關的動作皆依定義順序執行,執行完畢繼續下一條規則。該信息元中的所有規則執行完畢后,信息元將被銷毀,然后從隊列管理器接收下一個信息元。在這個過程中并未考慮兩個特殊動作:放棄動作(Discard Action)和包含動作(Include Action)。放棄動作如果被執行,將會跳過其所在信息元中接下來的所有規則,并銷毀所在信息元,規則引擎繼續接收隊列管理器中的下一個信息元。包含動作其實就是動作中包含其它現存規則集的動作。包含動作如果被執行,規則引擎將暫停并進入被包含的規則集,執行完畢后,規則引擎還會返回原來暫停的地方繼續執行。這一過程將遞歸進行。
圖3 規則引擎工作機制
Java規則引擎的工作機制與上述規則引擎機制十分類似,只不過對上述概念進行了重新包裝組合。Java規則引擎對提交給引擎的Java數據對象進行檢索,根據這些對象的當前屬性值和它們之間的關系,從加載到引擎的規則集中發現符合條件的規則,創建這些規則的執行實例。這些實例將在引擎接到執行指令時、依照某種優先序依次執行。一般來講,Java規則引擎內部由下面幾個部分構成:工作內存(Working Memory)即工作區,用于存放被引擎引用的數據對象集合;規則執行隊列,用于存放被激活的規則執行實例;靜態規則區,用于存放所有被加載的業務規則,這些規則將按照某種數據結構組織,當工作區中的數據發生改變后,引擎需要迅速根據工作區中的對象現狀,調整規則執行隊列中的規則執行實例。Java規則引擎的結構示意圖如圖4所示。
圖4 Java規則引擎工作機制
當引擎執行時,會根據規則執行隊列中的優先順序逐條執行規則執行實例,由于規則的執行部分可能會改變工作區的數據對象,從而會使隊列中的某些規則執行實例因為條件改變而失效,必須從隊列中撤銷,也可能會激活原來不滿足條件的規則,生成新的規則執行實例進入隊列。于是就產生了一種“動態”的規則執行鏈,形成規則的推理機制。這種規則的“鏈式”反應完全是由工作區中的數據驅動的。
任何一個規則引擎都需要很好地解決規則的推理機制和規則條件匹配的效率問題。規則條件匹配的效率決定了引擎的性能,引擎需要迅速測試工作區中的數據對象,從加載的規則集中發現符合條件的規則,生成規則執行實例。1982年美國卡耐基·梅隆大學的Charles L. Forgy發明了一種叫Rete算法,很好地解決了這方面的問題。目前世界頂尖的商用業務規則引擎產品基本上都使用Rete算法。
Java規則引擎API——JSR-94
為了使規則引擎技術標準化,Java社區制定了Java規則引擎API(JSR94)規范。它為Java平臺訪問規則引擎定義了一些簡單的API。
Java規則引擎API在javax.rules包中定義,是訪問規則引擎的標準企業級API。Java規則引擎API允許客戶程序使用統一的方式和不同廠商的規則引擎產品交互,就如同使用JDBC編寫獨立于廠商訪問不同的數據庫產品一樣。Java規則引擎API包括創建和管理規則集合的機制,在工作區中添加,刪除和修改對象的機制,以及初始化,重置和執行規則引擎的機制。
1、Java規則引擎API體系結構
Java規則引擎API主要由兩大類API組成: 規則管理API(The Rules Administrator API)和運行時客戶API(The Runtime Client API)。
1)規則管理API
規則管理API在javax.rules.admin中定義,包含裝載規則以及與規則對應的動作(執行集 execution sets)以及實例化規則引擎。規則可以從外部資源中裝載,比如URI,Input streams, XML streams和readers等等。同時規則管理API還提供了注冊和取消注冊執行集以及對執行集進行維護的機制。使用admin包定義規則有助于對客戶訪問運行規則進行控制管理,它通過在執行集上定義許可權使得未經授權的用戶無法訪問受控規則。
規則管理API使用類RuleServiceProvider來獲得規則管理器(RuleAdministrator)接口的實例。該接口提供方法注冊和取消注冊執行集。規則管理器提供了本地和遠程的RuleExecutionSetProvider,它負責創建規則執行集(RuleExecutionSet)。規則執行集可以從如XML streams, binary streams等來源中創建。這些數據來源及其內容經匯集和序列化后傳送到遠程的運行規則引擎的服務器上。在大多數應用程序中,遠程規則引擎或遠程規則數據來源的情況并不多。為了避免這些情況中的網絡開銷,API規定了可以從運行在同一JVM中規則庫中讀取數據的本地RuleExecutionSetProvider。規則執行集接口除了擁有能夠獲得有關規則執行集的方法,還有能夠檢索在規則執行集中定義的所有規則對象。這使得客戶能夠知道規則集中的規則對象并且按照自己需要來使用它們。
2)運行時客戶API
運行時API在javax.rules包中定義,為規則引擎用戶運行規則獲得結果提供了類和方法。運行時客戶只能訪問那些使用規則管理API注冊過的規則,運行時API幫助用戶獲得規則會話,并在這個會話中執行規則。
運行時API提供了對廠商規則引擎API的訪問方法,這類似于JDBC。類RuleServiceProvider提供了對具體規則引擎實現的運行時和管理API的訪問,規則引擎廠商通過該類將其規則引擎實現提供給客戶,并獲得RuleServiceProvider唯一標識規則引擎的URL。此URL的標準用法是使用類似于“com.mycompany.myrulesengine.rules.RuleServiceProvider”這樣的Internet域名空間,這保證了訪問URL的唯一性。類RuleServiceProvider內部實現了規則管理和運行時訪問所需的接口。所有的RuleServiceProvider要想被客戶所訪問都必須用RuleServiceProviderManager進行注冊,注冊方式類似于JDBC API的DriverManager和Driver。
運行時接口是運行時API的關鍵部分。運行時接口提供了用于創建規則會話(RuleSession)的方法,規則會話是用來運行規則的。運行時API同時也提供了訪問在service provider注冊過的所有規則執行集(RuleExecutionSets)。規則會話接口定義了客戶使用的會話的類型,客戶根據自己運行規則的方式可以選擇使用有狀態會話或者無狀態會話。無狀態會話的工作方式就像一個無狀態會話bean。客戶可以發送單個輸入對象或一列對象來獲得輸出對象。當客戶需要一個與規則引擎間的專用會話時,有狀態會話就很有用。輸入的對象通過addObject() 方法可以加入到會話當中。同一個會話當中可以加入多個對象。對話中已有對象可以通過使用updateObject()方法得到更新。只要客戶與規則引擎間的會話依然存在,會話中的對象就不會丟失。
RuleExecutionSetMetaData接口提供給客戶讓其查找規則執行集的元數據(metadata)。元數據通過規則會話接口(RuleSession Interface)提供給用戶。
2、Java規則引擎API安全問題
規則引擎API將管理API和運行時API加以分開,從而為這些包提供了較好粒度的安全控制。規則引擎API并沒有提供明顯的安全機制,它可以和J2EE規范中定義的標準安全API聯合使用。安全可以由以下機制提供,如Java 認證和授權服務 (JAAS),Java加密擴展(JCE),Java安全套接字擴展(JSSE),或者其它定制的安全API。使用JAAS可以定義規則執行集的許可權限,從而只有授權用戶才能訪問。
3、異常與日志
規則引擎API定義了javax.rules.RuleException作為規則引擎異常層次的根類。所有其它異常都繼承于這個根類。規則引擎中定義的異常都是受控制的異常(checked exceptions),所以捕獲異常的任務就交給了規則引擎。規則引擎API沒有提供明確的日志機制,但是它建議將Java Logging API用于規則引擎API。
JSR 94 為規則引擎提供了公用標準API,僅僅為實現規則管理API和運行時API提供了指導規范,并沒有提供規則和動作該如何定義以及該用什么語言定義規則,也沒有為規則引擎如何讀和評價規則提供技術性指導。
結束語
規則引擎技術為管理多變的業務邏輯提供了一種解決方案。規則引擎既可以管理應用層的業務邏輯又可以使表示層的頁面流程可訂制。這就給軟件架構師設計大型信息系統提供了一項新的選擇。而Java規則引擎在Java社區制定標準規范以后必將獲得更大發展。
Drools規則引擎初學入門實例HelloWorld
(1)下載eclipse(www.eclipse.org),如果是一般的java開發,下載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的工作臺,之后就可通過這個按鈕創建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",建立規則(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
注意:文件要放在相應的包里,然后編譯—執行,當時出現了錯誤,查找資料,還需要加載包,包括:
<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(不同版本,包名會稍有不同)。
重新運行,應該就不會有錯了。執行結果如下:
Hello World
Goodbye cruel world
game over now!
Oracle JOB 間隔時間詳解
INTERVAL參數設置:
每天運行一次 '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 中最大堆大小有三方面限制:相關操作系統的數據模型(32-bt還是64-bit)限制;系統的可用虛擬內存限制;系統的可用物理內存限制。32位系統下,一般限制在1.5G~2G;64為操作系統對內存無限制。我在Windows Server 2003 系統,3.5G物理內存,JDK5.0下測試,最大可設置為1478m。
典型設置:
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
-Xmx3550m:設置JVM最大可用內存為3550M。
-Xms3550m:設置JVM促使內存為3550m。此值可以設置與-Xmx相同,以避免每次垃圾回收完成后JVM重新分配內存。
-Xmn2g:設置年輕代大小為2G。整個JVM內存大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小為64m,所以增大年輕代后,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的3/8。
-Xss128k:設置每個線程的堆棧大小。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。
- java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
-XX:NewRatio=4:設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設置為4,則年輕代與年老代所占比值為1:4,年輕代占整個堆棧的1/5
-XX:SurvivorRatio=4:設置年輕代中Eden區與Survivor區的大小比值。設置為4,則兩個Survivor區與一個Eden區的比值為2:4,一個Survivor區占整個年輕代的1/6
-XX:MaxPermSize=16m:設置持久代大小為16m。
-XX:MaxTenuringThreshold=0:設置垃圾最大年齡。如果設置為0的話,則年輕代對象不經過Survivor區,直接進入年老代。對于年老代比較多的應用,可以提高效率。如果將此值設置為一個較大值,則年輕代對象會在Survivor區進行多次復制,這樣可以增加對象再年輕代的存活時間,增加在年輕代即被回收的概論。
- 回收器選擇
JVM給了三種選擇:串行收集器、并行收集器、并發收集器,但是串行收集器只適用于小數據量的情況,所以這里的選擇主要針對并行收集器和并發收集器。默認情況下,JDK5.0以前都是使用串行收集器,如果想使用其他收集器需要在啟動時加入相應參數。JDK5.0以后,JVM會根據當前系統配置進行判斷。
- 吞吐量優先的并行收集器
如上文所述,并行收集器主要以到達一定的吞吐量為目標,適用于科學技術和后臺處理等。
典型配置:
- java -Xmx3800m -Xms3800m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:ParallelGCThreads=20
-XX:+UseParallelGC:選擇垃圾收集器為并行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用并發收集,而年老代仍舊使用串行收集。
-XX:ParallelGCThreads=20:配置并行收集器的線程數,即:同時多少個線程一起進行垃圾回收。此值最好配置與處理器數目相等。
- 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會自動調整年輕代大小,以滿足此值。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseParallelGC -XX:MaxGCPauseMillis=100 -XX:+UseAdaptiveSizePolicy
-XX:+UseAdaptiveSizePolicy:設置此選項后,并行收集器會自動選擇年輕代區大小和相應的Survivor區比例,以達到目標系統規定的最低相應時間或者收集頻率等,此值建議使用并行收集器時,一直打開。
- 響應時間優先的并發收集器
如上文所述,并發收集器主要是保證系統的響應時間,減少垃圾收集時的停頓時間。適用于應用服務器、電信領域等。
典型配置:
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:ParallelGCThreads=20 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC:設置年老代為并發收集。測試中配置這個以后,-XX:NewRatio=4的配置失效了,原因不明。所以,此時年輕代大小最好用-Xmn設置。
-XX:+UseParNewGC:設置年輕代為并行收集??膳cCMS收集同時使用。JDK5.0以上,JVM會根據系統配置自行設置,所以無需再設置此值。
- java -Xmx3550m -Xms3550m -Xmn2g -Xss128k -XX:+UseConcMarkSweepGC -XX:CMSFullGCsBeforeCompaction=5 -XX:+UseCMSCompactAtFullCollection
-XX:CMSFullGCsBeforeCompaction:由于并發收集器不對內存空間進行壓縮、整理,所以運行一段時間以后會產生“碎片”,使得運行效率降低。此值設置運行多少次GC以后對內存空間進行壓縮、整理。
-XX:+UseCMSCompactAtFullCollection:打開對年老代的壓縮。可能會影響性能,但是可以消除碎片
- 輔助信息
JVM提供了大量命令行參數,打印信息,供調試使用。主要有以下一些:
- -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:打印每次垃圾回收前,程序未中斷的執行時間??膳c上面混合使用
輸出形式: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區與兩個Survivor區的比值。注意Survivor區有兩個。如:3,表示Eden:Survivor=3:2,一個Survivor區占整個年輕代的1/5
- -XX:MaxPermSize=n:設置持久代大小
- 收集器設置
- -XX:+UseSerialGC:設置串行收集器
- -XX:+UseParallelGC:設置并行收集器
- -XX:+UseParalledlOldGC:設置并行年老代收集器
- -XX:+UseConcMarkSweepGC:設置并發收集器
- 垃圾回收統計信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintGCTimeStamps
- -Xloggc:filename
- 并行收集器設置
- -XX:ParallelGCThreads=n:設置并行收集器收集時使用的CPU數。并行收集線程數。
- -XX:MaxGCPauseMillis=n:設置并行收集最大暫停時間
- -XX:GCTimeRatio=n:設置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)
- 并發收集器設置
- -XX:+CMSIncrementalMode:設置為增量模式。適用于單CPU情況。
- -XX:ParallelGCThreads=n:設置并發收集器年輕代收集方式為并行收集時,使用的CPU數。并行收集線程數。
四、調優總結
- 年輕代大小選擇
- 響應時間優先的應用:盡可能設大,直到接近系統的最低響應時間限制(根據實際情況選擇)。在此種情況下,年輕代收集發生的頻率也是最小的。同時,減少到達年老代的對象。
- 吞吐量優先的應用:盡可能的設置大,可能到達Gbit的程度。因為對響應時間沒有要求,垃圾收集可以并行進行,一般適合8CPU以上的應用。
- 年老代大小選擇
- 響應時間優先的應用:年老代使用并發收集器,所以其大小需要小心設置,一般要考慮并發會話率和會話持續時間等一些參數。如果堆設置小了,可以會造成內存碎片、高回收頻率以及應用暫停而使用傳統的標記清除方式;如果堆大了,則需要較長的收集時間。最優化的方案,一般需要參考以下數據獲得:
- 并發垃圾收集信息
- 持久代并發收集次數
- 傳統GC信息
- 花在年輕代和年老代回收上的時間比例
減少年輕代和年老代花費的時間,一般會提高應用的效率
- 吞吐量優先的應用:一般吞吐量優先的應用都有一個很大的年輕代和一個較小的年老代。原因是,這樣可以盡可能回收掉大部分短期對象,減少中期的對象,而年老代盡存放長期存活對象。
- 較小堆引起的碎片問題
因為年老代的并發收集器使用標記、清除算法,所以不會對堆進行壓縮。當收集器回收時,他會把相鄰的空間進行合并,這樣可以分配給較大的對象。但是,當堆空間較小時,運行一段時間以后,就會出現“碎片”,如果并發收集器找不到足夠的空間,那么并發收集器將會停止,然后使用傳統的標記、清除方式進行回收。如果出現“碎片”,可能需要進行如下配置:
- -XX:+UseCMSCompactAtFullCollection:使用并發收集器時,開啟對年老代的壓縮。
- -XX:CMSFullGCsBeforeCompaction=0:上面配置開啟的情況下,這里設置多少次Full GC后,對年老代進行壓縮
數據類型
Java虛擬機中,數據類型可以分為兩類:基本類型和引用類型?;绢愋偷淖兞勘4嬖贾?,即:他代表的值就是數值本身;而引用類型的變量保存引用值。“引用值”代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的地址的位置。
基本類型包括:byte,short,int,long,char,float,double,Boolean,returnAddress
引用類型包括:類類型,接口類型和數組。
堆與棧
堆和棧是程序運行的關鍵,很有必要把他們的關系說清楚。

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