調優學習筆記

兩種測試範圍

微基準性能測試

微基準性能測試可以精準定位到某個模塊或者某個方法的性能問題,特別適合做一個功能模塊或者一個方法在不同實現方式下的性能對比。例如,對比一個方法使用同步實現和非同步實現的性能。
微服務下由單元測試

宏基準性能測試

宏基準性能測試是一個綜合測試,需要考慮到測試環境、測試場景和測試目標。

String注意事項

String.intern減少堆內存使用量

在類中,如果String是被重複使用的情況下,:

Location.setCity(messageInfo.getCity().intern());		
Location.setCountryCode(messageInfo.getRegion().intern());

String.split 注意事項

split 兩種不使用正則表達式進行拆分的情況

第一種爲傳入的參數長度爲1,且不包含“.$|()[{^?*+\”regex元字符的情況下,不會使用正則表達式;
第二種爲傳入的參數長度爲2,第一個字符是反斜槓,並且第二個字符不是ASCII數字或ASCII字母的情況下,不會使用正則表達式。

split使用正則表達式導致慢的情況下,需優化正則表達式

正則表達式優化

減少捕獲嵌套

正則表達式減少使用()組捕獲的嵌套

儘量減少貪婪模式,多使用獨佔模式

貪婪模式 regex=“ab{1,3}c” ,對於"abbc" 的NFA自動機會產生回溯,先使用abbb跟"abb"匹配,然後回溯爲abb匹配,匹配成功後,再依次向後匹配
惰性模式 regex=“ab{1,3}?c” 使用?開啓惰性模式,惰性模式下,會先以最少量的b去匹配,一旦匹配成功,就向後匹配,避免回溯。(先ab 然後abb 然後abbb)
獨佔模式regex=“ab{1,3}+c” 使用+開啓獨佔模式,獨佔模式下,會以最多的b去匹配,一旦匹配成功,就先後匹配避免回溯。
例子:
使用“ab{1,3}+c” 匹配abbc

ArrayList 還是 KinkedList

在不發生擴容的情況下:
從集合頭部新增元素 ArrayList 慢於 LinkedList
中部和尾部新增元素ArrayLisy快於LinkedList

在元素容量已知,初始化容量足夠的情況下,元素基本從尾部插入的情況下,使用ArrayList。
在元素容量未知,動態擴容的情況下,使用LinkedList。

需要知道元素位置的情況下,使用ArrayList。
如果只使用迭代器循環的情況下,兩List查詢效率幾乎相同。

HashMap注意事項

降低hash衝突,從而減少鏈表的產生
爲什麼hash算法要採用對象的hashcode = (ObjectHash) ^ (ObjectHash>>>16);注 hashcode 爲32位下
目的是儘可能的打亂參與運算的低位。
(容量-1)&hashcode
設置好的初始容量及加載因子,降低擴容帶來的問題。

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
//1、判斷當 table 爲 null 或者 tab 的長度爲 0 時,即 table 尚未初始化,此時通過 resize() 方法得到初始化的 table
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
//1.1、此處通過(n - 1) & hash 計算出的值作爲 tab 的下標 i,並另 p 表示 tab[i],也就是該鏈表第一個節點的位置。並判斷 p 是否爲 null
            tab[i] = newNode(hash, key, value, null);
//1.1.1、當 p 爲 null 時,表明 tab[i] 上沒有任何元素,那麼接下來就 new 第一個 Node 節點,調用 newNode 方法返回新節點賦值給 tab[i]
        else {
//2.1 下面進入 p 不爲 null 的情況,有三種情況:p 爲鏈表節點;p 爲紅黑樹節點;p 是鏈表節點但長度爲臨界長度 TREEIFY_THRESHOLD,再插入任何元素就要變成紅黑樹了。
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
//2.1.1HashMap 中判斷 key 相同的條件是 key 的 hash 相同,並且符合 equals 方法。這裏判斷了 p.key 是否和插入的 key 相等,如果相等,則將 p 的引用賦給 e

                e = p;
            else if (p instanceof TreeNode)
//2.1.2 現在開始了第一種情況,p 是紅黑樹節點,那麼肯定插入後仍然是紅黑樹節點,所以我們直接強制轉型 p 後調用 TreeNode.putTreeVal 方法,返回的引用賦給 e
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
//2.1.3 接下里就是 p 爲鏈表節點的情形,也就是上述說的另外兩類情況:插入後還是鏈表 / 插入後轉紅黑樹。另外,上行轉型代碼也說明了 TreeNode 是 Node 的一個子類
                for (int binCount = 0; ; ++binCount) {
// 我們需要一個計數器來計算當前鏈表的元素個數,並遍歷鏈表,binCount 就是這個計數器

                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) 
// 插入成功後,要判斷是否需要轉換爲紅黑樹,因爲插入後鏈表長度加 1,而 binCount 並不包含新節點,所以判斷時要將臨界閾值減 1
                            treeifyBin(tab, hash);
// 當新長度滿足轉換條件時,調用 treeifyBin 方法,將該鏈表轉換爲紅黑樹
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

序列化

Java 提供的RMI等框架的默認序列化方式爲Java 序列化,1. 無法跨語言 2.易被攻擊 (可以被反序列化程任何對象實例,從而執行任意類型的代碼 創建循環對象鏈)3.序列化流太大 4.序列化性能太差
JDK 提供的輸入輸出流對象 ObjectInputStream和ObjectOutputStream,他們只能對實現了Serializable接口的類的對象進行反序列化和序列化。

serialVersionUID作用

反序列化過程中 驗證 序列化對象是否加載了反序列化的類,相同類名不同版本號,反序列化無法獲取對象。

實現序列化的時writeObject和readObject

具體實現序列化的時writeObject和readObject,通常爲默認,但是對於數組,鏈表等數據結構,還要序列化所屬元素,故需要自己實現Serialiazable接口並進行重寫這兩個方法。

TCP參數設置選項

/etc/sysctl.conf 配置
ulimit 最大連接數
net.ipv4.tcp_keepalive_time 檢測客戶端連接狀態時間
net.ipv4.tcp_max_syn_backlog 當SYN等待隊列溢出時,啓動cookies來處理。
net.ipv4.ip_local_port_range 默認TCP端口爲 32768-61000 可以擴大該範圍
net.ipv4.tcp_max_tw_buckets 當一個連接關閉時,TCP會通過四次握手來完成一次關閉連接操作。在請求量比較大的情況下,消費端會有大量TIME_WAIT狀態的連接。該參數可以限制TIME_WAIT狀態連接數量(數量超過該值時,會被立刻清除並打印警告信息)
net.ipv4.tcp_tw_resuse 一個TIME_WAIT端口被重複複用的間隔時間(與四部握手關閉有關,防止前一個關閉端口的信息發送到新端口)

I/O複用
select

調用後開始阻塞,當異常或就緒時,函數返回。

 int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
 返回寫文件描述符 讀文件描述符 異常事件文件描述符
poll

與select類似,無最大文件描述符數量限制。

select與poll共通

需要將大量文件描述符數組被複制到用戶態和內核的地址空間之間,無論這些文件描述符是否就緒,他們的開銷都會隨着文件描述符數量增大而線性增大。並且順序順序輪詢fd數組中的fd是否就緒,而且支持的fd數量不宜過大。

epoll

時間驅動的方式代替輪詢掃描,epoll_ctl()註冊文件描述符,放到內核時間表(紅黑樹),插入和刪除性能較好。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event event)
op 代表操作事件類型
fd 關聯文件描述符
event 監聽指定事件類型
應用進程調用epoll_wait()被文件描述符就緒事件給激活
int epoll_wait(int epfd, struct epoll_event events,int maxevents,int timeout)
阻塞直到文件描述符就緒時
NIO 用戶層模型
Acceptor(事件接收器)

主要負責接受請求連接

Reactor(事件分離器)

接受請求後,建立連接註冊到分離器中,依賴循環監聽多路複用器Selector,一旦監聽到事件,就會將事件dispatch到事件處理器;

基於線程模型的 Tomcat 參數調優

Tomcat 中,BIO、NIO 是基於主從 Reactor 線程模型實現的。
在 BIO 中,Tomcat 中的 Acceptor 只負責監聽新的連接,一旦連接建立監聽到 I/O 操作,將會交給 Worker 線程中,Worker 線程專門負責 I/O 讀寫操作。
在 NIO 中,Tomcat 新增了一個 Poller 線程池,Acceptor 監聽到連接後,不是直接使用 Worker 中的線程處理請求,而是先將請求發送給了 Poller 緩衝隊列。在 Poller 中,維護了一個 Selector 對象,當 Poller 從隊列中取出連接後,註冊到該 Selector 中;然後通過遍歷 Selector,找出其中就緒的 I/O 操作,並使用 Worker 中的線程處理相應的請求。
你可以通過以下幾個參數來設置 Acceptor 線程池和 Worker 線程池的配置項。
acceptorThreadCount:該參數代表 Acceptor 的線程數量,在請求客戶端的數據量非常巨大的情況下,可以適當地調大該線程數量來提高處理請求連接的能力,默認值爲 1。
maxThreads:專門處理I/O操作的Worker線程數量,默認200。
acceptCount:Tomcat 的 Acceptor 線程是負責從 accept 隊列中取出該 connection,然後交給工作線程去執行相關操作,這裏的 acceptCount 指的是 accept 隊列的大小。

vmstat命令查看虛擬機線程運行情況(主要CS)
procs r:等待運行的進程數 b:處於非終端睡眠狀態的進程數
memory swpd:虛擬內存使用情況 free:空閒的內存 buff:用來作爲緩衝的內存數 cache:緩存大小
swap si:從磁盤交換到內存的交換頁數量 so:從內存交換到磁盤的交換頁數量
io bi:發送到塊設備的快數 bo:從塊設備接收到的塊數
sysyemin in:每秒中斷數 cs:每秒上下文切換次數
cpu us:用戶cpu時間 sy:內核cpu時間 id:空閒時間 wa:等待I/O時間 st:運行虛擬機竊取的時間
pidstat查看具體線程上下文切換
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章