#########################################
SSL v2的設計順應經典的公鑰基礎設施PKI(public key infrastructure)設計,后者認為一個服務器只提供一個服務從而也就只使用一個證書。這意味著服務器可以在TLS啟動的早期階段發送或提交證書,因為它知道它在為哪個域服務。HTTP服務器開啟虛擬主機支持后,每個服務器通過相同的地址可以為很多域提供服務。服務器檢查每一個請求來決定它在為哪個域服務。這個信息通常從HTTP請求頭獲得。不幸的是,當設置了TLS加密,服務器在讀取HTTP請求里面的域名之前已經向客戶端提交了證書,也就是已經為默認域提供了服務。因此,這種為虛擬主機提供安全的簡單途徑經常導致使用了錯誤的數字證書,從而導致瀏覽器對用戶發出警告。 以上描述摘自OpenWares。詳細了解請到:服務器名字指示SNI(Server Name Indication) 即訪問www.buyberry.net會讀取到beta.buyberry.net的證書,這樣瀏覽器會報證書錯誤。 因此需要Server Name Indication (RFC 4366)這個擴展協議來修正。標準apache是支持Name Based SSL VHosts With SNI 。前提需要 OpenSSL 0.9.8f 之后才能支持。但是IHS并不支持這個擴展協議。 只能使用基于端口或者基于IP的虛擬主機來workaround LoadModule ibm_ssl_module modules/mod_ibm_ssl.so Listen 443 Listen 444 NameVirtualHost www.buyberry.net:443 ServerName www.buyberry.net SSLCipherSpec 34 SSLCipherSpec 35 SSLCipherSpec 3A SSLCipherSpec 33 SSLCipherSpec 36 SSLCipherSpec 39 SSLCipherSpec 32 SSLCipherSpec 31 SSLCipherSpec 30 DocumentRoot “/ihs/htdocs” SSLEnable SSLClientAuth none Keyfile “/ihs/sslkey20121227/key.kdb” ErrorLog logs/ssl1_error_log CustomLog logs/ssl1_access_log common env=!image ######################################### NameVirtualHost beta.buyberry.net:444 ServerName beta.buyberry.net SSLCipherSpec 34 SSLCipherSpec 35 SSLCipherSpec 3A SSLCipherSpec 33 SSLCipherSpec 36 SSLCipherSpec 39 SSLCipherSpec 32 SSLCipherSpec 31 SSLCipherSpec 30 DocumentRoot “/ihs/htdocs” SSLEnable SSLClientAuth none Keyfile “/ihs/2012key/2012key.kdb” ErrorLog logs/ssl2_error_log CustomLog logs/ssl2_access_log common env=!image 首先想到的就是dos輸出是用系統的默認編碼(gbk)的,我文件可是使用UTF-8編寫的,肯定會出亂碼樓。當時的想法就是在批處理文件中手動設置臨時編碼來進行輸出,可是對這塊不了解,百度吧,關鍵字:dos中設置編碼,結果還真的有人遇到了這樣的問題,不過是在做PHP項目中出現的,但是我也一樣可以借用,呵呵。原來系統會有很多的字體代碼的,在執行批處理前設置一下就OK了,命令如下: chcp 65001 問題就解決了,65001是UTF-8的代碼頁,其他如下: MS-DOS為以下國家和語言提供字符集:代碼頁描述 936 簡體中文(默認) 950 繁體中文 65001 UTF-8 1258 越南語 1257 波羅的語 1256 阿拉伯語 1255 希伯來語 1254 土耳其語 1253 希臘語 1252 拉丁 1 字符 (ANSI) 1251 西里爾語 1250 中歐語言 949 朝鮮語 932 日語 874 泰國語 850 多語種 (MS-DOS Latin1) 437 MS-DOS 美國英語 以上就是本次編寫批處理命令中遇到的比較特殊的問題,以此記錄,以備后用。 注:編寫的批處理命令我在頂端空出來一行,才能使 chcp 65001 生效,這應該是和UTF-8文件有無BOM編碼格式有關,我選擇的是UTF-8有BOM編碼格式保存的文件。 Hugepages是從Linux kernal 2.6后被引入的,其目的是使用更大的memory page size以適應越來越大的系統內存,使得oracle SGA這種大內存結構能分配到一個大塊(hugepagesize)連續的內存,提高效率. 在Linux下,默認的page size大小為4k。默認的hugepagesize=2M。 我們來看看兩者之間有什么區別 1. Page Table大小(小page size 可以節省內存,但提高管理成本page table的規模很大) Page Table是用來存放虛擬內存也和物理內存頁對應關系的內存結構。因為page size較小,所以相應的改內存結構也會比較大。 而Hugepages的常見page size為2M,是4k size的500倍,所以可以大大減小page table的size。 我們來看兩個例子: 這是一個沒有配置Hugepage的系統,系統內存128G,pagetable大小大約為4G。 cat /proc/meminfo MemTotal: 132086880 kB 這是配置了Hugepage的系統,系統內存96G, PageTable大小僅為78M MemTotal: 98999880 kB 2. 大大提高了CPU cache中存放的page table所覆蓋的內存大小,從而提高了TLB命中率 進程的虛擬內存地址段先連接到page tables然后再連接到物理內存。所以在訪問內存時需要先訪問page tables得到虛擬內存和物理內存的映射關系,然后再訪問物理內存。 CPU cache中有一部分TLB(Translation Lookaside Buffer)用來存放部分page table以提高這種裝換的速度。因為page size變大了,所以同樣大小的TLB,所覆蓋的內存大小也變大了。提高了TBL命中率,也就是提高了地址轉換的速度。 3. 使用Hugepages的內存頁是不會被交換出去的,永遠常駐在內存中,所以也減少了內存也替換的額外開銷 下面再說說在數據庫服務器上使用Hugepages要注意的幾點 1. Hugepages是在分配后就會預留出來的,其大小一定要比服務器上所有實例的SGA總和要大,差一點都不行。 比如說Hugepages設置為90G,oracle SGA為91G,那么oracle在啟動的時候就不會使用到這90G的Hugepages。這90G就浪費了。所以在設置Hugepages時要計算SGA的大小,后面會給出一個腳本來計算。 2. 其他進程無法使用Hugepages的內存,所以不要設置太大,稍稍比SGA大一點保證SGA可以使用到hugepages就好了。 3. PGA不會使用Hugepages的內存。所以11g的AMM (Automatic Memory Management,memory_target參數)是不被支持的。而ASMM(Automatic Shared Memory Management, SGA_target參數)是被支持的,這兩個不要搞混淆了。 4. 在meminfo中和Hugepage相關的有四項(RHEL5) HugePages_Total: 43000 HugePages_Total為所分配的頁面數目,和Hugepagesize相乘后得到所分配的內存大小。43000*2/1024大約為84GB HugePages_Free – HugePages_Rsvd 這部分是沒有被使用到的內存,如果沒有其他的oracle instance,這部分內存也許永遠都不會被使用到,也就是被浪費了。在該系統上有11.5GB的內存被浪費了。
最后說說如何設置HugePages: 1. 首先計算SGA大小已決定你要使用多少HugePages內存頁。 你可以手工計算,如果使用了ASMM可以用SGA_Target/Hugepagesize,否則可以將 db_cache_size,large_pool_size, shared_pool_size,jave_pool_size, streams_pool_size五個部分加起來除以Hugepagesize。 或者可以先將oracle instance都起起來,然后ipcs -m查看共享內存段大小來計算。oracle在401749.1中也提供了一個腳本來幫助計算,腳本如下: #!/bin/bash # # hugepages_settings.sh # # Linux bash script to compute values for the # recommended HugePages/HugeTLB configuration # # Note: This script does calculation for all shared memory # segments available when the script is run, no matter it # is an Oracle RDBMS shared memory segment or not. # # This script is provided by Doc ID 401749.1 from My Oracle Support # http://support.oracle.com # Welcome text echo " This script is provided by Doc ID 401749.1 from My Oracle Support (http://support.oracle.com) where it is intended to compute values for the recommended HugePages/HugeTLB configuration for the current shared memory segments. Before proceeding with the execution please make sure that: * Oracle Database instance(s) are up and running * Oracle Database 11g Automatic Memory Management (AMM) is not setup (See Doc ID 749851.1) * The shared memory segments can be listed by command: # ipcs -m Press Enter to proceed..." read # Check for the kernel version KERN=`uname -r | awk -F. '{ printf("%d.%d\n",$1,$2); }'` # Find out the HugePage size HPG_SZ=`grep Hugepagesize /proc/meminfo | awk '{print $2}'` # Initialize the counter NUM_PG=0 # Cumulative number of pages required to handle the running shared memory segments for SEG_BYTES in `ipcs -m | awk '{print $5}' | grep "[0-9][0-9]*"` do MIN_PG=`echo "$SEG_BYTES/($HPG_SZ*1024)" | bc -q` if [ $MIN_PG -gt 0 ]; then NUM_PG=`echo "$NUM_PG+$MIN_PG+1" | bc -q` fi done RES_BYTES=`echo "$NUM_PG * $HPG_SZ * 1024" | bc -q` # An SGA less than 100MB does not make sense # Bail out if that is the case if [ $RES_BYTES -lt 100000000 ]; then echo "***********" echo "** ERROR **" echo "***********" echo "Sorry! There are not enough total of shared memory segments allocated for HugePages configuration. HugePages can only be used for shared memory segments that you can list by command: # ipcs -m of a size that can match an Oracle Database SGA. Please make sure that: * Oracle Database instance is up and running * Oracle Database 11g Automatic Memory Management (AMM) is not configured" exit 1 fi # Finish with results case $KERN in '2.4') HUGETLB_POOL=`echo "$NUM_PG*$HPG_SZ/1024" | bc -q`; echo "Recommended setting: vm.hugetlb_pool = $HUGETLB_POOL" ;; '2.6') echo "Recommended setting: vm.nr_hugepages = $NUM_PG" ;; *) echo "Unrecognized kernel version $KERN. Exiting." ;; esac # End 2. 關閉所有oracle實例 3. 用root設定oracle memlock limit,設置一個較大的數值或者unlimited 在/etc/security/limits.conf最后添加 4. 分配hugepages內存 在/etc/sysctl.conf中添加 vm.nr_hugepages = 46000 (step1′s value) 執行sysctl -p使其生效。這時候內存就已經被分配了,可以查看meminfo grep Huge /proc/meminfo HugePages_Total為設定的值大小,HugePages_Free應該和HugePages_Total一樣大,HugePages_Rsvd為0. 5. 啟動Oracle instance 這時候再次查看meminfo HugePages_Total為設定的值大小不變,HugePages_Free有所降低,HugePages_Rsvd為一個較大的數值(因為剛剛啟動時,大部分SGA被分配但是沒有被使用到)。 如果Hugepages沒有被使用,可能一些memory page被分配為4k大小了,那么需要重啟server來設置。 從我們的測試結果看,Hugepages可以提高OLTP系統10%的吞吐量,當然不同的數據庫應用結果可能不同,但是總體來說這是一個nice to have的設置。 SoftReference、Weak Reference和PhantomRefrence分析和比較 本文將談一下對SoftReference(軟引用)、WeakReference(弱引用)和PhantomRefrence(虛引用)的理解,這三個類是對heap中java對象的應用,通過這個三個類可以和gc做簡單的交互。 強引用: 除了上面提到的三個引用之外,還有一個引用,也就是最長用到的那就是強引用。例如:
上面代碼中第一句是在heap堆中創建新的Object對象通過o引用這個對象,第二句是通過o建立o1到new Object()這個heap堆中的對象的引用,這兩個引用都是強引用.只要存在對heap中對象的引用,gc就不會收集該對象.如果通過如下代碼:
如果顯式地設置o和o1為null,或超出范圍,則gc認為該對象不存在引用,這時就可以收集它了。可以收集并不等于就一會被收集,什么時候收集這要取決于gc的算法,這要就帶來很多不確定性。例如你就想指定一個對象,希望下次gc運行時把它收集了,那就沒辦法了,有了其他的三種引用就可以做到了。其他三種引用在不妨礙gc收集的情況下,可以做簡單的交互。 heap中對象有強可及對象、軟可及對象、弱可及對象、虛可及對象和不可到達對象。應用的強弱順序是強、軟、弱、和虛。對于對象是屬于哪種可及的對象,由他的最強的引用決定。如下:
第一行在heap對中創建內容為“abc”的對象,并建立abc到該對象的強引用,該對象是強可及的。 第二行和第三行分別建立對heap中對象的軟引用和弱引用,此時heap中的對象仍是強可及的。 第四行之后heap中對象不再是強可及的,變成軟可及的。同樣第五行執行之后變成弱可及的。 SoftReference(軟引用) 軟引用是主要用于內存敏感的高速緩存。在jvm報告內存不足之前會清除所有的軟引用,這樣以來gc就有可能收集軟可及的對象,可能解決內存吃緊問題,避免內存溢出。什么時候會被收集取決于gc的算法和gc運行時可用內存的大小。當gc決定要收集軟引用是執行以下過程,以上面的abcSoftRef為例: 1、首先將abcSoftRef的referent設置為null,不再引用heap中的new String("abc")對象。 2、將heap中的new String("abc")對象設置為可結束的(finalizable)。 3、當heap中的new String("abc")對象的finalize()方法被運行而且該對象占用的內存被釋放, abcSoftRef被添加到它的ReferenceQueue中。 注:對ReferenceQueue軟引用和弱引用可以有可無,但是虛引用必須有,參見:
被 Soft Reference 指到的對象,即使沒有任何 Direct Reference,也不會被清除。一直要到 JVM 內存不足且 沒有 Direct Reference 時才會清除,SoftReference 是用來設計 object-cache 之用的。如此一來 SoftReference 不但可以把對象 cache 起來,也不會造成內存不足的錯誤 (OutOfMemoryError)。我覺得 Soft Reference 也適合拿來實作 pooling 的技巧。
弱引用 當gc碰到弱可及對象,并釋放abcWeakRef的引用,收集該對象。但是gc可能需要對此運用才能找到該弱可及對象。通過如下代碼可以了明了的看出它的作用:
運行結果: before gc: abc gc收集弱可及對象的執行過程和軟可及一樣,只是gc不會根據內存情況來決定是不是收集該對象。 如果你希望能隨時取得某對象的信息,但又不想影響此對象的垃圾收集,那么你應該用 Weak Reference 來記住此對象,而不是用一般的 reference。
在此例中,透過 get() 可以取得此 Reference 的所指到的對象,如果返回值為 null 的話,代表此對象已經被清除。 這類的技巧,在設計 Optimizer 或 Debugger 這類的程序時常會用到,因為這類程序需要取得某對象的信息,但是不可以 影響此對象的垃圾收集。 PhantomRefrence(虛引用) 虛顧名思義就是沒有的意思,建立虛引用之后通過get方法返回結果始終為null,通過源代碼你會發現,虛引用通向會把引用的對象寫進referent,只是get方法返回結果為null。先看一下和gc交互的過程在說一下他的作用。 1 不把referent設置為null,直接把heap中的new String("abc")對象設置為可結束的(finalizable). 2 與軟引用和弱引用不同,先把PhantomRefrence對象添加到它的ReferenceQueue中,然后在釋放虛可及的對象。 你會發現在收集heap中的new String("abc")對象之前,你就可以做一些其他的事情。通過以下代碼可以了解他的作用。
結果為: class java.lang.String@96354 Red5流媒體服務器簡介 Red5是一個采用Java開發開源的Flash流媒體服務器。它支持:把音頻(MP3)和視頻(FLV)轉換成播放流; 錄制客戶端播放流(只支持FLV);共享對象;現場直播流發布;遠程調用。Red5使用RSTP作為流媒體傳輸協議,在其自帶的一些示例中演示了在線錄制,flash流媒體播放,在線聊天,視頻會議等一些基本功能。 軟件環境 既然是Java開發的,自然少不了要安裝JDK,這里使用的是JDK6.x版本,Red5用的是0.9.1版本,Red5內嵌了Tomcat6.x服務器。為了測試和使用Red5,需要另外搭建開發環境,開發部署相應的服務端應用,開發IDE為Eclipse3.3.x + MyEclipse6.x(貌似版本有點低了,沒辦法,剛好電腦上安裝程序,不想另外下載了,同時也夠用了,哈哈),Web服務器為Tomcat6.x,最后客戶端播放器使用Flowplayer3.2.x。以下是Red5和Flowplayer3.2.x下載地址。 Red5下載:http://www.red5.org/downloads/ 安裝軟件與環境配置 1.安裝JDK。這里使用的是jdk-6u21-windows-i586.exe,雙擊按提示安裝即可 完畢后設置環境變量JAVA_HOME,PATH和CLASSPATH,如何設置環境變量請谷歌或百度 2.安裝Red5 因為是Windows環境,這里下載的是setup-Red5-0.9.1.exe。直接雙擊安裝程序安裝,安裝過程中,需要填寫服務器IP地址和端口,由于是本地測試,直接填寫127.0.0.1,端口隨意,不沖突即可,建議>1024,這里使用5050。安裝完之后不要忘記設置RED5_HOME環境變量。 Red5安裝程序會默認把Red5注冊為系統服務自動啟動,打開系統服務,查看是否服務是否已經存在: 我們看到服務已安裝,但還沒有啟動,需要我們手動啟動一下,選擇Red5服務,鼠標右鍵,選擇啟動或者重新啟動即可。系統界面操作,不贅述。如無意 外,應該可以正常啟動。如果啟動不了,請檢查前面的環境變量設置是否設置完畢并且正確,最后檢查Red5的啟動日志文件,看看是否有相應的提示信息,日志 文件在Red5主目錄下的log目錄下,日志文件有多個,查看red5_service.log即可。啟動后,打開瀏覽器,敲入安裝Red5時的IP地址 和端口,正常情況下,看到如下信息,說明Red5已經正確安裝了。 這個時候可以點擊Install進入下載其官方提供的demo進行研究學習,安裝后的demo文件在Red5根目錄下的webapps下,如 D:\Red5\webapps。安裝操作比較簡單,這里不詳細介紹,不過要這里要提醒一下,安裝完的demo后,需要重新啟動一下Red5服務器,重啟 操作參考上面的介紹。 3.安裝配置開發環境 主要安裝配置Eclipse3.3.x + MyEclipse6.x +Tomcat6.x。 Eclipse下載的是eclipse3.3.1.zip,直接解壓到D:\Program Files目錄下;Tomcat下載apache-tomcat-6.0.36-windows-x86.zip,直接解壓到D:\ProgramFiles目錄即可;然后安裝MyEclipse6.x,這里用的是MyEclipse_6.0.1GA_E3.3.1_Installer.exe,雙擊按提示安裝完畢即可。 配置Tomcat服務器和默認字符集為UTF-8 至此,軟件的安裝與環境配置完成,接下來就是開發和部署我們的流媒體服務器應用以及測試應用了。 信息標注到google地圖上(即在google本地商戶中心登記您的公司信息和標注地理位置): 第一步:核實是否收錄及正確性。 通過google地圖(http://ditu.google.cn/)搜索您的公司,核實google地圖是否已經收錄你的公司信息。如果正確收錄則無需再次添加。如果沒有收錄或者沒有正確收錄則需要添加或者修改不正確的信息。 第二步:錄入公司信息及標注地理位置。 如果要修改已有信息,點擊地圖中公司信息下的“修改”進入本地商戶中心;如果是新增加公司地圖標注,則通過http://bendi.google.com/local/add進入。 錄入基本信息(公司名稱、地址、電話、網址等)、選擇公司類別(最多5類)、可選擇營業時間、可選擇付款方式、可上傳多個圖片(最多10個)、可加載youtube視頻(最多5個)等。如果地圖自動標記的位置有誤可手動調整。 google本地商戶中心信息錄入 第三步:驗證 錄入信息檢查無誤后進入到驗證方式選擇界面,驗證所需的驗證碼(5位數字)可以選擇電話獲取(google自動語音系統撥打您的首選電話)或郵寄信函獲取(google給您郵寄信函)。建議選擇電話方式,可以在幾秒中內獲取驗證碼。(注意需要填寫直撥號碼而不是總機號碼,并在google電話打過來時記錄號驗證碼)。獲取驗證碼后在商戶列表頁中填入驗證碼進行驗證即可,成功驗證后一個小時內您就可以看到貴公司在google地圖中展示的效果了,趕快去試試吧。 驗證方式選擇界面 保存成日志文件形式的時候,大家經常會遇到一個問題:日志文件過大。上百兆的日志文件對 查閱日志信息來說也是一個問題。所以我希望能夠每天或每個月產生一個日志文件,這樣文件不至于過大。 或者根據日志文件大小來判斷,超過規定大小,日志自動增加新文件。 在log4j中這兩種方式的實現都很簡單,只要在配置文件中設置即可。 一、按照一定時間產生日志文件,配置文件如下: # Set root logger level to ERROR and its only appender to A1. log4j.rootLogger=ERROR,R # R is set to be a DailyRollingFileAppender. log4j.appender.R=org.apache.log4j.DailyRollingFileAppender log4j.appender.R.File=backup.log log4j.appender.R.DatePattern = '.'yyyy-MM-dd log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n 以上配置是每天產生一個備份文件。其中備份文件的名字叫backup.log。 具體的效果是這樣:當天的日志信息記錄在backup.log文件中,前一天的記錄在名稱為 backup.log.yyyy-mm-dd 的文件中。 類似的,如果需要每月產生一個文件可以修改上面的配置: 將 log4j.appender.R.DatePattern = '.'yyyy-MM-dd 改為 log4j.appender.R.DatePattern = '.'yyyy-MM 二、根據日志文件大小自動產生新日志文件 配置文件內容如下: # Set root logger level to ERROR and its only appender to A1. log4j.rootLogger=ERROR,R # R is set to be a RollingFileAppender. log4j.appender.R=org.apache.log4j.RollingFileAppender log4j.appender.R.File=backup.log #log4j.appender.R.MaxFileSize=100KB # Keep one backup file log4j.appender.R.MaxBackupIndex=1 log4j.appender.R.layout=org.apache.log4j.PatternLayout log4j.appender.R.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n 其中: #日志文件的大小 log4j.appender.R.MaxFileSize=100KB # 保存一個備份文件 log4j.appender.R.MaxBackupIndex=1 SIP5.0以后服務的請求量爆發性增長,因此也暴露了原來沒有暴露出來的問題。由于過去一般一個新版本發布周期在一個月左右,因此如果是小的內存泄露,在一個月之內重新發布以后也就看不出任何問題。
因此這陣子除了優化Memcache客戶端和SIP框架邏輯以外其他依賴部分以外,對于內存泄露的壓力測試也開始實實在在的做起來。經過這次問題的定位和解決以后,大致覺得對于一個大用戶量應用要放心的話,那么需要做這么幾步。 1. 在GC輸出的環境下,大壓力下做多天的測試。(可以在 JAVA_OPTS增加-verbose:gc -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError) 2. 檢查GC輸出日志來判斷是否有內存泄露。(這部分后面有詳細的實例說明) 3. 如果出現內存泄露問題,則使用jprofiler等工具來排查內存泄露點(之所以不一開始使用,因為jprofiler等工具對于壓力測試有影響,使得大壓力無法上去,也使問題不那么容易暴露) 4. 解決問題,并在重復2步驟。 這里對SIP在jdk1.5和jdk1.6下做壓力測試的GC 日志來做一個實際的分析對比,通過對比來大致描述一下如何根據輸出情況能夠了解應用是否存在內存泄露問題。(這里的內存泄露問題就是在以前blog寫過的jdk的concurrent包內LinkedBlockingQueue的poll方法存在比較嚴重的內存泄露,調用頻率越高,內存泄露的越厲害) 兩次壓力測試都差不多都是兩天,測試方案如下: 開始50個并發,每個并發每次請求完畢后休息0.1秒,10分鐘后增長50個并發,按此規律增長到500并發。 舊版本SIP是在JDK1.5環境下完成的壓力測試, 新版本SIP的JDK版本是1.6, 壓力機和以前一樣,是10.2.226.40,DELL1950,8CPU,8G內存。 壓力機模擬發出對一個需要簽名的API不斷的調用請求。 看看兩個Log的具體內容(內容很多截取部分做分析) 先說一下日志輸出的結構:(1.6和1.5略微有一些不同,只是1.6對于時間統計更加細致) [GC [<collector>: <starting occupancy1> -> <ending occupancy1>, <pause time1> secs] <starting occupancy3> -> <ending occupancy3>, <pause time3> secs] <collector>GC收集器的名稱 <starting occupancy1> 新生代在GC前占用的內存 <ending occupancy1> 新生代在GC后占用的內存 <pause time1> 新生代局部收集時jvm暫停處理的時間 <starting occupancy3> JVM Heap 在GC前占用的內存 <ending occupancy3> JVM Heap 在GC后占用的內存 <pause time3> GC過程中jvm暫停處理的總時間 Jdk1.5 log: 啟動時GC輸出: [GC [DefNew: 209792K->4417K(235968K), 0.0201630 secs] 246722K->41347K(498112K), 0.0204050 secs] [GC [DefNew: 214209K->4381K(235968K), 0.0139200 secs] 251139K->41312K(498112K), 0.0141190 secs] 一句輸出: 新生代回收前209792K,回收后4417K,回收數量205375K,Heap總量回收前246722K回收后41347K,回收總量205375K。這就表示100%的收回,沒有任何新生代的對象被提升到中生代或者永久區(名字說的不一定準確,只是表達意思)。 第二句輸出: 按照分析也就只是有1K內容被提升到中生代。 運行一段時間后: [GC [DefNew: 210686K->979K(235968K), 0.0257140 secs] 278070K->68379K(498244K), 0.0261820 secs] [GC [DefNew: 210771K->1129K(235968K), 0.0275160 secs] 278171K->68544K(498244K), 0.0280050 secs] 第一句輸出: 新生代回收前210686K,回收后979K,回收數量209707K,Heap總量回收前278070K回收后68379K,回收總量209691K。這就表示有16k沒有被回收。 第二句輸出: 新生代回收前210771K,回收后1129K,回收數量209642K,Heap總量回收前278171K回收后68544K,回收總量209627K。這就表示有15k沒有被回收。 比較一下啟動時與現在的新生代占用內存情況和Heap使用情況發現Heap的使用增長很明顯,新生代沒有增長,而Heap使用總量增長了27M,這就表明可能存在內存泄露,雖然每一次泄露的字節數很少,但是頻率很高,大部分泄露的對象都被升級到了中生代或者持久代。 又一段時間后: [GC [DefNew: 211554K->1913K(235968K), 0.0461130 secs] 350102K->140481K(648160K), 0.0469790 secs] [GC [DefNew: 211707K->2327K(235968K), 0.0546170 secs] 350275K->140921K(648160K), 0.0555070 secs] 第一句輸出: 新生代回收前211554K,回收后1913K,回收數量209641K,Heap總量回收前350102K回收后140481K,回收總量209621K。這就表示有20k沒有被回收。 分析到這里就可以看出每一次泄露的內存只有10幾K,但是在大壓力長時間的測試下,內存泄露還是很明顯的,此時Heap已經增長到了140M,較啟動時已經增長了100M。同時GC占用的時間越來越長。 后續的現象: 后續觀察日志會發現,Full GC的頻率越來越高,收集所花費時間也是越來越長。(Full GC定期會執行,同時局部回收不能滿足分配需求的情況下也會執行)。 [Full GC [Tenured: 786431K->786431K(786432K), 3.4882390 secs] 1022399K->1022399K(1022400K), [Perm : 36711K->36711K(98304K)], 3.4887920 secs] java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid7720.hprof ... 出現這個語句表示內存真的被消耗完了。 Jdk1.6 log: 啟動時GC的輸出: [GC [PSYoungGen: 221697K->31960K(229376K)] 225788K->36051K(491520K), 0.0521830 secs] [Times: user=0.26 sys=0.05, real=0.05 secs] [GC [PSYoungGen: 228568K->32752K(229376K)] 232659K->37036K(491520K), 0.0408620 secs] [Times: user=0.21 sys=0.02, real=0.04 secs] 第一句輸出: 新生代回收前221697K,回收后31960K,回收數量189737K,Heap總量回收前225788K回收后36051K,回收總量189737K。100%被回收。 運行一段時間后輸出: [GC [PSYoungGen: 258944K->2536K(259328K)] 853863K->598135K(997888K), 0.0471620 secs] [Times: user=0.15 sys=0.00, real=0.05 secs] [GC [PSYoungGen: 259048K->2624K(259328K)] 854647K->598907K(997888K), 0.0462980 secs] [Times: user=0.16 sys=0.02, real=0.04 secs] 第一句輸出: 新生代回收前258944K,回收后2536K,回收數量256408K,Heap總量回收前853863K回收后598135K,回收總量255728K。680K沒有被回收,但這并不意味著就會產生內存泄露。同時可以看出GC回收時間并沒有增加。 在運行一段時間后輸出: [GC [PSYoungGen: 258904K->2488K(259264K)] 969663K->713923K(1045696K), 0.0485140 secs] [Times: user=0.16 sys=0.01, real=0.04 secs] [GC [PSYoungGen: 258872K->2448K(259328K)] 970307K->714563K(1045760K), 0.0473770 secs] [Times: user=0.16 sys=0.01, real=0.05 secs] 第一句輸出: 新生代回收前258904K,回收后2488K,回收數量256416K,Heap總量回收前969663K回收后713923K,回收總量255740K。676K沒有被回收,同時總的Heap也有所增加。 此時看起來好像和1.5的狀況一樣。但是查看了一下Full GC的執行還是400-500次GC執行一次,因此繼續觀察。 運行一天多以后輸出: [GC [PSYoungGen: 257016K->3304K(257984K)] 1019358K->766310K(1044416K), 0.0567120 secs] [Times: user=0.18 sys=0.01, real=0.06 secs] [GC [PSYoungGen: 257128K->2920K(258112K)] 1020134K->766622K(1044544K), 0.0549570 secs] [Times: user=0.19 sys=0.00, real=0.05 secs] 可以發現Heap增長趨緩。 運行兩天以后輸出: [GC [PSYoungGen: 256936K->3584K(257792K)] 859561K->606969K(1044224K), 0.0565910 secs] [Times: user=0.18 sys=0.01, real=0.06 secs] [GC [PSYoungGen: 256960K->3368K(257728K)] 860345K->607445K(1044160K), 0.0553780 secs] [Times: user=0.18 sys=0.01, real=0.06 secs] 發現Heap反而減少了,此時可以對內存泄露問題作初步排除了。(其實在jdk1.6環境下用jprofiler來觀察,對于concurrent那個內存泄露點的跟蹤發現,內存的確還是會不斷增長的,不過在一段時間后還是有回收,因此也就可以部分解釋前面出現的情況) 總結: 對于GC輸出的觀察需要分兩個維度來看。一個是縱向比較,也就是一次回收對于內存變化的觀察。一個是橫向比較,對于長時間內存分配占用情況的比較,這部分比較需要較長時間的觀察,不能僅僅憑短時間的幾個抽樣比較,因為對于抽樣來說,Full GC前后的區別,運行時長的區別,資源瞬時占用的區別都會影響判斷。同時要結合Full GC發生的時間周期,每一次GC收集所耗費的時間作為輔助判斷標準。 順便說一下,Heap的 YoungGen,OldGen,PermGen的設置也是需要注意的,并不是越大越好,越大執行收集的時間越久,但是可能執行Full GC的頻率會比較低,因此需要權衡。這些仔細的去了解一下GC的基礎設計思想會更有幫助,不過一般用默認的也不錯。還有就是可以配置一些特殊的GC,并行,同步等等,充分利用多CPU的資源。 對于GC的優化可以通過現在很多圖形工具來做,也可以類似于我這樣采用最原始的分析方式,好處就是任何時間任何地點只要知道原理就可以分析無需借助外部工具。原始的總是最好的^_^。
摘要: 1、Java虛擬機運行時的數據區2、常用的內存區域調節參數-Xms:初始堆大小,默認為物理內存的1/64(<1GB);默認(MinHeapFreeRatio參數可以調整)空余堆內存小于40%時,JVM就會增大堆直到-Xmx的最大限制-Xmx:最大堆大小,默認(MaxHeapFreeRatio參數可以調整)空余堆內存大于70%時,JVM會減少堆直到 -Xms的最小限制-Xmn:新生代的內存空間... 閱讀全文
性能測試排查定位問題,分析調優過程中,會遇到要分析gc日志,人肉分析gc日志有時比較困難,相關圖形化或命令行工具可以有效地幫助輔助分析。 Gc日志參數 通過在tomcat啟動腳本中添加相關參數生成gc日志 -verbose.gc開關可顯示GC的操作內容。打開它,可以顯示最忙和最空閑收集行為發生的時間、收集前后的內存大小、收集需要的時間等。 打開-xx:+ printGCdetails開關,可以詳細了解GC中的變化。 打開-XX: + PrintGCTimeStamps開關,可以了解這些垃圾收集發生的時間,自JVM啟動以后以秒計量。 最后,通過-xx: + PrintHeapAtGC開關了解堆的更詳細的信息。 為了了解新域的情況,可以通過-XX:=PrintTenuringDistribution開關了解獲得使用期的對象權。 -Xloggc:$CATALINA_BASE/logs/gc.log gc日志產生的路徑 XX:+PrintGCApplicationStoppedTime // 輸出GC造成應用暫停的時間 -XX:+PrintGCDateStamps // GC發生的時間信息
Gc日志 日志中顯示了gc發生的時間,young區回收情況,整體回收情況,fullGC情況,回收所消耗時間等
常用JVM參數 分析gc日志后,經常需要調整jvm內存相關參數,常用參數如下 -Xms:初始堆大小,默認為物理內存的1/64(<1GB);默認(MinHeapFreeRatio參數可以調整)空余堆內存小于40%時,JVM就會增大堆直到-Xmx的最大限制 -Xmx:最大堆大小,默認(MaxHeapFreeRatio參數可以調整)空余堆內存大于70%時,JVM會減少堆直到 -Xms的最小限制 -Xmn:新生代的內存空間大小,注意:此處的大小是(eden+ 2 survivor space)。與jmap -heap中顯示的New gen是不同的。整個堆大小=新生代大小 + 老生代大小 + 永久代大小。 -XX:SurvivorRatio:新生代中Eden區域與Survivor區域的容量比值,默認值為8。兩個Survivor區與一個Eden區的比值為2:8,一個Survivor區占整個年輕代的1/10。 -Xss:每個線程的堆棧大小。JDK5.0以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。應根據應用的線程所需內存大小進行適當調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。一般小的應用, 如果棧不是很深, 應該是128k夠用的,大的應用建議使用256k。這個選項對性能影響比較大,需要嚴格的測試。和threadstacksize選項解釋很類似,官方文檔似乎沒有解釋,在論壇中有這樣一句話:"-Xss is translated in a VM flag named ThreadStackSize”一般設置這個值就可以了。 -XX:PermSize:設置永久代(perm gen)初始值。默認值為物理內存的1/64。 -XX:MaxPermSize:設置持久代最大值。物理內存的1/4。
Gc日志分析工具 (1)GCHisto http://java.net/projects/gchisto
直接點擊gchisto.jar就可以運行,點add載入gc.log 統計了總共gc次數,youngGC次數,FullGC次數,次數的百分比,GC消耗的時間,百分比,平均消耗時間,消耗時間最小最大值等 ![]() 統計的圖形化表示 ![]() YoungGC,FullGC不同消耗時間上次數的分布圖,勾選可以顯示youngGC或fullGC單獨的分布情況 ![]() 整個時間過程詳細的gc情況,可以對整個過程進行剖析 ![]()
(2)GCLogViewer
http://code.google.com/p/gclogviewer/
點擊run.bat運行
整個過程gc情況的趨勢圖,還顯示了gc類型,吞吐量,平均gc頻率,內存變化趨勢等 Tools里還能比較不同gc日志 ![]() (3)HPjmeter
獲取地址 http://www.hp.com/go/java
工具很強大,但只能打開由以下參數生成的GC log, -verbose:gc -Xloggc:gc.log,添加其他參數生成的gc.log無法打開。
(4)GCViewer http://www.tagtraum.com/gcviewer.html 這個工具用的挺多的,但只能在JDK1.5以下的版本中運行,1.6以后沒有對應。 (5)garbagecat http://code.google.com/a/eclipselabs.org/p/garbagecat/wiki/Documentation
其它監控方法 Jvisualvm動態分析jvm內存情況和gc情況,插件:visualGC ![]() ![]() jvisualvm還可以heapdump出對應hprof文件(默認存放路徑:監控的服務器 /tmp下),利用相關工具,比如HPjmeter可以對其進行分析 grep Full gc.log粗略觀察FullGC發生頻率 jstat –gcutil [pid] [intervel] [count] jmap -histo pid可以觀測對象的個數和占用空間 jprofiler,jmap dump出來用MAT分析
如果要分析的dump文件很大的話,就需要很多內存,很容易crash。 所以在啟動時,我們應該加上一些參數: Java –Xms512M –Xmx1024M –Xss8M |