私がログの助けを借りてどのように記憶の「食いしん坊」を見つけたかの物語

私は単純なものを共有したいと思いますが、私の意見では、手元に常にある最小限のツール(ログとjVisualVM)を使用して、メモリドゥーバーが見つかる場所を検索する興味深い方法です。



私たちは、何万人ものユーザーのために機能するように設計された電子文書管理システムを開発しています。 システムの動作中に、数か月間、システムに追いつくことができる変更がなかったにもかかわらず、サーバーがメモリを使い果たしたという状況に直面しました。 また、システムのテストコピーは疑わしいものではありません。



メモリプロファイリングオプションが失敗しました-システムで作成されたオブジェクトが多すぎるため、意味のある時間にプロファイラースナップショットを削除することはできません。 いくつかのヒープダンプを削除し、それらを手動で比較することで手がかりが得られました-システムには、同じHibernateエンティティの予想外に多数のインスタンス(わずか100万を超える)が含まれています。 わあ ! しかし、非常に多くのオブジェクトが生成される場所をどうやって知るのですか? コードで検索しますか? オプションではありません。システム全体で検索を行う必要がありますが、時間はもうすぐです-顧客はすでに青になっています。



次の解決策が思い浮かびました:エンティティクラスのコンストラクターに呼び出しスタックロギングを挿入しませんか? 興味深いアイデア...次のコードを使用してコンストラクターが作成されました。



 ...
保護された静的最終ロガーLOGGER = Logger.getLogger(Payment.class);

公共支払い()
 {
	 // TODO:クリア! メモリリークの検索のみ!
	 StringBuilder stringBuilder = new StringBuilder();
	 stringBuilder.append( "MEMLEAK_TEST:Payment()\ n");
	 StackTraceElement [] stackTraceElements = Thread.currentThread()。GetStackTrace();
	 for(StackTraceElement stackTraceElement:stackTraceElements)
	 {
		 stringBuilder.append( "at"
				 + stackTraceElement.getClassName()
				 + "。"  + stackTraceElement.getMethodName()
				 + "(" + stackTraceElement.getFileName()+ ":" + stackTraceElement.getLineNumber()+ "\ n");
	 }
	 LOGGER.info(stringBuilder.toString());
 }
 ...




産業システムを更新し、一晩放置しました。 翌朝、サイズが〜40 GBの印象的なログを受け取りました。 さあ、始めましょう。



最初に、ログを200 MBの断片にカットする必要がありました。 そうしないと、そのようなボリュームを単に開くことができません。 ログをすばやく実行すると、すぐにコールスタックを分離できました。これは非常に一般的です。 スタックコールで、Hibernate関数を呼び出すクラスが見つかりました。 ここにある-私たちの不運なクラス。 彼の方法の1つでは、テーブルからすべてのレコードのリストが取り出されます(そして、その中のレコードの数が200万を超えました)-これが私たちの本質のインスタンスの作成です。 さらに、コードはさらに悪化します。このリスト全体がループ処理され、1つのフィールド(フィルター実装の一種)によって比較されます。 もちろん、コードはすぐに書き直され(HQLクエリの場所に条件を挿入することにより)、作成者はすぐに分析されました 。 それだけです。この「昔ながらの」方法で、非常に「気難しい」問題が解決されました。



結論として 、データベースを操作するコードを書くために座ったなら、SQLを学んでください! (単純なクエリの言語がわからない-データベースの外に出ないほうがいい)。 そして、第二に、一見して難しいタスクを解決するために、「洗練された」ツールを持つ必要はありません。



All Articles