カウンター

2014年1月29日水曜日

SSL通信

最近お仕事で扱うことがあったので、備忘をかねてちょっとメモしておきます。

そもそもSSL通信とは、Secure Sockets Layerの略でhttp専用の暗号化通信のこと。公開鍵暗号を利用しています。

公開鍵暗号方式とは、ざっくりいうと暗号鍵と復号鍵が違う、という方式です。共通鍵方式の方が計算量は少ない、処理が早いのですが、知らない人同士が初めて通信する場合って当然共通鍵を持ってないので、共通鍵の交換にこの公開鍵暗号方式が使われるわけです。

SSLに戻ります。SSL通信を送信すると、まずWebサーバは自身のサーバ証明書を認証局の秘密鍵で暗号化して送ります。認証局とは、サーバ証明書を保証する第三者機関です。サーバ証明書とは、読んで字のごとく、自分のサーバの身分を証明するものです。当然第三者(認証局)によって証明されているものであり、これがあることで、なりすましを防ぐわけです。

サーバ証明書が届くと、クライアント側でそれを確認するフェーズになります。まず、認証局の秘密鍵によって暗号化されているので、認証局の公開鍵によってそれを復号化します。

認証局の公開鍵は、一般にはルート証明書という形式で既存のブラウザ等に既にインストールされていることが多いです。下記にIEのルート証明書一覧をのっけました。



ルート証明書が既にインストール済みであれば、復号化してサーバ証明書を確認できます。

サーバ証明書内には、サーバのFQDNが記載されています。これをアクセスしたURL(たとえばwww.google.com等)と一致しているかどうかを確認します。つまり、クライアント側は、本当に自分のアクセスしたかったサーバにアクセスしているのか、という検証を、アクセスしたURLとサーバ証明書内のFQDNと一致するかどうかを見て行うわけです。サーバ証明書自体の有効性はルート証明書が保証していますので問題ないわけです。

最終的に、確認が完了すると、クライアント側で共通鍵を作成しサーバ側と共有します。最初にサーバ証明書が来たときに併せてサーバの公開鍵を送っているので、この公開鍵を用いて共通鍵を暗号化しサーバに送ることで、共通鍵の共有は完了です。

で、ようやく暗号化通信ができるようになりました。

最初に、公開鍵暗号方式で証明書を送り、共通鍵を共有します。その後は、共通鍵で暗号化通信を行います。これが、SSL通信の概要になります。


ちょっと文字ばっかりになってしましました。。。

2014年1月3日金曜日

JavaVMのメモリ管理(Permanent領域編)

今回はPermanent領域に焦点を当てていきます。
※あまり経験が無い分野なので、間違っている箇所があるかもしれません。。。その際はご指摘ください。

JavaVMではクラスローダを利用してクラスファイルをPermanent領域に読み込みます。クラスファイルとは、Javaコンパイル時に生成される、中間ファイル(.classファイル)のことです。クラスローダには下記4種類あります。

  • ブートストラップクラスローダ
    • Javaの中核となるライブラリをロードする。ネイティブコード。(ネイティブコードとは、OS上で直接実行可能な形式、つまり機械語になっているものです)


  • 拡張クラスローダ
    • 拡張ディレクトリ(/lib/ext や java.ext.dirsプロパティで指定された他のディレクトリ)にあるコードをロードする。


  • システムクラスローダ
    • CLASSPATH変数に記述されたクラスをロードする。通常、ユーザが記述したコードはこちらのクラスローダによってロードされる。


  • ユーザ定義クラスローダ
    • ユーザが定義したClassLoaderクラスによってロードする。


無論Permanent領域にもGCがあり、当該領域がいっぱいになると発生し、Old領域と同じようにPermanennt領域のGC中はJava全体が止まってしまいます。ちなみに、私が経験したケースだと、Old領域のFullGCは0.2秒程度(コンカレントGC)だったのに対し、Permanent領域のFullGCには7秒もかかっていました。。。

上記のGC時間を見てもわかる通り、Permanent領域については極力防がないといけないものです。前回も記載した通り、定期再起動とかメモリ増設なんですが、今回はアプリケーションの観点で考察したいと思います。

最初に記載したとおり、Permanent領域ってクラスロードに利用されます。基本的には最初(起動時)にクラスをロードしたら一定のはず、、、なんですが、GCが発生するようなPermanent領域が次第に増えていくケースというのは、下記のような場合があると考えられます。

  1. 新規利用されるクラス
  2. ユーザ定義クラスローダ
  3. リフレクション


「1.新規利用されるクラス」

Javaの基本原則として、必要になって初めてクラスがロードされます。長い間稼働するプログラムで、呼び出されることのなかったクラスが呼び出されるとそこで初めてクラスがロードされますので、理論的には時間とともに微増していくと考えられます。

「2.ユーザ定義クラスローダ」

"ClassLoaderクラス"を継承ことによって、ユーザ定義のクラスローダを作成することができます。(ClassLoaderクラスは抽象クラスであるため、直接生成できない)こちらでクラスロードを実行すると、当然Permanent領域を利用することになり、当該領域が増えます。

「3.リフレクション」

リフレクションとは、プログラムの実行過程で自身の構造を読み取ったり書き換えたりする技術のことで、javaではjava6以降で導入されています。リフレクションの機能を利用することで、動的なクラス生成が可能です。例えば、生成したクラスorメソッドを、プログラムに渡す引数によって変えたり等の柔軟な処理が書けたりします。

ただ、一定回数以上の呼び出しがあると、当該クラスをバイトコード化して、Permanent領域に格納するためPermnent領域が増加していく、という現象があるようで、こちらもPermanent領域増加の原因となってしまうようです。

// リフレクション
Class sampleins = Class.forName("Hoge");
Method samplemethod = sampleins.getMethod("helloworld");
samplemethod.invoke(sampleins.newInstance());

Permanent領域のFullGC原因は一概には言えないですが、チューニングに際しては下記オプションが使えそうです。
  • -XX:+ TraceClassloading
    • クラスのロード時に情報を出力
  • -XX:+ TraceClassUnloading
    • クラスのアンロード時に情報を出力


JVMのメモリ管理

今回はJavaVMのメモリ管理について記載します。
#最近お仕事でシステムのリソース状況を見る機会が多いため、覚書として書いておきたいと思います。

まず、基本事項ですがJavaでプログラムを作成すると、C言語などとは違ってメモリを自動で管理してくれます。例えば、プログラム内で変数を宣言して正しく開放されない変数が残っていたりすると、次第にメモリ利用上不都合な状態(これをフラグメンテーション、或いは断片化と言います)になってしまいます。本来であればすべての変数に対して開放する処理を記述してあげる必要があるのですが、Javaではそういった面倒な記述が不要で、Java自身が自動で管理してくれます。具体的には、JavaVM内で利用しているメモリを自動整理する、という仕組み(ガーベジコレクション:GCと言います)が動きます。

もう少し詳しく見ていきます。
JavaVMでは、メモリ領域を下記図のように種別して管理します。


大きく分けると、
・New領域
 →生存期間の短いJavaオブジェクトが格納される
・Old領域
 →生存期間の長いJavaオブジェクトが格納される
・Permanent領域
 →ロードされたclassの情報が格納される
の3つになります。

New、Old領域の2つをJavaヒープ領域といいます。Javaヒープ領域では、オブジェクトを世代別に管理する仕組みになっており、新規に生成されたJavaオブジェクトはまずNew領域内のEden領域に格納されます。その後Edenがいっぱいになると、EdenのGC(YoungGC:YGCといいます)が発生し、まだ利用中であるオブジェクトのみをSurvivor領域の"To"に移動させ、利用していないオブジェクトは開放します。次のYGC時にはSurvivor領域のFromとToが入れ替わり、Eden領域のオブジェクトを"元々From:現To"に移し、同時に"元々To:現From"に格納されていたオブジェクトで利用中のものを"元々From:現To"の領域に移します。

さらにYGCを一定回数繰り返してもなおSurvivor領域に残っているオブジェクトが「生存期間の長いオブジェクト」だと判断され、Old領域に移されます。Old領域がいっぱいになってしまうと、FullGCと呼ばれるGCが発生し、JVMで稼働している全オブジェクトが停止してしまいます。。。(俗に、"Stop the World"といいます。なんかかっこいいw)なので、一般的にはFullGCが発生しないように、適切なチューニングを行います。

上記で記載した各領域のサイズは、Java実行時にオプションとして定義します。指定オプションを下記で紹介します。

  • -Xms
    • ヒープ初期サイズを指定
    • 例:-Xms2048k、-Xms2m
  • -Xmx
    • ヒープ領域最大サイズを指定
    • 例:-Xmx2048k、-Xmx2m
  • -XX:NewSize
    • New領域初期サイズを指定
  • -XX:MaxNewSize
    • New領域最大サイズを指定(初期サイズと同じがベター)
  • -XX:Permsize
    • Permanent領域初期サイズを指定
  • -XX:MaxPermsize
    • Permanent領域最大サイズを指定


Javaを利用しているWebアプリケーションでは、FullGC発生対策として一般に下記が考えられるのかなと思います。

・Javaアプリケーションサーバの定期再起動(一日1回とか)
 →サービス停止が必要
・割当メモリの増設
 →FullGC発生頻度は減るが、発生してしまった時の停止時間が増えてしまう。

"→"で記載したように、デメリットもあります。
 勿論、そもそものアプリケーションの作りが悪いから改修する、という方法もありますが現場だと中々そっちの手段は取れないのかな、、、と思います。

ただ、最近では「コンカレントGC」というFullGCで停止してしまう時間を最小化する仕組みもあり、Javaも進化してきておりますw

次回はPermanent領域について触れます。