社内se × プログラマ × ビッグデータ

プログラミングなどITに興味があります。

JVM は どうやって class ファイルをロードしているのか

JVM クラッシュの調査をしていると、そもそも JVM の動作についてあまり知らなかったことに気づく。
大まかには、コンパイルによって生成された class ファイルを JVM が取り込んで、解析して、実行する。

その class ファイルを取り込むところを Class Loader Subsystem と呼ばれるものが担当しているらしい。
これは、プログラムの実行時に行われる処理であって、コンパイル時の処理ではない。

ロードの処理は更に、LoadingLinkingInitialization というステップに分けられる。

Class Loader Subsystem

1. Loading

まずは、class ファイルがロードされるが、以下の3種類のローダーが存在する。
Boot Strap ClassLoader は、rt.jar など、JREにとって最もコアとなるライブラリがロードされる。
Extension ClassLoader は、ext フォルダ内にある準コアとなるライブラリがロードされる。
Application ClassLoader は、CLASSPATH 内にあるライブラリがロードされる。

もし、ロード対象のクラスが見つからなかった場合、ClassNotFoundExceptionがスローされる。

2. Linking

class ファイルのロード後、バリデーション的なことが行われる。
Verifyでは、生成されたバイトコードが正しいものであるかどうかをチェックする。
Prepareでは、全ての static 変数がメモリ上にデフォルト値でロードされる。
Resolveでは、全ての シンボリックリンクが実際の参照先に置き換えられる。

もし、参照先のクラスが見つからなかった場合、ClassDefNotFoundExceptionがスローされる。

3. Initialize

全ての static 変数に設定値が与えられる。そして、static ブロック内の処理が実行される。

所感

class ファイルのロードだけでも、想像していたより細かいステップによって行われている印象を持った。
ClassNotFoundException と ClassDefNotFoundException はしばしば出くわしますが、それぞれがスローされるタイミングが分かったのが嬉しい。
f:id:blueskyarea:20180605001252p:plain

Jackson の JsonSetter は便利なのか試してみる

普段は JSON の取り扱いには GSON を使っていて、特に不便は感じていませんが
今回はお試しで Jackson を使ってみました。

概要
JsonSetter は JSON の読み込み時に使います。
読み取るJSONに、エンティティのフィールドがすべて含まれていなくても読み込んでくれます。

使い方
1. エンティティの定義
こんな感じで、@JsonSetter 付きでセッターを定義します。

public class JsonSetterExample {
	private int key;
	private String name;
	private String memo;
	
	@JsonSetter("key")
	public void setKey(int key) {
		this.key = key;
	}

	@JsonSetter("name")
	public void setName(String name) {
		this.name = name;
	}
	
	@JsonSetter("memo")
	public void setMemo(String memo) {
		this.memo = memo;
	}
}

2. 読み取り
読み取るときは、ObjectMapper オブジェクトの readFor を使います。
定義したエンティティクラスを指定し、対象の json を readValue で読み取ります。
※ 各ゲッターは上記とは別に定義しています

@Test
public void test() throws JsonProcessingException, IOException {
	String json = "{\"key\":10,\"name\":\"my name\"}";
	JsonSetterExample entity = new ObjectMapper().readerFor(JsonSetterExample.class).readValue(json);
	assertThat(entity.getKey(), is(10));
	assertThat(entity.getName(), is("my name"));
	assertTrue(entity.getMemo() == null);
}

感想
Gsonでは private フィールドを自動的に保存してくれるので、ゲッターやセッターは不要。
Jackson では ゲッターやセッターを定義しないといけない分 ? 今のところ、メリットは感じられなかった。
普通に使う分には分かり易い動作だと思います。
また、JsonSetter 以外のアノテーションも試してみたい。

Java の puzzle ?

twitter でこんな問題が流れてきました。

public static main(String[] args) {
  int[] array[][] = {
    null,
    {{1, 2, 3, 4, 5}},
    new int[3][],
    {{6, 7, 8, 9. 10, 11}, {12, 13, 14, 15}, {16, 17, 18, 19}}
  };
  // ここに array に含まれている 18 を標準出力するコードを書いて下さい
}

なにやら、ややこしい配列が定義されてます。
null や new int[3][] など、混乱させようとする要素も。
普段こんなコード見ることがないので、戸惑いましたが
array が何次元配列になるのか考えられると、答えに辿り着けそうです。

あとは、色々と試してみました。
https://github.com/blueskyarea/java-practice/blob/master/src/main/java/puzzle/ArrayPuzzle.java

CalledFromWrongThreadException (メインスレッド以外から、UIの更新)

問題
Android のプログラミング中に以下の例外が発生。

CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

どうも、UIを実装しているスレッド以外のスレッドから、UIに更新をしようとしている時に発生しているよう。

原因
Android の制約らしい。

回避策
Handler のインスタンス経由で、別スレッドからメインスレッドに更新を依頼すれば良いらしい。
メインスレッド内で、Handler のインスタンスを生成。

val handler = Handler()

他のスレッド内で、handler.post に処理を渡してあげる。

thread {
  handler.post( Runnable() {
    val resultView: TextView = findViewById(R.id.result)
    resultView.text = "test"
  })
}

この場合、メインスレッドが画面の更新を担うことになるので、Android の制約に反せず、例外は発生しない。

Android textView を動的に追加 (kotlin)

表記のとおり、textView を動的に追加するコードを試してみました。
静的に追加するなら、xml に記述すればOKです。
動的に追加する場合、textView のインスタンスを生成し、それをレイアウトに追加します。
例として、2つの TextView を作成し、レイアウトに追加してみました。

// TextView の配列(サイズ2)を作っています
val texts: Array<TextView?> = arrayOfNulls(2)

// FrameLayout のインスタンスを取得しています
// textsWindow は予めレイアウトファイル(.xml)に定義したものです
val layout: FrameLayout = findViewById(R.id.textsWindow)

// それぞれのTextView に設定を行い、FrameLayout のインスタンスに追加しています
// 今回はそれぞれのテキストが重なってもOKなように、FrameLayout を使っています
// 特に制約がなければ、LinearLayout などでも構いません
for (i in 0..1){
  texts[i] = TextView(this)
  texts[i]?.text = "test text"
  texts[i]?.setTextColor(Color.WHITE)
  texts[i]?.x = 400.0f
  texts[i]?.y = 400.0f * (i + 1)
  layout.addView( texts[i], LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 50))
}

変数名や値は適当です。
画面は掲載していませんが、定義したテキスト("test text")が表示されることが確認できました。

感想
TextView の配置位置が予め決まっていて、表示・非表示の切り替えのみしたい
> visibility だけで対応できそう。
ユーザーの操作に合わせて、TextView を色んな位置に表示させたい
> 今回のように動的に追加すれば対応できそう。

WireMock で IllegalArgumentException(request + " does not have a getUri or getHttpURI method")

WireMock は簡単にREST API のモックが作れるのでとても便利です。
その WireMock を実装中に、表題のエラーに出くわしました。

IllegalArgumentException(request + " does not have a getUri or getHttpURI method")

WireMock のソースファイルを見てみると、どうも JettyUtils 内でスローされている様子。
https://github.com/tomakehurst/wiremock/blob/master/src/main/java/com/github/tomakehurst/wiremock/jetty9/JettyUtils.java

getUri あるいは getHttpURI のメソッドを探したが見つからなくて、例外が発生しているように見えます。
Exception で catch していますが、恐らくは NoSuchMethodException 辺りがスローされているのではと思います。

それらのメソッドは、org.eclipse.jetty.server.Request から探そうとしているようなのですが。
Request って名前のクラスって色んなところで使われていると思うんですよね。
実際、自分が扱っていたモジュール内を探してみても、4つか5つくらいの Request クラスが見つかりました。

これは、Request クラスがコンフリクトしているような気がしたので、依存ライブラリのロード順を変えてみたり、exclusion してみたところ、エラーを回避することができました。

1モジュールが大きくなってくると、依存ライブラリの数も多くなってきて、整合性が取りづらくなってきます。
モジュールの役割を明確にし、あまり大きくなり過ぎないように気をつけたいです。
あと、クラス名付ける時は分かり易いのがもちろんいいですが、なるべくユニークな名前にしておいた方が、コンフリクトに悩む機会は減るかもしれません。
とは言え、いつもコンフリクトに悩まされるのは、ほとんど内製ではない依存ライブラリからなんですけどね。。

android の getAssets() を MainActivity class 以外から使いたい

AppCompatActivity() を継承
MainActivity とは違う新しいクラスを生成し、AppCompatActivity() を継承し、getAssets() を呼び出してみた。

getAssets().open("sample.txt")

コンパイルエラーは起きないものの、以下のエラーが発生。

java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Context android.content.Context.getApplicationContext()' on a null object reference

一応、activity クラスを作ったつもりだけど、恐らく正しく作る(使う)ことが出来ていない。
調べてみたところ、MainActivity の context のインスタンスを渡してあげる方が良さそう。

MainActivity の context のインスタンスを渡す

class NonActivityClass(context: Context) {
  val myContext = context
}

# from MainActivity
NonActivityClass(this)

ちなみに、applicationContext という変数があるが、それを(this の代わりに)渡してあげても、エラーは解消されなかった。

Context について

  • Context とは、アプリケーションのグローバルな環境情報を受け渡すために使用されるもの。
  • Context には、Application Context と Activity Context の2種類が存在する。
  • Application Context は Activity クラス内で getApplicationContext() で取得できる。
  • Activity Context は Activity 自身のこと。

即ち少なくとも自分のコードでは、Application Context ではなくて、Activity Context が必要とされていることになると思いますが、両者の違いを理解しないといけない。