|
2012年11月14日
轉自:http://blog.csdn.net/jeffreynicole/article/details/46953059 一個性能較好的web服務器jvm參數配置:
- -server //服務器模式
- -Xmx2g //JVM最大允許分配的堆內存,按需分配
- -Xms2g //JVM初始分配的堆內存,一般和Xmx配置成一樣以避免每次gc后JVM重新分配內存。
- -Xmn256m //年輕代內存大小,整個JVM內存=年輕代 + 年老代 + 持久代
- -XX:PermSize=128m //持久代內存大小
- -Xss256k //設置每個線程的堆棧大小
- -XX:+DisableExplicitGC //忽略手動調用GC, System.gc()的調用就會變成一個空調用,完全不觸發GC
- -XX:+UseConcMarkSweepGC //并發標記清除(CMS)收集器
- -XX:+CMSParallelRemarkEnabled //降低標記停頓
- -XX:+UseCMSCompactAtFullCollection //在FULL GC的時候對年老代的壓縮
- -XX:LargePageSizeInBytes=128m //內存頁的大小
- -XX:+UseFastAccessorMethods //原始類型的快速優化
- -XX:+UseCMSInitiatingOccupancyOnly //使用手動定義初始化定義開始CMS收集
- -XX:CMSInitiatingOccupancyFraction=70 //使用cms作為垃圾回收使用70%后開始CMS收集
說明:-Xmn和-Xmx之比大概是1:9,如果把新生代內存設置得太大會導致young gc時間較長 一個好的Web系統應該是每次http請求申請內存都能在young gc回收掉,full gc永不發生,當然這是最理想的情況 xmn的值應該是保證夠用(夠http并發請求之用)的前提下設置得盡量小 web服務器和游戲服務器的配置思路不太一樣,最重要的區別是對游戲服務器的xmn即年輕代設置比較大,和Xmx大概1:3的關系,因為游戲服務器一般是長連接,在保持一定的并發量后需要較大的年輕代堆內存,如果設置得大小了會經常引發young gc

由上圖可以看出jvm堆內存的分類情況,JVM內存被分成多個獨立的部分。 廣泛地說,JVM堆內存被分為兩部分——年輕代(Young Generation)和老年代(Old Generation)。
年輕代是所有新對象產生的地方。當年輕代內存空間被用完時,就會觸發垃圾回收。這個垃圾回收叫做Minor GC。年輕代被分為3個部分——Enden區和兩個Survivor區。年輕代空間的要點:大多數新建的對象都位于Eden區。當Eden區被對象填滿時,就會執行Minor GC。并把所有存活下來的對象轉移到其中一個survivor區。Minor GC同樣會檢查存活下來的對象,并把它們轉移到另一個survivor區。這樣在一段時間內,總會有一個空的survivor區。經過多次GC周期后,仍然存活下來的對象會被轉移到年老代內存空間。通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的。 年老代內存里包含了長期存活的對象和經過多次Minor GC后依然存活下來的對象。通常會在老年代內存被占滿時進行垃圾回收。老年代的垃圾收集叫做Major GC。Major GC會花費更多的時間。Stop the World事件所有的垃圾收集都是“Stop the World”事件,因為所有的應用線程都會停下來直到操作完成(所以叫“Stop the World”)。因為年輕代里的對象都是一些臨時(short-lived )對象,執行Minor GC非常快,所以應用不會受到(“Stop the World”)影響。由于Major GC會檢查所有存活的對象,因此會花費更長的時間。應該盡量減少Major GC。因為Major GC會在垃圾回收期間讓你的應用反應遲鈍,所以如果你有一個需要快速響應的應用發生多次Major GC,你會看到超時錯誤。垃圾回收時間取決于垃圾回收策略。這就是為什么有必要去監控垃圾收集和對垃圾收集進行調優。從而避免要求快速響應的應用出現超時錯誤。永久代或者“Perm Gen”包含了JVM需要的應用元數據,這些元數據描述了在應用里使用的類和方法。注意,永久代不是Java堆內存的一部分。永久代存放JVM運行時使用的類。永久代同樣包含了Java SE庫的類和方法。永久代的對象在full GC時進行垃圾收集。方法區方法區是永久代空間的一部分,并用來存儲類型信息(運行時常量和靜態變量)和方法代碼和構造函數代碼。內存池如果JVM實現支持,JVM內存管理會為創建內存池,用來為不變對象創建對象池。字符串池就是內存池類型的一個很好的例子。內存池可以屬于堆或者永久代,這取決于JVM內存管理的實現。運行時常量池運行時常量池是每個類常量池的運行時代表。它包含了類的運行時常量和靜態方法。運行時常量池是方法區的一部分。Java棧內存Java棧內存用于運行線程。它們包含了方法里的臨時數據、堆里其它對象引用的特定數據。Java垃圾回收Java垃圾回收會找出沒用的對象,把它從內存中移除并釋放出內存給以后創建的對象使用。Java程序語言中的一個最大優點是自動垃圾回收,不像其他的程序語言那樣需要手動分配和釋放內存,比如C語言。垃圾收集器是一個后臺運行程序。它管理著內存中的所有對象并找出沒被引用的對象。所有的這些未引用的對象都會被刪除,回收它們的空間并分配給其他對象。一個基本的垃圾回收過程涉及三個步驟:標記:這是第一步。在這一步,垃圾收集器會找出哪些對象正在使用和哪些對象不在使用。正常清除:垃圾收集器清會除不在使用的對象,回收它們的空間分配給其他對象。壓縮清除:為了提升性能,壓縮清除會在刪除沒用的對象后,把所有存活的對象移到一起。這樣可以提高分配新對象的效率。簡單標記和清除方法存在兩個問題:效率很低。因為大多數新建對象都會成為“沒用對象”。經過多次垃圾回收周期的對象很有可能在以后的周期也會存活下來。上面簡單清除方法的問題在于Java垃圾收集的分代回收的,而且在堆內存里有年輕代和年老代兩個區域。這里有五種可以在應用里使用的垃圾回收類型。僅需要使用JVM開關就可以在我們的應用里啟用垃圾回收策略。
Serial GC(-XX:+UseSerialGC):Serial GC使用簡單的標記、清除、壓縮方法對年輕代和年老代進行垃圾回收,即Minor GC和Major GC。Serial GC在client模式(客戶端模式)很有用,比如在簡單的獨立應用和CPU配置較低的機器。這個模式對占有內存較少的應用很管用。 Parallel GC(-XX:+UseParallelGC):除了會產生N個線程來進行年輕代的垃圾收集外,Parallel GC和Serial GC幾乎一樣。這里的N是系統CPU的核數。我們可以使用 -XX:ParallelGCThreads=n 這個JVM選項來控制線程數量。并行垃圾收集器也叫throughput收集器。因為它使用了多CPU加快垃圾回收性能。Parallel GC在進行年老代垃圾收集時使用單線程。 Parallel Old GC(-XX:+UseParallelOldGC):和Parallel GC一樣。不同之處,Parallel Old GC在年輕代垃圾收集和年老代垃圾回收時都使用多線程收集。 并發標記清除(CMS)收集器(-XX:+UseConcMarkSweepGC):CMS收集器也被稱為短暫停頓并發收集器。它是對年老代進行垃圾收集的。CMS收集器通過多線程并發進行垃圾回收,盡量減少垃圾收集造成的停頓。CMS收集器對年輕代進行垃圾回收使用的算法和Parallel收集器一樣。這個垃圾收集器適用于不能忍受長時間停頓要求快速響應的應用。可使用 -XX:ParallelCMSThreads=n JVM選項來限制CMS收集器的線程數量。 G1垃圾收集器(-XX:+UseG1GC) G1(Garbage First):垃圾收集器是在Java 7后才可以使用的特性,它的長遠目標時代替CMS收集器。G1收集器是一個并行的、并發的和增量式壓縮短暫停頓的垃圾收集器。G1收集器和其他的收集器運行方式不一樣,不區分年輕代和年老代空間。它把堆空間劃分為多個大小相等的區域。當進行垃圾收集時,它會優先收集存活對象較少的區域,因此叫“Garbage First”。
摘要: class文件簡介及加載 Java編譯器編譯好Java文件之后,產生.class 文件在磁盤中。這種class文件是二進制文件,內容是只有JVM虛擬機能夠識別的機器碼。JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class 文件內的信息,生成對應的 Class對象: &nb... 閱讀全文
package com.qiyi.appstore.util; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.qiyi.appstore.exception.AppStoreException; import com.qiyi.cloud.user.ApiCode; public class XssUtils { private static final Logger logger=LoggerFactory.getLogger(XssUtils.class); public static String getSafeStringXSS(String s){ if (StringUtils.isBlank(s)) { return s; } StringBuilder sb = new StringBuilder(s.length() + 16); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); switch (c) { case '<': sb.append("<"); break; case '>': sb.append(">"); break; case '\'': sb.append("′");// ´"); break; case '′': sb.append("′");// ´"); break; case '\"': sb.append("""); break; case '"': sb.append("""); break; case '&': sb.append("&"); break; case '#': sb.append("#"); break; case '\\': sb.append('¥'); break; case '=': sb.append("="); break; default: sb.append(c); break; } } return sb.toString(); } public static <T> void getXssSaftBean(Class<?> clz,T bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{ String classname = clz.getSimpleName(); logger.info("map target class name is {} .",classname); Field[] fields = clz.getDeclaredFields(); for(Field field : fields){ Class<?> type = field.getType(); if(type.equals(String.class)){ String fieldname = field.getName(); String value = BeanUtils.getProperty(bean, fieldname); if(StringUtils.isNotBlank(value)){ BeanUtils.setProperty(bean, fieldname, getSafeStringXSS(value)); } } } } }
提升tomcat 性能 apr擴展lib 使用apr類庫 可以讓tomcat的性能提升到3到4倍 目前項目中都使用這樣的配置 <Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol" URIEncoding="UTF-8" enableLookups="false" acceptCount="300" connectionTimeout="20000" disableUploadTimeout="true" maxThreads="1000" maxSpareThreads="50" minSpareThreads="25" redirectPort="8443" /> catalia.sh CATALINA_OPTS="$CATALINA_OPTS -Djava.library.path=/usr/local/apr/lib"
摘要: public static boolean acquireLock(String lock) { // 1. 通過SETNX試圖獲取一個lock boolean success = false; Jedis jedis = pool.getResource();... 閱讀全文
對eclipse的默認配置很不爽,黑色字體白色底好刺眼,而且字體習慣用Courier New 改變背景顏色: windows->Preferences->General->Editor->Text Editors 右邊選擇Appearance color options 選Background color 選擇背景顏色 個人比較舒服的豆沙綠色和黑色背景,但黑色背景還要把其他的字體顏色也改了才好看,而且豆沙綠色跟默認的字體顏色搭配的很好。 豆沙綠色(色調:85 飽和度:123 亮度:205 ) 據說這個色調是眼科專家配的, 因其顏色比較柔和,據說閱讀的時候用這種顏色做背景有利于保護眼睛, word底色就許多人設置成豆沙綠色。 xml的字體調整: window--preferences--General--appearance--colors and fonts--Basic-- "Text font " 然后點change,可以設置字體,我喜歡Courier New Java的字體調整: window--preferences--General--appearance--colors and fonts--java
有時候在項目中 會變化路徑 把原有路徑的文件拷到新的路徑下面 再刪除原來不想的路徑再提交一次 這樣以來 原來的路徑確實不存在了 但是拷過來的文件帶有原來路徑的svn信息 這樣以來 在提交的時候 就無法提交 想要文件按照的路徑提交 但始終svn還是再往以前的路徑提交 并提示你路徑不存在 在網上搜了下 如何刪除文件自帶的svn路徑信息 按照下面的方式來操作即可
Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN] @="刪除該目錄下面.svn文件" [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN\command] @="cmd.exe /c \"TITLE Removing SVN Folders in %1 && COLOR 9A && FOR /r \"%1\" %%f IN (.svn) DO RD /s /q \"%%f\" \""
把上面這段文字保存問一個Done.reg文件 然后執行,導入到注冊表 就會在你右鍵一個文件夾的時候多出來一個菜單"刪除該目錄下面.svn文件" 執行該命令即可
在ibatis中不需要關注這些參數 而轉到mybatis后 如果字段值為空 必須設置jdbcType 如 insert into testTable (ID, NAME, DESCRIPTION, IMAGEURL, LINKURL, ISALWAYS, ISDISPLAYINDEX, DISPLAYWEIGHT, STARTTIME, ENDTIME, CREATOR, CREATTIME, MODIFYTIME) values (SEQ_ACTIVITY_TABLE.NEXTVAL, #{name}, #{desc,jdbcType=VARCHAR}, #{imageUrl,jdbcType=VARCHAR}, #{linkUrl,jdbcType=VARCHAR}, #{isAlways,jdbcType=CHAR}, #{isDisplayIndex,jdbcType=CHAR}, #{displayWeight,jdbcType=VARCHAR}, #{startTime,jdbcType=DATE}, #{endTime,jdbcType=DATE}, #{creator,jdbcType=VARCHAR}, sysdate, sysdate ) </insert> 這些設置之多,太煩了,最讓人煩的是 jdbcType = DATE,類型還必須大寫,不能小寫。 如下面的例子,將DATE 改成 Date 。結果讓人很抓狂啊!!! insert into testTable (ID, NAME, DESCRIPTION, IMAGEURL, LINKURL, ISALWAYS, ISDISPLAYINDEX, DISPLAYWEIGHT, STARTTIME, ENDTIME, CREATOR, CREATTIME, MODIFYTIME) values (SEQ_ACTIVITY_TABLE.NEXTVAL, #{name}, #{desc,jdbcType=VARCHAR}, #{imageUrl,jdbcType=VARCHAR}, #{linkUrl,jdbcType=VARCHAR}, #{isAlways,jdbcType=CHAR}, #{isDisplayIndex,jdbcType=CHAR}, #{displayWeight,jdbcType=VARCHAR}, #{startTime,jdbcType=Date}, #{endTime,jdbcType=DATE}, #{creator,jdbcType=VARCHAR}, sysdate, sysdate ) </insert> org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error resolving JdbcType. Cause: java.lang.IllegalArgumentException: No enum const class org.apache.ibatis.type.JdbcType.Date
org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:75)
org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:368) 更坑爹的在后面,上面insert時的時候用#{endTime,jdbcType=DATE},可以將時間插入成功,且可以精確到時分秒 但如果在update語句中也這樣使用,那你得到的只會有日期,這夠坑爹的了吧 ,尼瑪 比起ibatis方便之處差遠了 要想在update語句中 將時間格式化成時分秒 不得不再加一個類型 如下面: startTime = #{startTime,javaType=DATE, jdbcType=VARCHAR}
CSRF 背景與介紹
CSRF(Cross Site Request Forgery, 跨站域請求偽造)是一種網絡的攻擊方式,它在 2007 年曾被列為互聯網 20 大安全隱患之一。其他安全隱患,比如 SQL 腳本注入,跨站域腳本攻擊等在近年來已經逐漸為眾人熟知,很多網站也都針對他們進行了防御。然而,對于大多數人來說,CSRF 卻依然是一個陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在著 CSRF 漏洞,從而被黑客攻擊而使 Gmail 的用戶造成巨大的損失。
CSRF 攻擊實例
CSRF 攻擊可以在受害者毫不知情的情況下以受害者名義偽造請求發送給受攻擊站點,從而在并未授權的情況下執行在權限保護之下的操作。比如說,受害者 Bob 在銀行有一筆存款,通過對銀行的網站發送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款轉到 bob2 的賬號下。通常情況下,該請求發送到網站后,服務器會先驗證該請求是否來自一個合法的 session,并且該 session 的用戶 Bob 已經成功登陸。黑客 Mallory 自己在該銀行也有賬戶,他知道上文中的 URL 可以把錢進行轉帳操作。Mallory 可以自己發送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是這個請求來自 Mallory 而非 Bob,他不能通過安全認證,因此該請求不會起作用。這時,Mallory 想到使用 CSRF 的攻擊方式,他先自己做一個網站,在網站中放入如下代碼: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,并且通過廣告等誘使 Bob 來訪問他的網站。當 Bob 訪問該網站時,上述 url 就會從 Bob 的瀏覽器發向銀行,而這個請求會附帶 Bob 瀏覽器中的 cookie 一起發向銀行服務器。大多數情況下,該請求會失敗,因為他要求 Bob 的認證信息。但是,如果 Bob 當時恰巧剛訪問他的銀行后不久,他的瀏覽器與銀行網站之間的 session 尚未過期,瀏覽器的 cookie 之中含有 Bob 的認證信息。這時,悲劇發生了,這個 url 請求就會得到響應,錢將從 Bob 的賬號轉移到 Mallory 的賬號,而 Bob 當時毫不知情。等以后 Bob 發現賬戶錢少了,即使他去銀行查詢日志,他也只能發現確實有一個來自于他本人的合法請求轉移了資金,沒有任何被攻擊的痕跡。而 Mallory 則可以拿到錢后逍遙法外。
CSRF 攻擊的對象
在討論如何抵御 CSRF 之前,先要明確 CSRF 攻擊的對象,也就是要保護的對象。從以上的例子可知,CSRF 攻擊是黑客借助受害者的 cookie 騙取服務器的信任,但是黑客并不能拿到 cookie,也看不到 cookie 的內容。另外,對于服務器返回的結果,由于瀏覽器同源策略的限制,黑客也無法進行解析。因此,黑客無法從返回的結果中得到任何東西,他所能做的就是給服務器發送請求,以執行請求中所描述的命令,在服務器端直接改變數據的值,而非竊取服務器中的數據。所以,我們要保護的對象是那些可以直接產生數據改變的服務,而對于讀取數據的服務,則不需要進行 CSRF 的保護。比如銀行系統中轉賬的請求會直接改變賬戶的金額,會遭到 CSRF 攻擊,需要保護。而查詢余額是對金額的讀取操作,不會改變數據,CSRF 攻擊無法解析服務器返回的結果,無需保護。
當前防御 CSRF 的幾種策略
在業界目前防御 CSRF 攻擊主要有三種策略:驗證 HTTP Referer 字段;在請求地址中添加 token 并驗證;在 HTTP 頭中自定義屬性并驗證。下面就分別對這三種策略進行詳細介紹。
驗證 HTTP Referer 字段
根據 HTTP 協議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自于同一個網站,比如需要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用戶必須先登陸 bank.example,然后通過點擊頁面上的按鈕來觸發轉賬事件。這時,該轉帳請求的 Referer 值就會是轉賬按鈕所在的頁面的 URL,通常是以 bank.example 域名開頭的地址。而如果黑客要對銀行網站實施 CSRF 攻擊,他只能在他自己的網站構造請求,當用戶通過黑客的網站發送請求到銀行時,該請求的 Referer 是指向黑客自己的網站。因此,要防御 CSRF 攻擊,銀行網站只需要對于每一個轉賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果 Referer 是其他網站的話,則有可能是黑客的 CSRF 攻擊,拒絕該請求。
這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不需要操心 CSRF 的漏洞,只需要在最后給所有安全敏感的請求統一增加一個攔截器來檢查 Referer 的值就可以。特別是對于當前現有的系統,不需要改變當前系統的任何已有代碼和邏輯,沒有風險,非常便捷。
然而,這種方法并非萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,但是每個瀏覽器對于 Referer 的具體實現可能有差別,并不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴于第三方(即瀏覽器)來保障,從理論上來講,這樣并不安全。事實上,對于某些瀏覽器,比如 IE6 或 FF2,目前已經有一些方法可以篡改 Referer 值。如果 bank.example 網站支持 IE6 瀏覽器,黑客完全可以把用戶瀏覽器的 Referer 值設為以 bank.example 域名開頭的地址,這樣就可以通過驗證,從而進行 CSRF 攻擊。
即便是使用最新的瀏覽器,黑客無法篡改 Referer 值,這種方法仍然有問題。因為 Referer 值會記錄下用戶的訪問來源,有些用戶認為這樣會侵犯到他們自己的隱私權,特別是有些組織擔心 Referer 值會把組織內網中的某些信息泄露到外網中。因此,用戶自己可以設置瀏覽器使其在發送請求時不再提供 Referer。當他們正常訪問銀行網站時,網站會因為請求沒有 Referer 值而認為是 CSRF 攻擊,拒絕合法用戶的訪問。
在請求地址中添加 token 并驗證
CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造用戶的請求,該請求中所有的用戶驗證信息都是存在于 cookie 中,因此黑客可以在不知道這些驗證信息的情況下直接利用用戶自己的 cookie 來通過安全驗證。要抵御 CSRF,關鍵在于在請求中放入黑客所不能偽造的信息,并且該信息不存在于 cookie 之中。可以在 HTTP 請求中以參數的形式加入一個隨機產生的 token,并在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。
這種方法要比檢查 Referer 要安全一些,token 可以在用戶登陸后產生并放于 session 之中,然后在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在于如何把 token 以參數的形式加入請求。對于 GET 請求,token 將附在請求地址之后,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對于 POST 請求來說,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,這樣就把 token 以參數的形式加入請求了。但是,在一個網站中,可以接受請求的地方非常多,要對于每一個請求都加上 token 是很麻煩的,并且很容易漏掉,通常使用的方法就是在每次頁面加載時,使用 javascript 遍歷整個 dom 樹,對于 dom 中所有的 a 和 form 標簽后加入 token。這樣可以解決大部分的請求,但是對于在頁面加載之后動態生成的 html 代碼,這種方法就沒有作用,還需要程序員在編碼時手動添加 token。
該方法還有一個缺點是難以保證 token 本身的安全。特別是在一些論壇之類支持用戶自己發表內容的網站,黑客可以在上面發布自己個人網站的地址。由于系統也會在這個地址后面加上 token,黑客可以在自己的網站上得到這個 token,并馬上就可以發動 CSRF 攻擊。為了避免這一點,系統可以在添加 token 的時候增加一個判斷,如果這個鏈接是鏈到自己本站的,就在后面添加 token,如果是通向外網則不加。不過,即使這個 csrftoken 不以參數的形式附加在請求之中,黑客的網站也同樣可以通過 Referer 來得到這個 token 值以發動 CSRF 攻擊。這也是一些用戶喜歡手動關閉瀏覽器 Referer 功能的原因。
在 HTTP 頭中自定義屬性并驗證
這種方法也是使用 token 并進行驗證,和上一種方法不同的是,這里并不是把 token 以參數的形式置于 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性里。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,并把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 泄露到其他網站中去。
然而這種方法的局限性非常大。XMLHttpRequest 請求通常用于 Ajax 方法中對于頁面局部的異步刷新,并非所有的請求都適合用這個類來發起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,后退,刷新,收藏等操作,給用戶帶來不便。另外,對于沒有進行 CSRF 防護的遺留系統來說,要采用這種方法來進行防護,要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。
Java 代碼示例
下文將以 Java 為例,對上述三種方法分別用代碼進行示例。無論使用何種方法,在服務器端的攔截器必不可少,它將負責檢查到來的請求是否符合要求,然后視結果而決定是否繼續請求或者丟棄。在 Java 中,攔截器是由 Filter 來實現的。我們可以編寫一個 Filter,并在 web.xml 中對其進行配置,使其對于訪問所有需要 CSRF 保護的資源的請求進行攔截。
在 filter 中對請求的 Referer 驗證代碼如下 清單 1. 在 Filter 中驗證 Referer
1
2
3
4
5
6
7
8 |
String referer=request.getHeader( "Referer" );
if ((referer!= null ) &&(referer.trim().startsWith(“bank.example”))){
chain.doFilter(request, response);
} else {
request.getRequestDispatcher(“error.jsp”).forward(request,response);
}
|
以上代碼先取得 Referer 值,然后進行判斷,當其非空并以 bank.example 開頭時,則繼續請求,否則的話可能是 CSRF 攻擊,轉到 error.jsp 頁面。
如果要進一步驗證請求中的 token 值,代碼如下
1 |
<em><strong>清單 2 . 在 filter 中驗證請求中的</strong></em> token
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 |
HttpServletRequest req = (HttpServletRequest)request;
HttpSession s = req.getSession();
String sToken = (String)s.getAttribute(“csrftoken”);
if (sToken == null ){
sToken = generateToken();
s.setAttribute(“csrftoken”,sToken);
chain.doFilter(request, response);
} else {
String xhrToken = req.getHeader(“csrftoken”);
String pToken = req.getParameter(“csrftoken”);
if (sToken != null && xhrToken != null && sToken.equals(xhrToken)){
chain.doFilter(request, response);
} else if (sToken != null && pToken != null && sToken.equals(pToken)){
chain.doFilter(request, response);
} else {
request.getRequestDispatcher(“error.jsp”).forward(request,response);
}
}
|
首先判斷 session 中有沒有 csrftoken,如果沒有,則認為是第一次訪問,session 是新建立的,這時生成一個新的 token,放于 session 之中,并繼續執行請求。如果 session 中已經有 csrftoken,則說明用戶已經與服務器之間建立了一個活躍的 session,這時要看這個請求中有沒有同時附帶這個 token,由于請求可能來自于常規的訪問或是 XMLHttpRequest 異步訪問,我們分別嘗試從請求中獲取 csrftoken 參數以及從 HTTP 頭中獲取 csrftoken 自定義屬性并與 session 中的值進行比較,只要有一個地方帶有有效 token,就判定請求合法,可以繼續執行,否則就轉到錯誤頁面。生成 token 有很多種方法,任何的隨機算法都可以使用,Java 的 UUID 類也是一個不錯的選擇。
除了在服務器端利用 filter 來驗證 token 的值以外,我們還需要在客戶端給每個請求附加上這個 token,這是利用 js 來給 html 中的鏈接和表單請求地址附加 csrftoken 代碼,其中已定義 token 為全局變量,其值可以從 session 中得到。
1 |
<em><strong>清單 3 . 在客戶端對于請求附加</strong> </em>token
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62 |
function appendToken(){
updateForms();
updateTags();
}
function updateForms() {
var forms = document.getElementsByTagName( 'form' );
for (i= 0 ; i<forms.length; i++) {
var url = forms[i].action;
if (url == null || url == "" ) continue ;
var e = document.createElement( "input" );
e.name = "csrftoken" ;
e.value = token;
e.type= "hidden" ;
forms[i].appendChild(e);
}
}
function updateTags() {
var all = document.getElementsByTagName( 'a' );
var len = all.length;
for (var i= 0 ; i<len; i++) {
var e = all[i];
updateTag(e, 'href' , token);
}
}
function updateTag(element, attr, token) {
var location = element.getAttribute(attr);
if (location != null && location != '' '' ) {
var fragmentIndex = location.indexOf( '#' );
var fragment = null ;
if (fragmentIndex != - 1 ){
fragment = location.substring(fragmentIndex);
location = location.substring( 0 ,fragmentIndex);
}
var index = location.indexOf( '?' );
if (index != - 1 ) {
location = location + '&csrftoken=' + token;
} else {
location = location + '?csrftoken=' + token;
}
if (fragment != null ){
location += fragment;
}
element.setAttribute(attr, location);
}
}
|
在客戶端 html 中,主要是有兩個地方需要加上 token,一個是表單 form,另一個就是鏈接 a。這段代碼首先遍歷所有的 form,在 form 最后添加一隱藏字段,把 csrftoken 放入其中。然后,代碼遍歷所有的鏈接標記 a,在其 href 屬性中加入 csrftoken 參數。注意對于 a.href 來說,可能該屬性已經有參數,或者有錨標記。因此需要分情況討論,以不同的格式把 csrftoken 加入其中。
如果你的網站使用 XMLHttpRequest,那么還需要在 HTTP 頭中自定義 csrftoken 屬性,利用 dojo.xhr 給 XMLHttpRequest 加上自定義屬性代碼如下:
1 |
<strong><em>清單 4 . 在 HTTP 頭中自定義屬性</em></strong>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 |
var plainXhr = dojo.xhr;
dojo.xhr = function(method,args,hasBody) {
args.headers = args.header || {};
tokenValue = '<%=request.getSession(false).getAttribute("csrftoken")%>' ;
var token = dojo.getObject( "tokenValue" );
args.headers[ "csrftoken" ] = (token) ? token : " " ;
return plainXhr(method,args,hasBody);
};
|
這里改寫了 dojo.xhr 的方法,首先確保 dojo.xhr 中存在 HTTP 頭,然后在 args.headers 中添加 csrftoken 字段,并把 token 值從 session 里拿出放入字段中。
CSRF 防御方法選擇之道
通過上文討論可知,目前業界應對 CSRF 攻擊有一些克制方法,但是每種方法都有利弊,沒有一種方法是完美的。如何選擇合適的方法非常重要。如果網站是一個現有系統,想要在最短時間內獲得一定程度的 CSRF 的保護,那么驗證 Referer 的方法是最方便的,要想增加安全性的話,可以選擇不支持低版本瀏覽器,畢竟就目前來說,IE7+, FF3+ 這類高版本瀏覽器的 Referer 值還無法被篡改。
如果系統必須支持 IE6,并且仍然需要高安全性。那么就要使用 token 來進行驗證,在大部分情況下,使用 XmlHttpRequest 并不合適,token 只能以參數的形式放于請求之中,若你的系統不支持用戶自己發布信息,那這種程度的防護已經足夠,否則的話,你仍然難以防范 token 被黑客竊取并發動攻擊。在這種情況下,你需要小心規劃你網站提供的各種服務,從中間找出那些允許用戶自己發布信息的部分,把它們與其他服務分開,使用不同的 token 進行保護,這樣可以有效抵御黑客對于你關鍵服務的攻擊,把危害降到最低。畢竟,刪除別人一個帖子比直接從別人賬號中轉走大筆存款嚴重程度要輕的多。
如果是開發一個全新的系統,則抵御 CSRF 的選擇要大得多。筆者建議對于重要的服務,可以盡量使用 XMLHttpRequest 來訪問,這樣增加 token 要容易很多。另外盡量避免在 js 代碼中使用復雜邏輯來構造常規的同步請求來訪問需要 CSRF 保護的資源,比如 window.location 和 document.createElement(“a”) 之類,這樣也可以減少在附加 token 時產生的不必要的麻煩。
最后,要記住 CSRF 不是黑客唯一的攻擊手段,無論你 CSRF 防范有多么嚴密,如果你系統有其他安全漏洞,比如跨站域腳本攻擊 XSS,那么黑客就可以繞過你的安全防護,展開包括 CSRF 在內的各種攻擊,你的防線將如同虛設。
總結與展望
可見,CSRF 是一種危害非常大的攻擊,又很難以防范。目前幾種防御策略雖然可以很大程度上抵御 CSRF 的攻擊,但并沒有一種完美的解決方案。一些新的方案正在研究之中,比如對于每次請求都使用不同的動態口令,把 Referer 和 token 方案結合起來,甚至嘗試修改 HTTP 規范,但是這些新的方案尚不成熟,要正式投入使用并被業界廣為接受還需時日。在這之前,我們只有充分重視 CSRF,根據系統的實際情況選擇最合適的策略,這樣才能把 CSRF 的危害降到最低。
“為什么存儲密碼用字符數組比字符串更合適”這個問題是我的一個朋友在最近一次面試中提到的。那哥們是應聘的是一個技術lead的職位,有超過六年的工作經驗。字符數組和字符串都可以用于存儲文本數據,但是在選擇具體哪一種時,如果你沒有針對具體的情況是很難回答這個問題的。但是正如這哥們說的任何與字符串相關的問題一定有線索可以在字符串的屬性里面找到,比如不可變性。他就用這種方式去說服面試官。這里我們就來探討一些關于為什么你應該使用char[] 來存儲密碼而不是字符串。
1. 因為字符串是不可變對象,如果作為普通文本存儲密碼,那么它會一直存在內存中直至被垃圾收集器回收。因為字符串從字符串池中取出的(如果池中有該字符串就直接從池中獲取,否則new 一個出來,然后把它放入池中),這樣有很大的機會長期保留在內存中,這樣會引發安全問題。因為任何可以訪問內存的人能以明碼的方式把密碼dump出來。另外你還應該始終以加密而不是普通的文本來表示密碼。因為字符串是不可變,因此沒有任何方法可以改變其內容,任何改變都將產生一個新的字符串,而如果使用char[],你就可以設置所有的元素為空或者為零(這里作者的意思是說,讓認證完后該數組不再使用了,就可以用零或者null覆蓋原來的密碼,防止別人從內存中dump出來)。所以存儲密碼用字符數組可以明顯的減輕密碼被盜的危險。
2. Java官方本身也推薦字符數組,JpasswordField的方法getPassword()就是返回一個字符數組,而由于安全原因getText()方法是被廢棄掉的,因為它返回一個純文本字符串。跟隨Java 團隊的步伐吧,沒有錯。
3. 字符串以普通文本打印在在log文件或控制臺中也易引起危險,但是如果使用數組你不能打印數組的內容,而是它的內存地址。盡管這不是它的真正原因,但仍值得注意。
1
2
3
4
5
6
7 |
String strPassword= "Unknown" ;
char [] charPassword= new char []{ 'U' , 'n' , 'k' , 'w' , 'o' , 'n' };
System.out.println( "String password: " + strPassword);
System.out.println( "Character password: " + charPassword);
String password: Unknown
Character password: [C @110b053
|
以上所有就是為什么字符數組比字符串保存密碼要好的原因,盡管使用char[]還不足以安全。我同樣建議你用hash或者密碼加密代替普通文本,而且一旦認證完成盡可能快的把他清除掉。
MyISAM:這個是默認類型,它是基于傳統的ISAM類型,ISAM是Indexed Sequential Access Method (有索引的順序訪問方法) 的縮寫,它是存儲記錄和文件的標準方法.與其他存儲引擎比較,MyISAM具有檢查和修復表格的大多數工具. MyISAM表格可以被壓縮,而且它們支持全文搜索.它們不是事務安全的,而且也不支持外鍵。如果事物回滾將造成不完全回滾,不具有原子性。如果執行大量的SELECT,MyISAM是更好的選擇。
InnoDB:這種類型是事務安全的.它與BDB類型具有相同的特性,它們還支持外鍵.InnoDB表格速度很快.具有比BDB還豐富的特性, 因此如果需要一個事務安全的存儲引擎,建議使用它.如果你的數據執行大量的INSERT或UPDATE,出于性能方面的考慮,應該使用InnoDB表,
對于支持事物的InnoDB類型的標,影響速度的主要原因是AUTOCOMMIT默認設置是打開的,而且程序沒有顯式調用BEGIN 開始事務,導致每插入一條都自動Commit,嚴重影響了速度。可以在執行sql前調用begin,多條sql形成一個事物(即使autocommit打開也可以),將大大提高性能。
===============================================================
InnoDB和MyISAM是在使用MySQL最常用的兩個表類型,各有優缺點,視具體應用而定。下面是已知的兩者之間的差別,僅供參考。
innodb
InnoDB 給 MySQL 提供了具有事務(commit)、回滾(rollback)和崩潰修復能力(crash recovery capabilities)的事務安全(transaction-safe (ACID compliant))型表。InnoDB 提供了行鎖(locking on row level),提供與 Oracle 類型一致的不加鎖讀取(non-locking read in SELECTs)。這些特性均提高了多用戶并發操作的性能表現。在InnoDB表中不需要擴大鎖定(lock escalation),因為 InnoDB 的列鎖定(row level locks)適宜非常小的空間。InnoDB 是 MySQL 上第一個提供外鍵約束(FOREIGN KEY constraints)的表引擎。
InnoDB 的設計目標是處理大容量數據庫系統,它的 CPU 利用率是其它基于磁盤的關系數據庫引擎所不能比的。在技術上,InnoDB 是一套放在 MySQL 后臺的完整數據庫系統,InnoDB 在主內存中建立其專用的緩沖池用于高速緩沖數據和索引。 InnoDB 把數據和索引存放在表空間里,可能包含多個文件,這與其它的不一樣,舉例來說,在 MyISAM 中,表被存放在單獨的文件中。InnoDB 表的大小只受限于操作系統的文件大小,一般為 2 GB。
InnoDB所有的表都保存在同一個數據文件 ibdata1 中(也可能是多個文件,或者是獨立的表空間文件),相對來說比較不好備份,免費的方案可以是拷貝數據文件、備份 binlog,或者用 mysqldump。
MyISAM
MyISAM 是MySQL缺省存貯引擎 .
每張MyISAM 表被存放在三個文件 。frm 文件存放表格定義。 數據文件是MYD (MYData) 。 索引文件是MYI (MYIndex) 引伸。
因為MyISAM相對簡單所以在效率上要優于InnoDB..小型應用使用MyISAM是不錯的選擇.
MyISAM表是保存成文件的形式,在跨平臺的數據轉移中使用MyISAM存儲會省去不少的麻煩
以下是一些細節和具體實現的差別:
1.InnoDB不支持FULLTEXT類型的索引。
2.InnoDB 中不保存表的具體行數,也就是說,執行select count(*) from table時,InnoDB要掃描一遍整個表來計算有多少行,但是MyISAM只要簡單的讀出保存好的行數即可。注意的是,當count(*)語句包含 where條件時,兩種表的操作是一樣的。
3.對于AUTO_INCREMENT類型的字段,InnoDB中必須包含只有該字段的索引,但是在MyISAM表中,可以和其他字段一起建立聯合索引。
4.DELETE FROM table時,InnoDB不會重新建立表,而是一行一行的刪除。
5.LOAD TABLE FROM MASTER操作對InnoDB是不起作用的,解決方法是首先把InnoDB表改成MyISAM表,導入數據后再改成InnoDB表,但是對于使用的額外的InnoDB特性(例如外鍵)的表不適用。
另外,InnoDB表的行鎖也不是絕對的,如果在執行一個SQL語句時MySQL不能確定要掃描的范圍,InnoDB表同樣會鎖全表,例如update table set num=1 where name like “%aaa%”
任何一種表都不是萬能的,只用恰當的針對業務類型來選擇合適的表類型,才能最大的發揮MySQL的性能優勢。
===============================================================
以下是InnoDB和MyISAM的一些聯系和區別!
1. 4.0以上mysqld都支持事務,包括非max版本。3.23的需要max版本mysqld才能支持事務。
2. 創建表時如果不指定type則默認為myisam,不支持事務。
可以用 show create table tablename 命令看表的類型。
2.1 對不支持事務的表做start/commit操作沒有任何效果,在執行commit前已經提交,測試:
執行一個msyql:
use test;
drop table if exists tn;
create table tn (a varchar(10)) type=myisam;
drop table if exists ty;
create table ty (a varchar(10)) type=innodb;
begin;
insert into tn values('a');
insert into ty values('a');
select * from tn;
select * from ty;
都能看到一條記錄
執行另一個mysql:
use test;
select * from tn;
select * from ty;
只有tn能看到一條記錄
然后在另一邊
commit;
才都能看到記錄。
3. 可以執行以下命令來切換非事務表到事務(數據不會丟失),innodb表比myisam表更安全:
alter table tablename type=innodb;
3.1 innodb表不能用repair table命令和myisamchk -r table_name
但可以用check table,以及mysqlcheck [OPTIONS] database [tables]
4. 啟動mysql數據庫的命令行中添加了以下參數可以使新發布的mysql數據表都默認為使用事務(
只影響到create語句。)
--default-table-type=InnoDB
測試命令:
use test;
drop table if exists tn;
create table tn (a varchar(10));
show create table tn;
5. 臨時改變默認表類型可以用:
set table_type=InnoDB;
show variables like 'table_type';
或:
c:\mysql\bin\mysqld-max-nt --standalone --default-table-type=InnoDB
今天遇到這坑爹的事情, eclipse導入的工程運行總是報java.lang.NoNoClassDefFoundError錯誤 如果是自己創建的工程沒遇到這樣的錯誤 eclipse沒有把工程編繹到classes目錄下,該目錄下為空
查看工程目錄中bin路徑下沒有生成對應的.class文檔
網上查閱了很多資料,大部分都指示classpath設置不對。但是Eclipse本身并不需要配置classpath仍然可以正確運行。
最終,在網絡上找到一盞明燈,方法如下: 把properties屬性里的java compiler-->building-->abort build when build path errors occur 前的勾去掉了 這樣就ok 了
public synchronized void methodA(int a, int b);
public synchronized void methodB(int a){ methodA(a, 0); }
這樣的代碼是成立的,一個線程對同一個對象的鎖可以反復獲取。這種同步鎖稱為可重入的鎖。 加在非static方法上的synchronized方法是和synchronized(this)塊等價的,均為對象鎖,即對this加鎖。 獲得當前對象鎖的線程,可以繼續獲得當前對象鎖,JVM負責跟蹤對象被加鎖的次數。線程運行B方法,此時如果this鎖可以用,線程獲得該鎖,線程給對象加鎖,計數器變成1,然后B方法調用A方法,由于是對同一個對象同一個線程,線程可以繼續獲得鎖,計數器變為2,表示this被加鎖2次。A方法完畢后,線程釋放鎖,計數器變為1,此時對象鎖對其他線程依然是不可獲得的。B方法完畢后,線程繼續釋放鎖,此時計數器變為0,表示鎖被完全釋放,其他線程可以獲得對象鎖。
public synchronized void methodA(int a, int b){
}
public synchronized void methodB(int a, int b){
} 以上兩方法在同一實例對象上是互斥的,synchronized 加在方法上 即對this加鎖,因此在同一實例對象上 兩方法是互斥的。
摘要: 當一個類中有聲明為static final的變量,這樣的變量對類的加載器有一定的影響,首先看看下面的例子。package com.bird.classLoad; class FinalTest{ public static&... 閱讀全文
Sequence是數據庫系統的特性,有的數據庫有Sequence,有的沒有。比如Oracle、DB2、PostgreSQL數據庫有Sequence,MySQL、SQL Server、Sybase等數據庫沒有Sequence。 定義一個seq_test,最小值為10000,最大值為99999999999999999,從20000開始,增量的步長為1,緩存為20的循環排序Sequence。 Oracle的定義方法:
create sequence seq_test minvalue 10000 maxvalue 99999999999999999 start with 20000 increment by 1 cache 20 cycle order; Sequence與indentity的基本作用都差不多。都可以生成自增數字序列。 Sequence是數據庫系統中的一個對象,可以在整個數據庫中使用,和表沒有任何關系;indentity僅僅是指定在表中某一列上,作用范圍就是這個表。
一個表中可以有多個字段使用sequence字段 insert into temp(event_id,event_priority,event_status) values(sequence1.nextval, sequence1.nextval,sequence1.nextval);
mysql 實現sequence
由于mysql不帶sequence,所以要手寫的,創建一張儲存sequence的表(tb_sequence),然后手動插入一條數據 ,最后自定義一個函數來處理要增長的值。
1、創建表tb_sequence,用來存放sequence值:
create table tb_sequence(name varchar(50) not null,current_value int not null,_increment int not null default 1, primary key(name));
2 手動插入數據: insert into tb_sequence values('userid',100,2);
3、定義函數 _nextval:
- DELIMITER //
- create function _nextval(n varchar(50)) returns integer
- begin
- declare _cur int;
- set _cur=(select current_value from tb_sequence where name= n);
- update tb_sequence
- set current_value = _cur + _increment
- where name=n ;
- return _cur;
- end;
檢驗結果
select _nextval('userid');
摘要: (1)BitSet類大小可動態改變, 取值為true或false的位集合。用于表示一組布爾標志。 此類實現了一個按需增長的位向量。位 set 的每個組件都有一個 boolean 值。用非負的整數將 BitSet 的位編入索引。可以對每個編入索引的位進行測試、設置或者清除。通過邏輯與、邏輯或和邏輯異或操作,可以使用一個 BitSet 修改另一個 BitSet 的內容。
默認情況下,set 中所有位... 閱讀全文
摘要: 普通泛型
Java代碼
class Point<T>{ // 此處可以隨便寫標識符號,T是type的簡稱 private T var ; // var的類型由T指定,即:由外部指定 public T getVar(){ // 返回值的類型由外部決定 return var ; } public void setVar(T var){ // 設置的類型也由外部決定 this.... 閱讀全文
nbtstat -a IP nbtstat -anp 10.14x.1x.26x 通過ip 反查 局域網 機器名 注冊服務會經常用到
1 depoly: mvn clean source:jar deploy -Denforcer.skip=true -Dmaven.test.skip=true -U
2 packet mvn clean install -Dmaven.test.skip=true -Denforcer.skip=true -U
3 eclipse
mvn eclipse:eclipse -Dwtpversion=2.0 -Denforcer.skip=true -DdownloadJavadocs=true -o pause
前兩天休眠后機器非正常關機,重新啟動后運行eclipse。悲催的發現eclipse 無法啟動了。每次雙擊啟動后,確定完workspace后,顯示啟動畫面,沒過一會就進入灰色無響應狀態。啟動畫面始終停留在Loading workbench狀態。反復重啟,狀態依舊。嘗試解決。
搜索了一下,應該是非正常關機導致eclipse工作區的文件狀態錯誤導致。在工作區目錄中,有一個.metadata目錄,里面是工作區及各插件的信息,刪除此目錄可以解決問題。
為保險起見,將.metadata改名移動到/tmp目錄,再重啟eclipse,果然可以正常啟動eclipse了,但原來工作區的配置和項目信息也都消失,直接顯示的是歡迎界面。
如何恢復原來的project配置呢?嘗試對比了當前的.metadata和之前備份的那個目錄,發現缺少了很多配置文件。試著一點點恢復一些目錄,但效果不理想。因為不知道哪些文件(目錄)可以恢復,哪些恢復會帶來問題。將備份的整個目錄恢復試試?Eclipse又回到了無法啟動的狀態了。
怎么辦?這時想到啟動停止時顯示的狀態:"Loading workbench",看來和這個workbench插件有關。查看原來的.metadata/.plugins目錄,在眾多文件夾中 com.collabnet.subversion.merge org.eclipse.search org.eclipse.compare org.eclipse.team.core org.eclipse.core.resources org.eclipse.team.cvs.core org.eclipse.core.runtime org.eclipse.team.ui org.eclipse.debug.core org.eclipse.ui.ide org.eclipse.debug.ui org.eclipse.ui.intro org.eclipse.dltk.core org.eclipse.ui.views.log org.eclipse.dltk.core.index.sql.h2 org.eclipse.ui.workbench org.eclipse.dltk.ui org.eclipse.ui.workbench.texteditor org.eclipse.epp.usagedata.recording org.eclipse.wb.discovery.core org.eclipse.jdt.core org.eclipse.wst.internet.cache org.eclipse.jdt.ui org.eclipse.wst.jsdt.core org.eclipse.ltk.core.refactoring org.eclipse.wst.jsdt.ui org.eclipse.ltk.ui.refactoring org.eclipse.wst.jsdt.web.core org.eclipse.m2e.core org.eclipse.wst.sse.ui org.eclipse.m2e.logback.configuration org.eclipse.wst.validation org.eclipse.mylyn.bugzilla.core org.eclipse.wst.xml.core org.eclipse.mylyn.tasks.ui org.tigris.subversion.subclipse.core org.eclipse.php.core org.tigris.subversion.subclipse.graph org.eclipse.php.ui org.tigris.subversion.subclipse.ui
發現了兩個: org.eclipse.ui.workbench 和 org.eclipse.ui.workbench.texteditor。
不管三七二十一,刪了這兩個目錄,重新啟動eclipse。正常啟動且原項目信息正確加載。
原文:http://www.iteye.com/topic/1118660
整個ThreadPoolExecutor的任務處理有4步操作:
- 第一步,初始的poolSize < corePoolSize,提交的runnable任務,會直接做為new一個Thread的參數,立馬執行
- 第二步,當提交的任務數超過了corePoolSize,就進入了第二步操作。會將當前的runable提交到一個block queue中
- 第三步,如果block queue是個有界隊列,當隊列滿了之后就進入了第三步。如果poolSize < maximumPoolsize時,會嘗試new 一個Thread的進行救急處理,立馬執行對應的runnable任務
- 第四步,如果第三步救急方案也無法處理了,就會走到第四步執行reject操作。
幾點說明:(相信這些網上一搜一大把,我這里簡單介紹下,為后面做一下鋪墊)
- block queue有以下幾種實現:
1. ArrayBlockingQueue : 有界的數組隊列 2. LinkedBlockingQueue : 可支持有界/無界的隊列,使用鏈表實現 3. PriorityBlockingQueue : 優先隊列,可以針對任務排序 4. SynchronousQueue : 隊列長度為1的隊列,和Array有點區別就是:client thread提交到block queue會是一個阻塞過程,直到有一個worker thread連接上來poll task。 - RejectExecutionHandler是針對任務無法處理時的一些自保護處理:
1. Reject 直接拋出Reject exception 2. Discard 直接忽略該runnable,不可取 3. DiscardOldest 丟棄最早入隊列的的任務 4. CallsRun 直接讓原先的client thread做為worker線程,進行執行
容易被人忽略的點:
1. pool threads啟動后,以后的任務獲取都會通過block queue中,獲取堆積的runnable task.
所以建議: block size >= corePoolSize ,不然線程池就沒任何意義
2. corePoolSize 和 maximumPoolSize的區別, 和大家正常理解的數據庫連接池不太一樣。
* 據dbcp pool為例,會有minIdle , maxActive配置。minIdle代表是常駐內存中的threads數量,maxActive代表是工作的最大線程數。
* 這里的corePoolSize就是連接池的maxActive的概念,它沒有minIdle的概念(每個線程可以設置keepAliveTime,超過多少時間多有任務后銷毀線程,但不會固定保持一定數量的threads)。
* 這里的maximumPoolSize,是一種救急措施的第一層。當threadPoolExecutor的工作threads存在滿負荷,并且block queue隊列也滿了,這時代表接近崩潰邊緣。這時允許臨時起一批threads,用來處理runnable,處理完后立馬退出。
所以建議: maximumPoolSize >= corePoolSize =期望的最大線程數。 (我曾經配置了corePoolSize=1, maximumPoolSize=20, blockqueue為無界隊列,最后就成了單線程工作的pool。典型的配置錯誤)
3. 善用blockqueue和reject組合. 這里要重點推薦下CallsRun的Rejected Handler,從字面意思就是讓調用者自己來運行。
我們經常會在線上使用一些線程池做異步處理,比如我前面做的 (業務層)異步并行加載技術分析和設計, 將原本串行的請求都變為了并行操作,但過多的并行會增加系統的負載(比如軟中斷,上下文切換)。所以肯定需要對線程池做一個size限制。但是為了引入異步操作后,避免因在block queue的等待時間過長,所以需要在隊列滿的時,執行一個callsRun的策略,并行的操作又轉為一個串行處理,這樣就可以保證盡量少的延遲影響。
所以建議: RejectExecutionHandler = CallsRun , blockqueue size = 2 * poolSize (為啥是2倍poolSize,主要一個考慮就是瞬間高峰處理,允許一個thread等待一個runnable任務)
Btrace容量規劃
再提供一個btrace腳本,分析線上的thread pool容量規劃是否合理,可以運行時輸出poolSize等一些數據。
- import static com.sun.btrace.BTraceUtils.addToAggregation;
- import static com.sun.btrace.BTraceUtils.field;
- import static com.sun.btrace.BTraceUtils.get;
- import static com.sun.btrace.BTraceUtils.newAggregation;
- import static com.sun.btrace.BTraceUtils.newAggregationKey;
- import static com.sun.btrace.BTraceUtils.printAggregation;
- import static com.sun.btrace.BTraceUtils.println;
- import static com.sun.btrace.BTraceUtils.str;
- import static com.sun.btrace.BTraceUtils.strcat;
-
- import java.lang.reflect.Field;
- import java.util.concurrent.atomic.AtomicInteger;
-
- import com.sun.btrace.BTraceUtils;
- import com.sun.btrace.aggregation.Aggregation;
- import com.sun.btrace.aggregation.AggregationFunction;
- import com.sun.btrace.aggregation.AggregationKey;
- import com.sun.btrace.annotations.BTrace;
- import com.sun.btrace.annotations.Kind;
- import com.sun.btrace.annotations.Location;
- import com.sun.btrace.annotations.OnEvent;
- import com.sun.btrace.annotations.OnMethod;
- import com.sun.btrace.annotations.OnTimer;
- import com.sun.btrace.annotations.Self;
-
-
-
-
-
-
- @BTrace
- public class AsyncLoadTracer {
-
- private static AtomicInteger rejecctCount = BTraceUtils.newAtomicInteger(0);
- private static Aggregation histogram = newAggregation(AggregationFunction.QUANTIZE);
- private static Aggregation average = newAggregation(AggregationFunction.AVERAGE);
- private static Aggregation max = newAggregation(AggregationFunction.MAXIMUM);
- private static Aggregation min = newAggregation(AggregationFunction.MINIMUM);
- private static Aggregation sum = newAggregation(AggregationFunction.SUM);
- private static Aggregation count = newAggregation(AggregationFunction.COUNT);
-
- @OnMethod(clazz = "java.util.concurrent.ThreadPoolExecutor", method = "execute", location = @Location(value = Kind.ENTRY))
- public static void executeMonitor(@Self Object self) {
- Field poolSizeField = field("java.util.concurrent.ThreadPoolExecutor", "poolSize");
- Field largestPoolSizeField = field("java.util.concurrent.ThreadPoolExecutor", "largestPoolSize");
- Field workQueueField = field("java.util.concurrent.ThreadPoolExecutor", "workQueue");
-
- Field countField = field("java.util.concurrent.ArrayBlockingQueue", "count");
- int poolSize = (Integer) get(poolSizeField, self);
- int largestPoolSize = (Integer) get(largestPoolSizeField, self);
- int queueSize = (Integer) get(countField, get(workQueueField, self));
-
- println(strcat(strcat(strcat(strcat(strcat("poolSize : ", str(poolSize)), " largestPoolSize : "),
- str(largestPoolSize)), " queueSize : "), str(queueSize)));
- }
-
- @OnMethod(clazz = "java.util.concurrent.ThreadPoolExecutor", method = "reject", location = @Location(value = Kind.ENTRY))
- public static void rejectMonitor(@Self Object self) {
- String name = str(self);
- if (BTraceUtils.startsWith(name, "com.alibaba.pivot.common.asyncload.impl.pool.AsyncLoadThreadPool")) {
- BTraceUtils.incrementAndGet(rejecctCount);
- }
- }
-
- @OnTimer(1000)
- public static void rejectPrintln() {
- int reject = BTraceUtils.getAndSet(rejecctCount, 0);
- println(strcat("reject count in 1000 msec: ", str(reject)));
- AggregationKey key = newAggregationKey("rejectCount");
- addToAggregation(histogram, key, reject);
- addToAggregation(average, key, reject);
- addToAggregation(max, key, reject);
- addToAggregation(min, key, reject);
- addToAggregation(sum, key, reject);
- addToAggregation(count, key, reject);
- }
-
- @OnEvent
- public static void onEvent() {
- BTraceUtils.truncateAggregation(histogram, 10);
- println("---------------------------------------------");
- printAggregation("Count", count);
- printAggregation("Min", min);
- printAggregation("Max", max);
- printAggregation("Average", average);
- printAggregation("Sum", sum);
- printAggregation("Histogram", histogram);
- println("---------------------------------------------");
- }
- }
import static com.sun.btrace.BTraceUtils.addToAggregation;
import static com.sun.btrace.BTraceUtils.field;
import static com.sun.btrace.BTraceUtils.get;
import static com.sun.btrace.BTraceUtils.newAggregation;
import static com.sun.btrace.BTraceUtils.newAggregationKey;
import static com.sun.btrace.BTraceUtils.printAggregation;
import static com.sun.btrace.BTraceUtils.println;
import static com.sun.btrace.BTraceUtils.str;
import static com.sun.btrace.BTraceUtils.strcat;
import java.lang.reflect.Field;
import java.util.concurrent.atomic.AtomicInteger;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.aggregation.Aggregation;
import com.sun.btrace.aggregation.AggregationFunction;
import com.sun.btrace.aggregation.AggregationKey;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnEvent;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.OnTimer;
import com.sun.btrace.annotations.Self;
/**
* 并行加載監控
*
* @author jianghang 2011-4-7 下午10:59:53
*/
@BTrace
public class AsyncLoadTracer {
private static AtomicInteger rejecctCount = BTraceUtils.newAtomicInteger(0);
private static Aggregation histogram = newAggregation(AggregationFunction.QUANTIZE);
private static Aggregation average = newAggregation(AggregationFunction.AVERAGE);
private static Aggregation max = newAggregation(AggregationFunction.MAXIMUM);
private static Aggregation min = newAggregation(AggregationFunction.MINIMUM);
private static Aggregation sum = newAggregation(AggregationFunction.SUM);
private static Aggregation count = newAggregation(AggregationFunction.COUNT);
@OnMethod(clazz = "java.util.concurrent.ThreadPoolExecutor", method = "execute", location = @Location(value = Kind.ENTRY))
public static void executeMonitor(@Self Object self) {
Field poolSizeField = field("java.util.concurrent.ThreadPoolExecutor", "poolSize");
Field largestPoolSizeField = field("java.util.concurrent.ThreadPoolExecutor", "largestPoolSize");
Field workQueueField = field("java.util.concurrent.ThreadPoolExecutor", "workQueue");
Field countField = field("java.util.concurrent.ArrayBlockingQueue", "count");
int poolSize = (Integer) get(poolSizeField, self);
int largestPoolSize = (Integer) get(largestPoolSizeField, self);
int queueSize = (Integer) get(countField, get(workQueueField, self));
println(strcat(strcat(strcat(strcat(strcat("poolSize : ", str(poolSize)), " largestPoolSize : "),
str(largestPoolSize)), " queueSize : "), str(queueSize)));
}
@OnMethod(clazz = "java.util.concurrent.ThreadPoolExecutor", method = "reject", location = @Location(value = Kind.ENTRY))
public static void rejectMonitor(@Self Object self) {
String name = str(self);
if (BTraceUtils.startsWith(name, "com.alibaba.pivot.common.asyncload.impl.pool.AsyncLoadThreadPool")) {
BTraceUtils.incrementAndGet(rejecctCount);
}
}
@OnTimer(1000)
public static void rejectPrintln() {
int reject = BTraceUtils.getAndSet(rejecctCount, 0);
println(strcat("reject count in 1000 msec: ", str(reject)));
AggregationKey key = newAggregationKey("rejectCount");
addToAggregation(histogram, key, reject);
addToAggregation(average, key, reject);
addToAggregation(max, key, reject);
addToAggregation(min, key, reject);
addToAggregation(sum, key, reject);
addToAggregation(count, key, reject);
}
@OnEvent
public static void onEvent() {
BTraceUtils.truncateAggregation(histogram, 10);
println("---------------------------------------------");
printAggregation("Count", count);
printAggregation("Min", min);
printAggregation("Max", max);
printAggregation("Average", average);
printAggregation("Sum", sum);
printAggregation("Histogram", histogram);
println("---------------------------------------------");
}
}
運行結果:
- poolSize : 1 , largestPoolSize = 10 , queueSize = 10
- reject count in 1000 msec: 0
poolSize : 1 , largestPoolSize = 10 , queueSize = 10
reject count in 1000 msec: 0
說明:
1. poolSize 代表為當前的線程數
2. largestPoolSize 代表為歷史最大的線程數
3. queueSize 代表blockqueue的當前堆積的size
4. reject count 代表在1000ms內的被reject的數量
最后
這是我對ThreadPoolExecutor使用過程中的一些經驗總結,希望能對大家有所幫助,如有描述不對的地方歡迎拍磚。
- 堆大小設置
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:設置年輕代為并行收集。可與CMS收集同時使用。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:打印每次垃圾回收前,程序未中斷的執行時間。可與上面混合使用
輸出形式: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后,對年老代進行壓縮
google的guava工具包的確很多好東西,包括之前的字符串處理工具類的,還有大量的collection相關的,項目地址在:http://code.google.com/p/guava-libraries/ 留意到其中的collection相關類中的map,簡單介紹如下,更多的請大家補充挖掘或者 看原來的文檔: guava提供的是多值map!,就是說,一個key,可以對應多個value了,比如一個人會有多個聯系號碼等,可以表達為: multimap<String,String> phonebook=ArrayListMultmap.create(); phonebook.put("a","43434"); phonebook.put("b","3434434"); system.out.println(phonebook,get("a")); 還有map的查詢:
- Map<String, Integer> user = new HashMap<String, Integer>();
- user.put("張三", 20);
- user.put("李四", 22);
- user.put("王五", 25);
-
- Map<String, Integer> filtedMap = Maps.filterValues(user,
- new Predicate<Integer>() {
- public boolean apply(Integer value) {
- return value > 20;
- }
- });
- System.out.println(filtedMap);
Map<String, Integer> user = new HashMap<String, Integer>();
user.put("張三", 20);
user.put("李四", 22);
user.put("王五", 25);
// 所有年齡大于20歲的人員
Map<String, Integer> filtedMap = Maps.filterValues(user,
new Predicate<Integer>() {
public boolean apply(Integer value) {
return value > 20;
}
});
System.out.println(filtedMap);
再來點例子,加深了解:
-
- public class MutliMapTest {
- public static void main(String... args) {
- Multimap<String, String> myMultimap = ArrayListMultimap.create();
-
-
- myMultimap.put('Fruits', 'Bannana');
- myMultimap.put('Fruits', 'Apple');
- myMultimap.put('Fruits', 'Pear');
- myMultimap.put('Vegetables', 'Carrot');
-
-
- int size = myMultimap.size();
- System.out.println(size);
-
-
- Collection<string> fruits = myMultimap.get('Fruits');
- System.out.println(fruits);
-
- Collection<string> vegetables = myMultimap.get('Vegetables');
- System.out.println(vegetables);
-
-
- for(String value : myMultimap.values()) {
- System.out.println(value);
- }
-
-
- myMultimap.remove('Fruits','Pear');
- System.out.println(myMultimap.get('Fruits'));
-
-
- myMultimap.removeAll('Fruits');
- System.out.println(myMultimap.get('Fruits'));
- }
- }
public class MutliMapTest {
public static void main(String... args) {
Multimap<String, String> myMultimap = ArrayListMultimap.create();
// Adding some key/value
myMultimap.put('Fruits', 'Bannana');
myMultimap.put('Fruits', 'Apple');
myMultimap.put('Fruits', 'Pear');
myMultimap.put('Vegetables', 'Carrot');
// Getting the size
int size = myMultimap.size();
System.out.println(size); // 4
Collection<string> fruits = myMultimap.get('Fruits');
System.out.println(fruits); // [Bannana, Apple, Pear]
Collection<string> vegetables = myMultimap.get('Vegetables');
System.out.println(vegetables); // [Carrot]
// 循環輸出
for(String value : myMultimap.values()) {
System.out.println(value);
}
// 移走某個值
myMultimap.remove('Fruits','Pear');
System.out.println(myMultimap.get('Fruits')); // [Bannana, Pear]
//移走某個KEY的所有對應value
myMultimap.removeAll('Fruits');
System.out.println(myMultimap.get('Fruits')); // [] (Empty Collection!)
}
}
更詳細的看: http://docs.guava-libraries.googlecode.com/git-history/release09/javadoc/com/google/common/collect/Multimap.html
最近老是出現雙擊啟動后,確定完workspace后,顯示啟動畫面,沒過一會就進入灰色無響應狀態。啟動畫面始終停留在Loading workbench狀態。反復重啟,狀態依舊。 在網上看到有人已經解決了,嘗試使用后的確可以解決問題,所以留下分享。
搜索了一下,應該是非正常關機導致eclipse工作區的文件狀態錯誤導致。在工作區目錄中,有一個.metadata目錄,里面是工作區及各插件的信息,刪除此目錄可以解決問題。
Jem保險起見,將.metadata改名移動到/tmp目錄,再重啟eclipse,果然可以正常啟動eclipse了,但原來工作區的配置和項目信息也都消失,直接顯示的是歡迎界面。
如何恢復原來的project配置呢?嘗試對比了當前的.metadata和之前備份的那個目錄,發現缺少了n多的配置文件。試著一點點恢復一些目錄,但效果不理想。因為不知道哪些文件(目錄)可以恢復,哪些恢復會帶來問題。將備份的整個目錄恢復試試?Eclipse又回到了無法啟動的狀態了。
咋辦?這時想到啟動停止時顯示的狀態:"Loading workbench",看來和這個workbench插件有關。查看原來的.metadata/.plugins目錄,在眾多文件夾中
com.collabnet.subversion.merge org.eclipse.search |
org.eclipse.compare org.eclipse.team.core |
org.eclipse.core.resources org.eclipse.team.cvs.core |
org.eclipse.core.runtime org.eclipse.team.ui |
org.eclipse.debug.core org.eclipse.ui.ide |
org.eclipse.debug.ui org.eclipse.ui.intro |
org.eclipse.dltk.core org.eclipse.ui.views.log |
org.eclipse.dltk.core.index.sql.h2 org.eclipse.ui.workbench |
org.eclipse.dltk.ui org.eclipse.ui.workbench.texteditor |
org.eclipse.epp.usagedata.recording org.eclipse.wb.discovery.core |
org.eclipse.jdt.core org.eclipse.wst.internet.cache |
org.eclipse.jdt.ui org.eclipse.wst.jsdt.core |
org.eclipse.ltk.core.refactoring org.eclipse.wst.jsdt.ui |
org.eclipse.ltk.ui.refactoring org.eclipse.wst.jsdt.web.core |
org.eclipse.m2e.core org.eclipse.wst.sse.ui |
org.eclipse.m2e.logback.configuration org.eclipse.wst.validation |
org.eclipse.mylyn.bugzilla.core org.eclipse.wst.xml.core |
org.eclipse.mylyn.tasks.ui org.tigris.subversion.subclipse.core |
org.eclipse.php.core org.tigris.subversion.subclipse.graph |
org.eclipse.php.ui org.tigris.subversion.subclipse.ui |
發現了兩個:org.eclipse.ui.workbench和 org.eclipse.ui.workbench.texteditor。
不管三七二十一,刪了這兩個目錄,重新啟動eclipse。正常啟動且原項目信息正確加載。
1.synchronized與static synchronized 的區別
synchronized是對類的當前實例進行加鎖,防止其他線程同時訪問該類的該實例的所有synchronized塊,注意這里是“類的當前實例”, 類的兩個不同實例就沒有這種約束了。那么static synchronized恰好就是要控制類的所有實例的訪問了,static synchronized是限制線程同時訪問jvm中該類的所有實例同時訪問對應的代碼快。實際上,在類中某方法或某代碼塊中有 synchronized,那么在生成一個該類實例后,改類也就有一個監視快,放置線程并發訪問改實例synchronized保護快,而static synchronized則是所有該類的實例公用一個監視快了,也也就是兩個的區別了,也就是synchronized相當于 this.synchronized,而static synchronized相當于Something.synchronized. 一個日本作者-結成浩的《java多線程設計模式》有這樣的一個列子:
pulbic class Something(){ publicsynchronizedvoid isSyncA(){} publicsynchronizedvoid isSyncB(){} publicstaticsynchronizedvoid cSyncA(){} publicstaticsynchronizedvoid cSyncB(){} } 那么,加入有Something類的兩個實例a與b,那么下列組方法何以被1個以上線程同時訪問呢
a. x.isSyncA()與x.isSyncB() b. x.isSyncA()與y.isSyncA() c. x.cSyncA()與y.cSyncB() d. x.isSyncA()與Something.cSyncA() 這里,很清楚的可以判斷:
a,都是對同一個實例的synchronized域訪問,因此不能被同時訪問 b,是針對不同實例的,因此可以同時被訪問 c,因為是static synchronized,所以不同實例之間仍然會被限制,相當于Something.isSyncA()與 Something.isSyncB()了,因此不能被同時訪問。 那么,第d呢?,書上的 答案是可以被同時訪問的,答案理由是synchronzied的是實例方法與synchronzied的類方法由于鎖定(lock)不同的原因。 個人分析也就是synchronized 與static synchronized 相當于兩幫派,各自管各自,相互之間就無約束了,可以被同時訪問。目前還不是分清楚java內部設計synchronzied是怎么樣實現的。 結論:A: synchronized static是某個類的范圍,synchronized static cSync{}防止多個線程同時訪問這個 類中的synchronized static 方法。它可以對類的所有對象實例起作用。 B: synchronized 是某實例的范圍,synchronized isSync(){}防止多個線程同時訪問這個實例中的synchronized 方法。
2.synchronized方法與synchronized代碼快的區別
synchronized methods(){} 與synchronized(this){}之間沒有什么區別,只是synchronized methods(){} 便于閱讀理解,而synchronized(this){}可以更精確的控制沖突限制訪問區域,有時候表現更高效率。
3.synchronized關鍵字是不能繼承的
這個在《搞懂java中的synchronized關鍵字》一文中看到的,我想這一點也是很值得注意的,繼承時子類的覆蓋方法必須顯示定義成synchronized。(但是如果使用繼承開發環境的話,會默認加上synchronized關鍵字)
在Java中,為了保證多線程讀寫數據時保證數據的一致性,可以采用兩種方式:
同步
如用synchronized關鍵字,或者使用鎖對象.
volatile
使用volatile關鍵字 用一句話概括volatile,它能夠使變量在值發生改變時能盡快地讓其他線程知道.
volatile詳解
首先我們要先意識到有這樣的現象,編譯器為了加快程序運行的速度,對一些變量的寫操作會先在寄存器或者是CPU緩存上進行,最后才寫入內存. 而在這個過程,變量的新值對其他線程是不可見的.而volatile的作用就是使它修飾的變量的讀寫操作都必須在內存中進行!
volatile與synchronized
volatile本質是在告訴jvm當前變量在寄存器中的值是不確定的,需要從主存中讀取,synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住. volatile僅能使用在變量級別,synchronized則可以使用在變量,方法. volatile僅能實現變量的修改可見性,但不具備原子特性,而synchronized則可以保證變量的修改可見性和原子性. volatile不會造成線程的阻塞,而synchronized可能會造成線程的阻塞. volatile標記的變量不會被編譯器優化,而synchronized標記的變量可以被編譯器優化.
一般大家都知道ArrayList和LinkedList的大致區別: 1.ArrayList是實現了基于動態數組的數據結構,LinkedList基于鏈表的數據結構。 2.對于隨機訪問get和set,ArrayList覺得優于LinkedList,因為LinkedList要移動指針。 3.對于新增和刪除操作add和remove,LinedList比較占優勢,因為ArrayList要移動數據。
ArrayList和LinkedList是兩個集合類,用于存儲一系列的對象引用(references)。例如我們可以用ArrayList來存儲一系列的String或者Integer。那么ArrayList和LinkedList在性能上有什么差別呢?什么時候應該用ArrayList什么時候又該用LinkedList呢?
一.時間復雜度 首先一點關鍵的是,ArrayList的內部實現是基于基礎的對象數組的,因此,它使用get方法訪問列表中的任意一個元素時(random access),它的速度要比LinkedList快。LinkedList中的get方法是按照順序從列表的一端開始檢查,直到另外一端。對LinkedList而言,訪問列表中的某個指定元素沒有更快的方法了。 假設我們有一個很大的列表,它里面的元素已經排好序了,這個列表可能是ArrayList類型的也可能是LinkedList類型的,現在我們對這個列表來進行二分查找(binary search),比較列表是ArrayList和LinkedList時的查詢速度,看下面的程序:
- package com.mangocity.test;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Random;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collections;
- public class TestList ...{
- public static final int N=50000;
-
- public static List values;
-
- static...{
- Integer vals[]=new Integer[N];
-
- Random r=new Random();
-
- for(int i=0,currval=0;i<N;i++)...{
- vals=new Integer(currval);
- currval+=r.nextInt(100)+1;
- }
-
- values=Arrays.asList(vals);
- }
-
- static long timeList(List lst)...{
- long start=System.currentTimeMillis();
- for(int i=0;i<N;i++)...{
- int index=Collections.binarySearch(lst, values.get(i));
- if(index!=i)
- System.out.println("***錯誤***");
- }
- return System.currentTimeMillis()-start;
- }
- public static void main(String args[])...{
- System.out.println("ArrayList消耗時間:"+timeList(new ArrayList(values)));
- System.out.println("LinkedList消耗時間:"+timeList(new LinkedList(values)));
- }
- }
package com.mangocity.test;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class TestList ...{
public static final int N=50000;
public static List values;
static...{
Integer vals[]=new Integer[N];
Random r=new Random();
for(int i=0,currval=0;i<N;i++)...{
vals=new Integer(currval);
currval+=r.nextInt(100)+1;
}
values=Arrays.asList(vals);
}
static long timeList(List lst)...{
long start=System.currentTimeMillis();
for(int i=0;i<N;i++)...{
int index=Collections.binarySearch(lst, values.get(i));
if(index!=i)
System.out.println("***錯誤***");
}
return System.currentTimeMillis()-start;
}
public static void main(String args[])...{
System.out.println("ArrayList消耗時間:"+timeList(new ArrayList(values)));
System.out.println("LinkedList消耗時間:"+timeList(new LinkedList(values)));
}
}
我得到的輸出是:ArrayList消耗時間:15 LinkedList消耗時間:2596 這個結果不是固定的,但是基本上ArrayList的時間要明顯小于LinkedList的時間。因此在這種情況下不宜用LinkedList。二分查找法使用的隨機訪問(random access)策略,而LinkedList是不支持快速的隨機訪問的。對一個LinkedList做隨機訪問所消耗的時間與這個list的大小是成比例的。而相應的,在ArrayList中進行隨機訪問所消耗的時間是固定的。 這是否表明ArrayList總是比LinkedList性能要好呢?這并不一定,在某些情況下LinkedList的表現要優于ArrayList,有些算法在LinkedList中實現時效率更高。比方說,利用Collections.reverse方法對列表進行反轉時,其性能就要好些。 看這樣一個例子,加入我們有一個列表,要對其進行大量的插入和刪除操作,在這種情況下LinkedList就是一個較好的選擇。請看如下一個極端的例子,我們重復的在一個列表的開端插入一個元素:
- package com.mangocity.test;
-
- import java.util.*;
- public class ListDemo {
- static final int N=50000;
- static long timeList(List list){
- long start=System.currentTimeMillis();
- Object o = new Object();
- for(int i=0;i<N;i++)
- list.add(0, o);
- return System.currentTimeMillis()-start;
- }
- public static void main(String[] args) {
- System.out.println("ArrayList耗時:"+timeList(new ArrayList()));
- System.out.println("LinkedList耗時:"+timeList(new LinkedList()));
- }
- }
package com.mangocity.test;
import java.util.*;
public class ListDemo {
static final int N=50000;
static long timeList(List list){
long start=System.currentTimeMillis();
Object o = new Object();
for(int i=0;i<N;i++)
list.add(0, o);
return System.currentTimeMillis()-start;
}
public static void main(String[] args) {
System.out.println("ArrayList耗時:"+timeList(new ArrayList()));
System.out.println("LinkedList耗時:"+timeList(new LinkedList()));
}
}
這時我的輸出結果是:ArrayList耗時:2463
LinkedList耗時:15 這和前面一個例子的結果截然相反,當一個元素被加到ArrayList的最開端時,所有已經存在的元素都會后移,這就意味著數據移動和復制上的開銷。相反的,將一個元素加到LinkedList的最開端只是簡單的未這個元素分配一個記錄,然后調整兩個連接。在LinkedList的開端增加一個元素的開銷是固定的,而在ArrayList的開端增加一個元素的開銷是與ArrayList的大小成比例的。
二.空間復雜度 在LinkedList中有一個私有的內部類,定義如下:
- private static class Entry {
- Object element;
- Entry next;
- Entry previous;
- }
private static class Entry {
Object element;
Entry next;
Entry previous;
}
每個Entry對象reference列表中的一個元素,同時還有在LinkedList中它的上一個元素和下一個元素。一個有1000個元素的LinkedList對象將有1000個鏈接在一起的Entry對象,每個對象都對應于列表中的一個元素。這樣的話,在一個LinkedList結構中將有一個很大的空間開銷,因為它要存儲這1000個Entity對象的相關信息。 ArrayList使用一個內置的數組來存儲元素,這個數組的起始容量是10.當數組需要增長時,新的容量按如下公式獲得:新容量=(舊容量*3)/2+1,也就是說每一次容量大概會增長50%。這就意味著,如果你有一個包含大量元素的ArrayList對象,那么最終將有很大的空間會被浪費掉,這個浪費是由ArrayList的工作方式本身造成的。如果沒有足夠的空間來存放新的元素,數組將不得不被重新進行分配以便能夠增加新的元素。對數組進行重新分配,將會導致性能急劇下降。如果我們知道一個ArrayList將會有多少個元素,我們可以通過構造方法來指定容量。我們還可以通過trimToSize方法在ArrayList分配完畢之后去掉浪費掉的空間。
三.總結 ArrayList和LinkedList在性能上各有優缺點,都有各自所適用的地方,總的說來可以描述如下: 1.對ArrayList和LinkedList而言,在列表末尾增加一個元素所花的開銷都是固定的。對ArrayList而言,主要是在內部數組中增加一項,指向所添加的元素,偶爾可能會導致對數組重新進行分配;而對LinkedList而言,這個開銷是統一的,分配一個內部Entry對象。
2.在ArrayList的中間插入或刪除一個元素意味著這個列表中剩余的元素都會被移動;而在LinkedList的中間插入或刪除一個元素的開銷是固定的。
3.LinkedList不支持高效的隨機元素訪問。
4.ArrayList的空間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間
可以這樣說:當操作是在一列數據的后面添加數據而不是在前面或中間,并且需要隨機地訪問其中的元素時,使用ArrayList會提供比較好的性能;當你的操作是在一列數據的前面或中間添加或刪除數據,并且按照順序訪問其中的元素時,就應該使用LinkedList了。
|