最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot程序CPU飆升的排查指南

 更新時間:2025年08月28日 08:26:39   作者:風(fēng)象南  
在生產(chǎn)環(huán)境里,大家或多或少都遇到過這種場景:某個 Spring Boot 應(yīng)用突然 CPU 飆升,打滿服務(wù)器資源,監(jiān)控報警狂響,業(yè)務(wù)接口開始超時,登錄服務(wù)器,top一看,只能看到是 java 進(jìn)程在耗 CPU,所以本文給大家介紹了SpringBoot程序CPU飆升的排查指南,需要的朋友可以參考下

在生產(chǎn)環(huán)境里,大家或多或少都遇到過這種場景:

某個 Spring Boot 應(yīng)用突然 CPU 飆升,打滿服務(wù)器資源;

監(jiān)控報警狂響,業(yè)務(wù)接口開始超時;

登錄服務(wù)器,top 一看,只能看到是 java 進(jìn)程在耗 CPU;

再往下就蒙了 —— 到底是哪段代碼在吃 CPU?

如果你和我一樣,不想每次都靠運(yùn)維層面“重啟大法”,而是希望快速定位到 具體的類和方法,那你就需要一把更鋒利的武器。

本文要介紹的就是:如何在 SpringBoot 程序里,自制一個 方法級采樣火焰圖工具,3 分鐘鎖定 CPU 熱點(diǎn)。

一、痛點(diǎn)分析

常見的排查思路:

  • top  /  htop:只能看到進(jìn)程或線程 ID,定位不到代碼。
  • jstack:能 dump 出線程棧,但靜態(tài)快照往往抓不到真正的熱點(diǎn)方法(CPU 飆升的時候,線程可能在不停切換)。
  • async-profiler   arthas:功能很強(qiáng)大,但對于一些沒有安裝權(quán)限的生產(chǎn)環(huán)境,落地成本比較高。

所以我的需求是:

輕量化:不用復(fù)雜部署,應(yīng)用自身就能帶。

方法級采樣:不僅要看到類名,還要定位到具體的方法。

火焰圖可視化:一眼看出熱點(diǎn),而不是一堆堆日志。

二、思路設(shè)計

核心思路其實(shí)很簡單:

1、在應(yīng)用運(yùn)行時,定時對線程棧進(jìn)行采樣;

2、把每次采樣到的「調(diào)用棧」進(jìn)行統(tǒng)計和合并;

3、輸出為 火焰圖格式數(shù)據(jù)(一般是 Flame Graph 的 stack collapse 格式);

4、前端頁面用現(xiàn)成的 JS 庫(比如 d3-flame-graph)渲染成交互式圖譜。

這樣,我們就能在瀏覽器里看到:哪些方法被調(diào)用得最多,消耗了 CPU。

三、技術(shù)選型

采樣方式:基于 JDK 自帶的 ThreadMXBean + getThreadInfo,周期性拉取棧幀;

數(shù)據(jù)存儲:用內(nèi)存里的 ConcurrentHashMap 統(tǒng)計調(diào)用棧頻次;

可視化輸出:提供一個 /flamegraph HTTP 接口,返回 collapse 格式數(shù)據(jù);

前端展示:引入 d3-flame-graph繪制火焰圖。

四、關(guān)鍵實(shí)現(xiàn)

1. 采樣器

@Component
public class CpuSampler {

    private final ScheduledExecutorService executor =
            Executors.newSingleThreadScheduledExecutor();

    private final Map<String, AtomicInteger> stackCount = new ConcurrentHashMap<>();

    @PostConstruct
    public void start() {
        executor.scheduleAtFixedRate(this::sample, 0, 50, TimeUnit.MILLISECONDS);
    }

    private void sample() {
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        for (long tid : threadMXBean.getAllThreadIds()) {
            ThreadInfo info = threadMXBean.getThreadInfo(tid, Integer.MAX_VALUE);
            if (info == null) continue;

            StringBuilder sb = new StringBuilder();
            for (StackTraceElement frame : info.getStackTrace()) {
                sb.append(frame.getClassName())
                  .append(".")
                  .append(frame.getMethodName())
                  .append(";");
            }
            String stackLine = sb.toString();
            stackCount.computeIfAbsent(stackLine, k -> new AtomicInteger(0)).incrementAndGet();
        }
    }

    public Map<String, AtomicInteger> getStackCount() {
        return stackCount;
    }
}

2. 提供 HTTP 接口

@RestController
public class FlameGraphController {

    @Autowired
    private CpuSampler sampler;

    @GetMapping("/flamegraph")
    public String getFlameGraphData() {
        StringBuilder sb = new StringBuilder();
        sampler.getStackCount().forEach((stack, count) -> {
            // flamegraph 的輸入格式: method1;method2;method3 count
            sb.append(stack).append(" ").append(count.get()).append("\n");
        });
        return sb.toString();
    }
}

3. 前端展示

一個簡單的 HTML 頁面即可:

<script>
    let flameChart = null;
    let flameGraphFactory = null; // 添加全局變量
    let samplingEnabled = false;

    // 初始化
    document.addEventListener('DOMContentLoaded', function() {
        // 等待一段時間確保庫完全加載
        setTimeout(function() {
            console.log('檢查庫加載狀態(tài)...');
            console.log('d3:', typeof d3);
            console.log('flamegraph:', typeof flamegraph);
            console.log('d3.flamegraph:', typeof (d3 && d3.flamegraph));
            console.log('window對象上的flame相關(guān)屬性:', Object.keys(window).filter(key => key.toLowerCase().includes('flame')));
            console.log('d3對象的屬性:', d3 ? Object.keys(d3).filter(key => key.toLowerCase().includes('flame')) : 'd3未定義');
            
            if (typeof d3 === 'undefined') {
                showError('D3.js庫未加載成功');
                return;
            }
            
            // 嘗試不同的API訪問方式
            if (typeof flamegraph !== 'undefined') {
                console.log('使用全局flamegraph函數(shù)');
                flameGraphFactory = flamegraph;
            } else if (d3 && typeof d3.flamegraph !== 'undefined') {
                console.log('使用d3.flamegraph函數(shù)');
                flameGraphFactory = d3.flamegraph;
            } else if (d3 && typeof d3.flameGraph !== 'undefined') {
                console.log('使用d3.flameGraph函數(shù)');
                flameGraphFactory = d3.flameGraph;
            } else if (typeof window.flamegraph !== 'undefined') {
                console.log('使用window.flamegraph函數(shù)');
                flameGraphFactory = window.flamegraph;
            } else if (window.flamegraph && window.flamegraph.flamegraph) {
                console.log('使用window.flamegraph.flamegraph函數(shù)');
                flameGraphFactory = window.flamegraph.flamegraph;
            } else {
                console.error('未找到flamegraph函數(shù),可用的全局變量:', Object.keys(window).filter(k => k.includes('flame')));
                showError('d3-flame-graph庫加載成功但API不匹配');
                return;
            }
            
            if (flameGraphFactory) {
                try {
                    initFlameGraph(flameGraphFactory);
                } catch (e) {
                    console.error('初始化火焰圖失敗:', e);
                    showError('火焰圖初始化失敗: ' + e.message);
                }
            }
            
            updateStatus();
            
            // 綁定事件
            document.getElementById('refreshBtn').onclick = refreshFlameGraph;
            document.getElementById('enableBtn').onclick = enableSampling;
            document.getElementById('disableBtn').onclick = disableSampling;
            document.getElementById('clearBtn').onclick = clearData;
            document.getElementById('debugBtn').onclick = showDebugInfo;
            document.getElementById('rawDataBtn').onclick = showRawData;
            
            // 自動刷新狀態(tài)
            setInterval(updateStatus, 5000);
        }, 1000);
    });

    function initFlameGraph(flameGraphFactory) {
        console.log('開始初始化火焰圖,工廠函數(shù):', flameGraphFactory);
        
        try {
            // 使用d3-flame-graph v4的正確API
            flameChart = flameGraphFactory()
                .width(Math.max(960, window.innerWidth - 80))
                .cellHeight(18)
                .transitionDuration(750)
                .minFrameSize(1)
                .sort(true);
                
            console.log('火焰圖初始化成功,實(shí)例:', flameChart);
        } catch (e) {
            console.error('火焰圖初始化過程中出錯:', e);
            throw e;
        }
    }

    async function updateStatus() {
        try {
            const response = await fetch('/api/sampling/status');
            const data = await response.json();
            
            samplingEnabled = data.enabled;
            const statusElement = document.getElementById('status');
            const dataCountElement = document.getElementById('dataCount');
            
            if (samplingEnabled) {
                statusElement.textContent = '采樣運(yùn)行中';
                statusElement.className = 'status enabled';
            } else {
                statusElement.textContent = '采樣已停止';
                statusElement.className = 'status disabled';
            }
            
            dataCountElement.textContent = `數(shù)據(jù)量: ${data.stackCountSize}`;
        } catch (error) {
            console.error('Error updating status:', error);
        }
    }

    async function refreshFlameGraph() {
        const loadingElement = document.getElementById('loading');
        const noDataElement = document.getElementById('noData');
        const chartElement = document.getElementById('chart');
        
        loadingElement.style.display = 'block';
        noDataElement.style.display = 'none';
        chartElement.innerHTML = '';
        
        try {
            const response = await fetch('/api/flamegraph');
            const data = await response.text();
            
            loadingElement.style.display = 'none';
            
            if (!data.trim() || data.trim().startsWith('#')) {
                noDataElement.style.display = 'block';
                return;
            }
            
            // 檢查火焰圖是否已經(jīng)初始化
            if (!flameChart) {
                showError('火焰圖未初始化,請刷新頁面重試');
                return;
            }
            
            try {
                console.log('原始數(shù)據(jù)長度:', data.length);
                console.log('原始數(shù)據(jù)前500字符:', data.substring(0, 500));
                console.log('flameGraphFactory:', flameGraphFactory);
                
                // 手動解析collapsed格式數(shù)據(jù)
                const lines = data.trim().split('\n').filter(line => line.trim() && !line.startsWith('#'));
                if (lines.length === 0) {
                    noDataElement.style.display = 'block';
                    showError('沒有有效的采樣數(shù)據(jù)');
                    return;
                }
                
                console.log('有效數(shù)據(jù)行數(shù):', lines.length);
                console.log('前5行數(shù)據(jù):', lines.slice(0, 5));
                
                // 構(gòu)建火焰圖數(shù)據(jù)結(jié)構(gòu) - 簡化格式
                const root = {
                    name: "all",
                    children: [],
                    value: 0
                };
                
                const pathMap = new Map();
                pathMap.set("", root);
                
                lines.forEach(line => {
                    const parts = line.trim().split(' ');
                    if (parts.length < 2) return;
                    
                    const count = parseInt(parts[parts.length - 1]);
                    if (isNaN(count) || count <= 0) return;
                    
                    const stackTrace = parts.slice(0, -1).join(' ');
                    const methods = stackTrace.split(';').filter(m => m.trim());
                    
                    if (methods.length === 0) return;
                    
                    let currentPath = "";
                    let currentNode = root;
                    
                    methods.forEach((method) => {
                        const newPath = currentPath + (currentPath ? ";" : "") + method;
                        
                        if (!pathMap.has(newPath)) {
                            const newNode = {
                                name: method,
                                children: [],
                                value: count
                            };
                            pathMap.set(newPath, newNode);
                            currentNode.children.push(newNode);
                        } else {
                            pathMap.get(newPath).value += count;
                        }
                        
                        currentNode = pathMap.get(newPath);
                        currentPath = newPath;
                    });
                });
                
                // 計算總值
                function calculateTotals(node) {
                    let total = node.value || 0;
                    if (node.children) {
                        for (const child of node.children) {
                            total += calculateTotals(child);
                        }
                    }
                    node.value = total;
                    return total;
                }
                calculateTotals(root);
                
                console.log('構(gòu)建的火焰圖數(shù)據(jù):', root);
                console.log('根節(jié)點(diǎn)children數(shù)量:', root.children.length);
                console.log('根節(jié)點(diǎn)value:', root.value);
                console.log('子節(jié)點(diǎn)詳情:', root.children.map(c => ({name: c.name, value: c.value, childrenCount: c.children.length})));
                
                if (root.children.length === 0) {
                    noDataElement.style.display = 'block';
                    showError('沒有找到有效的調(diào)用棧數(shù)據(jù)');
                    return;
                }
                
                // 清空并準(zhǔn)備chart容器
                const chartElement = document.getElementById('chart');
                chartElement.innerHTML = '';
                console.log('Chart容器已清空');
                
                try {
                    // 直接渲染火焰圖,不使用復(fù)雜的檢查
                    console.log('開始渲染火焰圖...');
                    
                    d3.select("#chart")
                        .datum(root)
                        .call(flameChart);
                        
                    console.log('火焰圖渲染調(diào)用完成');
                    
                } catch (renderError) {
                    console.error('火焰圖渲染時出錯:', renderError);
                    showError('火焰圖渲染失敗: ' + renderError.message);
                    showTreeView(root);
                }
                    
                console.log('火焰圖渲染完成');
                    
            } catch (parseError) {
                console.error('Error parsing flame graph data:', parseError);
                showError('解析火焰圖數(shù)據(jù)時出錯: ' + parseError.message);
            }
            
        } catch (error) {
            loadingElement.style.display = 'none';
            showError('獲取火焰圖數(shù)據(jù)失敗: ' + error.message);
            console.error('Error refreshing flame graph:', error);
        }
    }

    function showDataAsTable(data) {
        const lines = data.trim().split('\n').filter(line => line.trim() && !line.startsWith('#'));
        
        let tableHtml = `
            <div style="max-height: 400px; overflow-y: auto;">
                <h4>?? CPU采樣數(shù)據(jù) (表格形式)</h4>
                <table style="width: 100%; border-collapse: collapse; font-size: 12px;">
                    <thead>
                        <tr style="background: #f0f0f0;">
                            <th style="border: 1px solid #ddd; padding: 8px; text-align: left;">調(diào)用棧</th>
                            <th style="border: 1px solid #ddd; padding: 8px; text-align: right;">采樣次數(shù)</th>
                        </tr>
                    </thead>
                    <tbody>
        `;
        
        lines.slice(0, 50).forEach(line => {
            const parts = line.trim().split(' ');
            if (parts.length < 2) return;
            
            const count = parts[parts.length - 1];
            const stack = parts.slice(0, -1).join(' ');
            
            tableHtml += `
                <tr>
                    <td style="border: 1px solid #ddd; padding: 4px; font-family: monospace;">${stack}</td>
                    <td style="border: 1px solid #ddd; padding: 4px; text-align: right;">${count}</td>
                </tr>
            `;
        });
        
        if (lines.length > 50) {
            tableHtml += `<tr><td colspan="2" style="text-align: center; padding: 8px;">... 還有 ${lines.length - 50} 條數(shù)據(jù)</td></tr>`;
        }
        
        tableHtml += `
                    </tbody>
                </table>
            </div>
        `;
        
        document.getElementById('chart').innerHTML = tableHtml;
    }

    function showTreeView(root) {
        console.log('顯示樹狀視圖');
        
        function renderNode(node, depth = 0) {
            const indent = '  '.repeat(depth);
            const percentage = root.value > 0 ? ((node.value / root.value) * 100).toFixed(1) : '0.0';
            let html = `<div style="margin-left: ${depth * 20}px; padding: 2px 0; font-family: monospace; font-size: 12px;">`;
            html += `<span style="color: #666;">${percentage}%</span> `;
            html += `<span style="color: #333;">${node.name}</span> `;
            html += `<span style="color: #999;">(${node.value})</span>`;
            html += `</div>`;
            
            if (node.children && node.children.length > 0) {
                // 按value排序,顯示最重要的調(diào)用
                const sortedChildren = [...node.children].sort((a, b) => b.value - a.value);
                for (const child of sortedChildren.slice(0, 10)) { // 只顯示前10個
                    html += renderNode(child, depth + 1);
                }
                if (sortedChildren.length > 10) {
                    html += `<div style="margin-left: ${(depth + 1) * 20}px; padding: 2px 0; color: #999; font-size: 11px;">... ${sortedChildren.length - 10} more</div>`;
                }
            }
            
            return html;
        }
        
        let treeHtml = `
            <div style="max-height: 600px; overflow-y: auto; border: 1px solid #ddd; padding: 15px; background: #f9f9f9;">
                <h4 style="margin: 0 0 15px 0;">?? CPU調(diào)用棧樹狀視圖</h4>
                <div style="font-size: 11px; color: #666; margin-bottom: 10px;">
                    格式: 百分比 方法名 (采樣次數(shù)) | 按CPU占用排序
                </div>
        `;
        
        if (root.children && root.children.length > 0) {
            const sortedChildren = [...root.children].sort((a, b) => b.value - a.value);
            for (const child of sortedChildren) {
                treeHtml += renderNode(child);
            }
        }
        
        treeHtml += `</div>`;
        
        document.getElementById('chart').innerHTML = treeHtml;
    }

    async function enableSampling() {
        try {
            const response = await fetch('/api/sampling/enable', { method: 'POST' });
            const data = await response.json();
            showSuccess(data.message);
            await updateStatus();
        } catch (error) {
            showError('啟用采樣失敗: ' + error.message);
        }
    }

    async function disableSampling() {
        try {
            const response = await fetch('/api/sampling/disable', { method: 'POST' });
            const data = await response.json();
            showSuccess(data.message);
            await updateStatus();
        } catch (error) {
            showError('停止采樣失敗: ' + error.message);
        }
    }

    async function clearData() {
        if (!confirm('確定要清空所有采樣數(shù)據(jù)嗎?')) {
            return;
        }
        
        try {
            const response = await fetch('/api/sampling/clear', { method: 'POST' });
            const data = await response.json();
            showSuccess(data.message);
            await updateStatus();
            document.getElementById('chart').innerHTML = '';
            document.getElementById('noData').style.display = 'block';
        } catch (error) {
            showError('清空數(shù)據(jù)失敗: ' + error.message);
        }
    }

    async function showDebugInfo() {
        try {
            const response = await fetch('/api/sampling/debug');
            const data = await response.json();
            
            let debugHtml = `
                <div class="info">
                    <strong>?? 調(diào)試信息</strong>

                    采樣狀態(tài): ${data.enabled ? '啟用' : '禁用'}

                    數(shù)據(jù)條目數(shù): ${data.stackCountSize}

                    <strong>示例數(shù)據(jù):</strong>

            `;
            
            if (data.sampleData && Object.keys(data.sampleData).length > 0) {
                for (const [stack, count] of Object.entries(data.sampleData)) {
                    debugHtml += `<small>${stack} (${count})</small>
`;
                }
            } else {
                debugHtml += '<small>暫無采樣數(shù)據(jù)</small>
';
            }
            
            debugHtml += '</div>';
            
            const testResult = document.getElementById('testResult');
            testResult.innerHTML = debugHtml;
            
            setTimeout(() => {
                testResult.innerHTML = '';
            }, 15000);
            
        } catch (error) {
            showError('獲取調(diào)試信息失敗: ' + error.message);
        }
    }

    async function showRawData() {
        try {
            const response = await fetch('/api/flamegraph');
            const data = await response.text();
            
            let rawHtml = `
                <div class="info">
                    <strong>?? 原始火焰圖數(shù)據(jù)</strong>

                    <small>格式: 調(diào)用棧 采樣次數(shù)</small>


                    <pre style="font-size: 11px; max-height: 300px; overflow-y: auto; background: #f8f9fa; padding: 10px; border-radius: 4px;">`;
            
            if (data && data.trim() && !data.startsWith('#')) {
                const lines = data.trim().split('\n');
                const displayLines = lines.slice(0, 50); // 只顯示前50行
                rawHtml += displayLines.join('\n');
                if (lines.length > 50) {
                    rawHtml += '\n... (' + (lines.length - 50) + ' more lines)';
                }
            } else {
                rawHtml += '暫無原始數(shù)據(jù)\n\n建議:\n1. 確保采樣已啟用\n2. 運(yùn)行一些測試任務(wù)\n3. 等待幾秒鐘收集數(shù)據(jù)';
            }
            
            rawHtml += '</pre></div>';
            
            const testResult = document.getElementById('testResult');
            testResult.innerHTML = rawHtml;
            
            setTimeout(() => {
                testResult.innerHTML = '';
            }, 20000);
            
        } catch (error) {
            showError('獲取原始數(shù)據(jù)失敗: ' + error.message);
        }
    }

    // 測試函數(shù)
    async function testCpuIntensive() {
        showTestLoading('正在執(zhí)行CPU密集型任務(wù)...');
        
        // 執(zhí)行前檢查采樣數(shù)據(jù)
        console.log('=== 執(zhí)行CPU密集型任務(wù)前的采樣數(shù)據(jù) ===');
        await checkSamplingData();
        
        try {
            const response = await fetch('/test/cpu-intensive?iterations=2000');
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            const data = await response.json();
            console.log('CPU intensive test response:', data);
            showTestResult('CPU密集型任務(wù)完成', data);
            
            // 執(zhí)行后等待一段時間讓采樣器收集數(shù)據(jù)
            setTimeout(async () => {
                console.log('=== 執(zhí)行CPU密集型任務(wù)后的采樣數(shù)據(jù) ===');
                await checkSamplingData();
            }, 2000);
            
        } catch (error) {
            console.error('CPU intensive test error:', error);
            showTestError('CPU密集型任務(wù)失敗: ' + error.message);
        }
    }

    async function testNestedCalls() {
        showTestLoading('正在執(zhí)行嵌套調(diào)用測試...');
        
        console.log('=== 執(zhí)行嵌套調(diào)用測試前的采樣數(shù)據(jù) ===');
        await checkSamplingData();
        
        try {
            const response = await fetch('/test/nested-calls?depth=15');
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            const data = await response.json();
            console.log('Nested calls test response:', data);
            showTestResult('嵌套調(diào)用測試完成', data);
            
            setTimeout(async () => {
                console.log('=== 執(zhí)行嵌套調(diào)用測試后的采樣數(shù)據(jù) ===');
                await checkSamplingData();
            }, 2000);
            
        } catch (error) {
            console.error('Nested calls test error:', error);
            showTestError('嵌套調(diào)用測試失敗: ' + error.message);
        }
    }

    async function testMixedWorkload() {
        showTestLoading('正在執(zhí)行混合工作負(fù)載...');
        
        console.log('=== 執(zhí)行混合工作負(fù)載前的采樣數(shù)據(jù) ===');
        await checkSamplingData();
        
        try {
            const response = await fetch('/test/mixed-workload');
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}: ${response.statusText}`);
            }
            const data = await response.json();
            console.log('Mixed workload test response:', data);
            showTestResult('混合工作負(fù)載完成', data);
            
            setTimeout(async () => {
                console.log('=== 執(zhí)行混合工作負(fù)載后的采樣數(shù)據(jù) ===');
                await checkSamplingData();
            }, 2000);
            
        } catch (error) {
            console.error('Mixed workload test error:', error);
            showTestError('混合工作負(fù)載失敗: ' + error.message);
        }
    }

    async function checkSamplingData() {
        try {
            const response = await fetch('/api/flamegraph');
            const data = await response.text();
            console.log('當(dāng)前采樣數(shù)據(jù)長度:', data.length);
            console.log('當(dāng)前采樣數(shù)據(jù)前300字符:', data.substring(0, 300));
            
            const lines = data.trim().split('\n').filter(line => line.trim() && !line.startsWith('#'));
            console.log('有效數(shù)據(jù)行數(shù):', lines.length);
            
            const businessLines = lines.filter(line => line.includes('com.example'));
            console.log('包含業(yè)務(wù)代碼的行數(shù):', businessLines.length);
            
            if (businessLines.length > 0) {
                console.log('業(yè)務(wù)代碼示例:', businessLines.slice(0, 3));
            }
        } catch (error) {
            console.error('檢查采樣數(shù)據(jù)失敗:', error);
        }
    }

    function showSuccess(message) {
        showNotification(message, 'success');
    }

    function showError(message) {
        showNotification(message, 'error');
    }

    function showNotification(message, type) {
        const notification = document.createElement('div');
        notification.className = type === 'success' ? 'info' : 'error';
        notification.textContent = message;
        
        const controls = document.querySelector('.controls');
        controls.parentNode.insertBefore(notification, controls.nextSibling);
        
        setTimeout(() => {
            notification.remove();
        }, 5000);
    }

    function showTestLoading(message) {
        const testResult = document.getElementById('testResult');
        testResult.innerHTML = `<div class="info">${message}</div>`;
    }

    function showTestResult(title, data) {
        console.log('Test result data:', data); // 調(diào)試信息
        const testResult = document.getElementById('testResult');
        
        const executionTime = data.executionTimeMs || data.executionTime || 0;
        const resultValue = data.result ? data.result.toFixed(2) : 'N/A';
        
        testResult.innerHTML = `
            <div class="info">
                <strong>${title}</strong>

                執(zhí)行時間: ${executionTime}ms

                ${data.result ? '結(jié)果: ' + resultValue : ''}
                ${data.iterations ? '
迭代次數(shù): ' + data.iterations : ''}
                ${data.depth ? '
遞歸深度: ' + data.depth : ''}
                ${data.cpuResult ? '
CPU結(jié)果: ' + data.cpuResult.toFixed(2) : ''}
                ${data.ioResult ? '
IO結(jié)果: ' + data.ioResult.toFixed(2) : ''}
                ${data.finalResult ? '
最終結(jié)果: ' + data.finalResult.toFixed(2) : ''}
            </div>
        `;
        
        setTimeout(() => {
            testResult.innerHTML = '';
        }, 10000);
    }

    function showTestError(message) {
        const testResult = document.getElementById('testResult');
        testResult.innerHTML = `<div class="error">${message}</div>`;
        
        setTimeout(() => {
            testResult.innerHTML = '';
        }, 10000);
    }

    // 窗口大小改變時重新調(diào)整火焰圖
    window.addEventListener('resize', function() {
        if (flameChart && document.getElementById('chart').hasChildNodes()) {
            initFlameGraph();
            refreshFlameGraph();
        }
    });
</script>

這樣,訪問 /index.html 就能直接看到 交互式火焰圖 了。

五、實(shí)戰(zhàn)應(yīng)用場景

1. CPU 突然飆高:馬上打開 flamegraph 頁面,一眼就能看到是 XxxService.doQuery() 占比較高。

2. 定時任務(wù)異常:有些死循環(huán)、重試邏輯,傳統(tǒng)日志難發(fā)現(xiàn),但火焰圖能直接看出占比最高的方法。

3. 性能優(yōu)化前后對比:采集一段時間的火焰圖,優(yōu)化后再對比,能量化效果。

六、總結(jié)

通過上面的方式,我們用 極少的代碼 在 SpringBoot 程序里實(shí)現(xiàn)了一個輕量級的 CPU 火焰圖采樣器。

1、不依賴第三方大工具,隨項目自帶

2、方法級別可視化,能快速定位 CPU 熱點(diǎn);

3、火焰圖展示直觀、體驗(yàn)好;

當(dāng)然,這種方案有局限:

1、采樣頻率過高會帶來一定開銷

2、精度比不上專業(yè)的 async-profiler

3、火焰圖數(shù)據(jù)需要定期清理,避免內(nèi)存膨脹。

以上就是SpringBoot程序CPU飆升的排查指南的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot程序CPU飆升的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java如何實(shí)現(xiàn)判斷并輸出文件大小

    Java如何實(shí)現(xiàn)判斷并輸出文件大小

    這篇文章主要介紹了Java如何實(shí)現(xiàn)判斷并輸出文件大小問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • elasticsearch節(jié)點(diǎn)的transport請求發(fā)送處理分析

    elasticsearch節(jié)點(diǎn)的transport請求發(fā)送處理分析

    這篇文章主要為大家介紹了elasticsearch節(jié)點(diǎn)的transport請求發(fā)送處理分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-04-04
  • SpringBoot 任務(wù)調(diào)度動態(tài)設(shè)置方式(不用重啟服務(wù))

    SpringBoot 任務(wù)調(diào)度動態(tài)設(shè)置方式(不用重啟服務(wù))

    這篇文章主要介紹了SpringBoot 任務(wù)調(diào)度 動態(tài)設(shè)置方式(不用重啟服務(wù)),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Arrays.sort如何實(shí)現(xiàn)降序排序

    Arrays.sort如何實(shí)現(xiàn)降序排序

    這篇文章主要介紹了Arrays.sort如何實(shí)現(xiàn)降序排序問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • java中TestNG使用教程詳解

    java中TestNG使用教程詳解

    TestNG是Java中的一個測試框架, 類似于JUnit 和NUnit, 本文主要介紹了java中TestNG使用教程詳解,具有一定的參考價值,感興趣的可以了解一下
    2021-12-12
  • SpringBoot @ExceptionHandler與@ControllerAdvice異常處理詳解

    SpringBoot @ExceptionHandler與@ControllerAdvice異常處理詳解

    在Spring Boot應(yīng)用的開發(fā)中,不管是對底層數(shù)據(jù)庫操作,對業(yè)務(wù)層操作,還是對控制層操作,都會不可避免的遇到各種可預(yù)知的,不可預(yù)知的異常需要處理,如果每個處理過程都單獨(dú)處理異常,那么系統(tǒng)的代碼耦合度會很高,工作量大且不好統(tǒng)一,以后維護(hù)的工作量也很大
    2022-10-10
  • 使用Idea快速搭建SpringMVC項目的詳細(xì)步驟記錄

    使用Idea快速搭建SpringMVC項目的詳細(xì)步驟記錄

    這篇文章主要給大家介紹了關(guān)于使用Idea快速搭建SpringMVC項目的詳細(xì)步驟,Spring?MVC是一種基于MVC模式的框架,它是Spring框架的一部分,它提供了一種更簡單和更有效的方式來構(gòu)建Web應(yīng)用程序,需要的朋友可以參考下
    2024-05-05
  • 解決SpringBoot項目讀取yml文件中值為中文時,在視圖頁面顯示亂碼

    解決SpringBoot項目讀取yml文件中值為中文時,在視圖頁面顯示亂碼

    這篇文章主要介紹了解決SpringBoot項目讀取yml文件中值為中文時,在視圖頁面顯示亂碼的問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • java實(shí)現(xiàn)鮮花銷售系統(tǒng)

    java實(shí)現(xiàn)鮮花銷售系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)鮮花銷售系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • SpringBoot項目Slf4j沖突問題及解決方案

    SpringBoot項目Slf4j沖突問題及解決方案

    在Spring Boot項目中使用Log4j2替代默認(rèn)的SLF4J實(shí)現(xiàn)時,可能會遇到多個SLF4J實(shí)現(xiàn)的沖突,通過調(diào)整pom.xml文件的配置順序,并通過查看Maven依賴樹形關(guān)系,找到并排除沖突的依賴(例如jdbc依賴中的logback),可以解決這個問題
    2025-12-12

最新評論

庆云县| 朔州市| 安达市| 金湖县| 水富县| 临漳县| 乌拉特后旗| 信宜市| 惠来县| 汉中市| 鱼台县| 庄浪县| 海兴县| 二连浩特市| 定兴县| 绥芬河市| 揭阳市| 万山特区| 陵川县| 阿克苏市| 阳泉市| 阳春市| 团风县| 达拉特旗| 马边| 南漳县| 云和县| 景泰县| 秭归县| 松滋市| 游戏| 新津县| 绥宁县| 城市| 湘乡市| 南开区| 孙吴县| 新田县| 平武县| 台北县| 林西县|