1. 程式人生 > >分析一個bug,竟然還被誤導

分析一個bug,竟然還被誤導

現象:

     線上一個應用在使用css掃描時經常出現cpu被吃掉的情況 ,一會吃掉一個核,一會吃掉一個核 。分析肯定是某種條件下觸發了死迴圈或大量耗時cpu操作。

於是用jstack拿到執行緒棧:

直接定位到270行:

第一印象是迴圈問題,但是進入this.nextChar()後發現,有一個buf是動態擴容的:

	public int nextChar() throws IOException {
		current = reader.read();

		if (current == -1) {
			return current;
		}

		if (position == buffer.length) {
			// list is full, grow to 1.5 * size
			char[] t = new char[1 + position + position / 2];
			System.arraycopy(buffer, 0, t, 0, position);
			buffer = t;
		}

		return buffer[position++] = (char) current;
	}

如果有種特殊情況,如在html中,只有<style>沒有</style>那麼從<style>開始到結束就都作為<style>的內容送進來來掃描,而這些內容又不是真正的css,所以可能一直找不到  :和;這些分隔,那麼需要的buffer就會很大,而初始只有128的陣列就會不斷擴容和System.arrayCopy。

所以第一時間優化這段程式,加大buffer初始值,減少copy,但沒有效果,上去還是很快就將cpu吃掉。

一直把buffer設定為10240,並判斷position >= 10240就丟擲異常,不產生一次擴容和copy,仍然沒有解決問題。

重新審視問題,準備拿樣本,在出現部分cpu核 被吃掉時拿dump:

又被這個font-size:20**誤導了,以為是很長,直接定位到它的地址:0x760352f88,發現:


也以為是很長,後面的**是省略了。

於是直接寫程式讀這一段地址開始的20k欄位,然後再打開發面原來就是"font-sizt:20**", 我去,也沒去注意那個48位元組的說明。

直接拿這個樣本去除錯,果然死迴圈了:

原來while (CSSScannerUtilities.isCSSNameCharacter((char) current)) 這裡,在20**表示式出錯以後,為了容錯會去讀";",之前的跳過,從:開始繼續解析。這樣是為了能儘量多解析一些正確的內容出來,可是這裡 20** 這個表示式出錯後,就到讀了-1。

(char)-1變成了65535,CSSScannerUtilities.isCSSNameCharacter(65535)返回true,一直讀到-1,一直迴圈下去。

修復當然容易了:

while (current != -1 && CSSScannerUtilities.isCSSNameCharacter((char) current))

 果然,一切都安靜了。

這個案子被誤導的第一個地方是死迴圈出現時進入nextChar方法中,假想了一個內容很長情況,就去優化buffer,雖然這個情況有可能存在,但卻不是真實的。

第二個誤導被第一個誤導先入了,是看到**就覺得這個樣本應該是很長很長,**是省略顯示,沒想到樣本就是 "font-sizt:20**",造成異常而觸發bug。