実行時に1.2GBのメモリを使用するgolangプログラムを作成しました。
go tool pprof http://10.10.58.118:8601/debug/pprof/heap
を呼び出すと、323.4MBのヒープ使用量のダンプが生成されます。
gcvis
を使用すると、これが得られます。
..およびこのヒープフォームプロファイル:
ここに私のコードがあります: https://github.com/sharewind/Push-server/blob/v3/broker
ヒーププロファイルは、アクティブメモリ、ランタイムがgoプログラムによって使用されていると見なしているメモリ(つまり、ガベージコレクタによって収集されていないメモリ)を示します。 GCがメモリを収集すると、プロファイルは縮小しますが、システムにメモリは返されません。今後の割り当てでは、システムにさらに要求する前に、以前に収集したオブジェクトのプールからメモリを使用しようとします。
外部から見ると、これはプログラムのメモリ使用量が増加するか、レベルが維持されることを意味します。プログラムの「常駐サイズ」として外部システムが提示するのは、使用中のgo値を保持するか収集した値を保持するかに関係なく、プログラムに割り当てられるRAMのバイト数です。
これらの2つの数値がしばしばまったく異なる理由は、次のとおりです。
Goがメモリをどのように認識するかを正確に分類したい場合は、runtime.ReadMemStats呼び出しを使用できます。 http://golang.org/pkg/runtime/#ReadMemStats
または、ブラウザでhttp://10.10.58.118:8601/debug/pprof/
からプロファイリングデータにアクセスできる場合は、Webベースのプロファイリングを使用しているため、ヒープリンクをクリックすると、ランタイムの出力を含むヒーププロファイルのデバッグビューが表示されます。下部の.MemStats構造。
Runtime.MemStatsのドキュメント( http://golang.org/pkg/runtime/#MemStats )にはすべてのフィールドの説明がありますが、この議論で興味深いのは次のとおりです。
Goがシステムに要求することと、OSがシステムに与えることは常に同じではないため、SysとOSが報告する内容との間には依然として矛盾があります。また、CGO/syscall(例:malloc/mmap)メモリはgoによって追跡されません。
簡単に言うと、@ Cookie of Nineの答えに加えて、--alloc_space
オプションを試すことができます。
go tool pprof
はデフォルトで--inuse_space
を使用します。メモリ使用量をサンプリングするため、結果は実際のサブセットです。
By --alloc_space
pprofは、プログラムの開始以降に割り当てられたすべてのメモリを返します。
私は、Goアプリケーションの常駐メモリの増加について常に混乱していました。最後に、Goエコシステムに存在するプロファイリングツールを学ぶ必要がありました。ランタイムは runtime.Memstats 構造内に多くのメトリックを提供しますが、どれがメモリ増加の原因を見つけるのに役立つかを理解するのが難しい場合があるため、いくつかの追加ツールが必要です。
プロファイリング環境
アプリケーションで https://github.com/tevjef/go-runtime-metrics を使用します。たとえば、これをmain
に入れることができます。
import(
metrics "github.com/tevjef/go-runtime-metrics"
)
func main() {
//...
metrics.DefaultConfig.CollectionInterval = time.Second
if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
// handle error
}
}
InfluxDB
コンテナー内でGrafana
およびDocker
を実行します。
docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0
Grafana
とInfluxDB
Grafana
の相互作用を設定します(Grafanaメインページ->左上隅->データソース->新しいデータソースの追加):
ダッシュボードのインポート #3242 from https://grafana.com (Grafanaメインページ->左上隅->ダッシュボード->インポート):
最後に、アプリケーションを起動します。ランタイムメトリックスを、生成されたInfluxdb
に送信します。アプリケーションに適切な負荷をかけます(私の場合は非常に小さく、数時間で5 RPS)。
メモリ消費分析
Sys
(RSS
の同義語)曲線は、HeapSys
曲線に非常に似ています。動的なメモリ割り当てが全体的なメモリ増加の主な要因であることが判明したため、スタック変数によって消費される少量のメモリは一定であるようで、無視できます。HeapIdle
はSys
と同じ速度で成長していますが、 HeapReleased
は常にゼロです。明らかに、少なくともこのテストの条件下では、ランタイムはメモリをOSに返しませんat all。HeapIdle minus HeapReleased estimates the amount of memory that could be returned to the OS, but is being retained by the runtime so it can grow the heap without requesting more memory from the OS.
メモリ消費の問題を調査しようとしている人には、いくつかの些細なエラー(ゴルーチンリークなど)を除外するために、説明されている手順に従うことをお勧めします。
メモリを明示的に解放する
debug.FreeOSMemory()
を明示的に呼び出すことでメモリ消費を大幅に削減できるのは興味深いことです。
// in the top-level package
func init() {
go func() {
t := time.Tick(time.Second)
for {
<-t
debug.FreeOSMemory()
}
}()
}
実際、このアプローチはデフォルトの状態と比較して約35%のメモリを節約しました。
StackImpact を使用することもできます。これは、通常および異常にトリガーされたメモリ割り当てプロファイルを自動的に記録し、ダッシュボードに報告します。詳細については、このブログ投稿を参照してください Production Goアプリケーションでのメモリリーク検出
免責事項:StackImpactで働いています