20210412のJavaに関する記事は9件です。

【Java アルゴリズム修行④】素数判定

素数判定 素数の意味がピンとこなかったので、もはやアルゴリズム以前の問題じゃないのか。。と思いましたが 素数を知らないことをこの瞬間に知れたんだ!と前向きに捉えつつ、まずは調べてみました! 素数 とは、「1とその数以外の正の約数を持たない自然数」を指す ふむ。。 1と自身以外で割り切れなければ素数らしいです。 例えば7だと、7÷2も7÷3も7÷4も7÷5も7÷6も割り切れないから素数だよ って感じみたいですね。 入力された値が素数かどうかの判定をするとなると。。 その数が1以下かどうか(1以下はまず素数じゃない) その数が1と自身以外の数で割り切れるかどうか を判定すれば良さそうです。 alog.java import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int target = sc.nextInt(); //入力値がそもそも1以下であれば素数ではないので弾く if (target <= 1){ System.out.println(target + "は素数ではありません"); return; } for (int i = 2; i < target; i++){ // 2~入力値未満の間で割り切れてしまえば素数ではない判定 if(target % i == 0){ System.out.println(target + "は素数ではありません"); return; } } //割り切れない場合は素数判定 System.out.println(target + "は素数です"); } } 入力値に対して、まず最初に1以下であれば素数ではないのが確定なので弾いてしまいましょう。 その後、入力値を[2~入力値未満]で繰り返し除算し、もし割り切れない場合は素数だね!という流れで書いてみました。 条件さえ整理できればそれほど難しくなさそうですね!(成長を感じます) 入力値までの素数を出力してみる 調子に乗って、今度は2~入力値まで(1以下は素数ではないので)に存在する素数を出力するプログラムを作ってみます。 下記判定条件を3~入力値まで存在する数だけ行うことになるので2重ループを使うことになりそうですね。。(プログラムが動いた後だからこんな余裕の口調になってます) その数が1以下かどうか その数が1と自身以外の数で割り切れるかどうか algo.java import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int target = 0; // 1以下の数字には素数は存在しないので、2以上の値が入力されるまで待つ do { System.out.println("2以上の数を入力してください"); target = sc.nextInt(); } while (target < 2); // 2は1と自身の数以外では割れないので決め打ちで出力する System.out.println("2は素数です"); // 3 ~ 自身の数の間の奇数のみで繰り返し素数判定を行う for (int i = 1; i <= target; i += 2) { // 1か自身の数以外で割り切れれば素数ではない for (int j = 2; j < i; j++) { if (i % j == 0) { break; } else if (j == i - 1) { System.out.println(i + "は素数です"); } } } } } 入力値が2以上の数でなければ素数は表示されないので、do-while文で(targe < 2) として入力値が1未満であれば再度入力を促すようにしました。 2以上であれば、2だけは決め打ちで出力し、それ以降は繰り返し処理で、それぞれ素数判定を行います。 入力値自身の素数判定だけでなく、3〜入力値までの数それぞれに判定が必要になるので、2重ループを使用しました。 (偶数は2で割れるため、奇数のみで判定するように更新文にて、i+=2を行っています。) その数が1以下かどうかという判定は先のdo-while文で終わっているので、 その数が1と自身以外の数で割り切れるかどうかのみを2重目のループで判定しています。 if(i % j == 0)ここのiは素数か否かを判定したい数そのもので、jは3~判定したい数までをインクリメントして除算を行い、余りが0であれば割り切れてしまっている(その数は素数ではない)ので、その時点でbreak文を使って2重目のループごと抜けるようにしました。 else if(j == i - 1)では、全く割り切れずに自身の数の直前まで繰り返せた場合を想定しているので、 そこまできたら素数だね!ということで素数である旨を出力しています。 個人的に、最後の判定部分がちょっとブサイク気味なので2重目の条件文とかでどうにかならないか試行錯誤中です。。 さらにスマートに algo.java import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int counter = 0; int i = 0; for(int n = 2; n <= 1000; n++){ for(i = 2; i < n; i++){ System.out.println(i+"iの数は左の通り"); counter++; if (n % i == 0) { break; } } //どの数でも割り切れることなく、自身の数まできたときは素数となる if(n == i){ System.out.println(n + "は素数です"); } } System.out.println("除算回数は" + counter); } } 入力値ではなく、1000以下の数として決め打ちではありますが、素数判定が個人的にかなりスマートな印象ですね! 2重目のループで2〜自身の数未満の間のどれでも割り切れなかった場合は という部分をif(n == i)で判定することでよりすっきりできた気がします。。  ここでの除算回数は78,022回。。!(ターミナル上ではほぼ一瞬ですが笑) algo.java import java.util.*; public class Main { public static void main(String[] args) { Scanner sc = new Scanner(System.in); int timer = 0; //得られた素数の数 int counter = 0; //1000の偶数を除いた数の500で配列を生成する int [] prime = new int[500]; //1要素目には既定の素数、2を代入する prime[counter++] = 2; int i = 0; //2は既に代入したので、3~1000の間で、偶数のもののみで素数判定を行う for(int n = 3; n <= 15; n +=2){ // 素数だけで除算を行う System.out.println("counterは" + counter); for(i = 1; i < counter; i++){ timer++; if (n % prime[i] == 0) { break; } } //どの数でも割り切れることなく、自身の数まできたときは素数配列に代入する //素数の個数と、繰り返す回数が一致していれば素数の最後まで除算をしたということになる if(counter == i){ prime[counter++]= n; } } for(int a : prime){ if(a == 0) break; System.out.println(a + "は素数です"); } System.out.println("除算回数は" + timer); } } これは自力ではありませんが、より効率的に行う考え方として参考に載せておきます! 奇数だけで繰り返すのは同じですが、全ての値で除算するのではなく、それまでの素数で割り切れるかを確かめるというものです。素数でも割り切れなければ確実に素数であるので、これで除算回数は 14,622回まで減るのが確認できました。。!(凄まじい。。) 素数だけの配列を最初に組んでしまい、その素数の要素数まで割り切れなければ、判定対象の数字も素数とみなし、配列に代入することでより効率的になると言った感じですね。 学んだこと 条件を明確にして、どこまでをif文判定するのかを見極める break文を使ってループごと抜ければ、余計な繰り返し処理を省ける 無駄な除算など、削れるものがないか偶数・奇数など条件を絞れないか試してみるのは大事 これまでは全てをゴリ押しif判定していましたが、do-while文で最初の入力値を弾くことによって無駄な処理を行わずに済むことが分かりました!do-whileは使い所ないのでは。。と思っていましたが、判定する前に処理を挟めるのは入力値の判定と相性がいいのかもしれません。。 break文、continue文も有効活用して余計な処理はなるべく省くことも意識していきたいと思いましたし、無駄なものが削れないか、条件を絞れないかなどの閃きは量をこなしていけば身についてくれる、、と信じて引き続き頑張っていきます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Microsoft Build の OpenJDK をプレビュー版として提供開始

この記事は https://devblogs.microsoft.com/java/announcing-preview-of-microsoft-build-of-openjdk/ の記事を翻訳した内容です。 本日、Microsoft Build の OpenJDK のプレビュー版を発表できることを心より嬉しく思います。 今回提供する Microsoft Build の OpenJDK はオープンソースで、どなたでも、そしてどこでも無料で利用可能な ロングタームサポート(LTS)付きの新しいディストリビューションです。OpenJDK 11.0.10+9 をベースに、MacOS、Linux、Windows の x64 サーバーおよびデスクトップ版を用意しています。 また最新の OpenJDK 16+36 をベースにした、Windows AArch64/ARM64 版 Java16 のアーリー・アクセスバイナリも公開しています。 入手先 https://www.microsoft.com/openjdk Javaは、現在使用されているプログラミング言語の中で最も重要な言語の1つです。開発者は Java を利用して企業における重要なアプリケーションの構築から趣味のロボット用のプログラミングまで様々な環境で利用されています。 マイクロソフトは、我々が提供するクラウドサービスや開発ツール等で Java の利用者が増えていることを確認しています。そして我々は Java を利用するお客様と開発者の皆様のために、Java サポートの拡大を継続的に取り組んでいます。 Microsoft Build の OpenJDK バイナリは、OpenJDK のソースコードをベースに、Eclipse Adoptium プロジェクトで使用されているビルド・スクリプトと同じビルド・スクリプトを使用し、Eclipse Adoptium Quality Assurance Suite(OpenJDK プロジェクトテストを含む)を利用してテストを行なっています。 我々が提供する Java のバイナリは、Java の仕様と互換性を検証するために使用する Java11 用の Technology Compatibility Kit (TCK) に合格しています。 そこで、Microsoft Build の OpenJDK は、Java エコシステムで利用可能な他の OpenJDK ディストリビューションを簡単に置き換える事が可能です。 どうぞお試しください!!もし、Azure をご利用ならば、ブラウザから Azure の Cloud Shellにアクセスするか、Windows Terminal からアクセスしてください。 Microsoft における OpenJDK への取り組みは、当初は OpenJDK の開発プロセスの把握と参加方法について学ぶところから始めました。そして直近の過去 18 か月間、MacOS 用のパッケージ化、ビルドとインフラストラクチャの提供、GC のバグ修正、Windows 用の拡張機能の提供など、約 50 を超えるパッチを提供してきました。 中でも大きな貢献の1つに、JEP 388: Windows/AArch64 Port があります。 この作業は、Apple Silicon 用 MacOS 上で OpenJDK を利用するためのポートにとても重要で、2020年に新しいプラットフォーム用の OpenJDK16 のアーリー・アクセス・ビルドを公開しました。 x64 プラットフォーム用の OpenJDK11 をベースに、主要な3つのオペレーティングシステムをカバーし、この作業を継続して行い、その成果を Java コミュニティの皆様そして Microsoft Azure のお客様と共有できることを嬉しく思います。 Microsoft Build の OpenJDK 11 バイナリは、バックポートされたバグ修正対応や、マイクロソフトのお客様や社内利用者からのフィードバックに基づく拡張機能が含まれている可能性があります。こうした一部は、まだ正式にアップストリームにバックポートされていない場合もありますが、リリースノートに明確に示しています。これにより、これらの変更を並行してアップストリームに進めながら、改善と修正をより迅速に行うことができます。 OpenJDK のアップデートは無料で、すべてのJava開発者がどこにでもデプロイできるようになります。 過去数年間、我々は Azul Systems 社や他のベンダーと協力して、お客様、ユーザー、および私たち自身の業務に高品質な Java のサポートを提供してきました。マイクロソフトは、提供されたすばらしい支援に感謝しており、OpenJDK プロジェクトと Eclipse Adoptium ワーキンググループを通じて引き続き協力することを約束します。 Microsoft における Java: Microsoft ではさまざまな社内システム、アプリケーション、およびワークロードを Java テクノロジに依存して利用しており、有名なパブリックなサービスや製品、および Azure インフラを強化する為のミッション・クリティカルなシステムで利用されています。 我々は、Java ベースのシステムの最適化と、お客様とユーザーに利益をもたらすサプライチェーンの保護に取り組んできました。 マイクロソフト社内では、500,000 を超える Java 仮想マシン(JVM) のサービスをデプロイし(すべての Azure サービスと顧客のワークロードを除く)、バックエンドのマイクロサービスからビッグデータシステム、メッセージブローカー、イベントストリーミングサービス、ゲーム・サーバーに至るまで様々なニーズに対応しています。そして、これらの JVM の内 140,000 以上のサービスは、すでに Microsoft Build の OpenJDK を利用しています。 例えば、Azure 上でグローバル・インフラをサポートするための重要なタスクやビッグ・データ、さらにはログ分析システムなどで、オープンソースの Java プロジェクトを利用しています。実際、LinkedIn や Yammer のバックエンドサービスでは、ほぼ完全な Java の分散クラウドネイティブ・マイクロサービスとして実装されています。 Minecraft Java Editionは、Mod の重要なエコシステムでありいたるところに存在しています。そして熱狂的な Modder のコミュニティもいたるところに存在しています。そして Minecraft Realms のバックエンド・サーバでも Java を利用しています。Azureでは、Azure Spring Cloud、Azure App Service、Azure Functions、および Azure Kubernetes Service などの環境で Java の利用が大幅に増加しています。将来的にはこうした環境用に最適化した Java の実行環境を提供し、Microsoft Build の OpenJDK の利用を推奨したいと考えています。 今年後半には、Microsoft Build の OpenJDK は Azure マネージドサービス全体でデフォルトの Java11 ディストリビューションになります。 アプリケーションのデプロイにおいて、移行をスムーズかつ透過的に行うため、お客様はメンテナンスに関して意識する必要はありません。他のすべての Azure サービスにおいて、お客様は、Microsoft Build of OpenJDKを含め、お客様が選択した JDK を利用できます。今後数か月以内に、さらに詳しい情報を提供する予定です。 FAQ (よくあるご質問) 1. Java 11は2018年にリリースされました。なぜこれらのバイナリはプレビューなのですか? このディストリビューションは、OpenJDK11.0.10 の正式リリース版をコード・ベースにしていますが、今回提供する Microsoft Build の OpenJDK はプレビュー版です。これは、お客様とユーザーに対して、正式リリース前にパッケージングやインストールエクスペリエンスなどに関するフィードバックを頂く機会を得るためです。 2. Java 11をどのくらいの期間サポートしますか? Microsoftは、少なくとも2024年までJava11をサポートします。 3. 他のバージョンのJavaをリリースしますか? Java 17が完成次第、今年末までにOpenJDK17バイナリをリリースする予定です。 4. Java 8をサポートしますか? ターゲットの実行環境の選択肢として Java8 を提供する Azure のマネージドサービスでは、Microsoft は Eclipse Adoptium (以前のAdoptOpenJDK)のJava8バイナリをサポートします。 他の全 Azure サービスにおいて Java8 用の選択肢として、お客様は、Azul Systems の Zulu などの JDK を利用できます。しかし、Microsoft としては、クラウドへのデプロイやコスト削減、開発生産性の向上や、最新 Java によるいくつかの機能強化の恩恵を受けるために、Java11 以降のバージョンに移行することをお勧めします。バージョン・アップを行なって頂くことは、努力する価値があると信じており、この移行を進めて頂くための役立つガイダンスも提供しています。 5. これらのバイナリはどのようにライセンスされていますか? General Public License v2.0 (GPLv2+ Classpath Exceptionを含む)で提供します 6. Microsoft Build の OpenJDK を含むコンテナ・イメージを提供しますか? 間もなくDockerHubを介して公開します。 フィードバックをお寄せください Microsoft Build の OpenJDK を改善するために、コメント、アイデア、ご意見をお寄せください。 GitHub ページにアクセスし、フィードバックを送信できます。 ダウンロードは こちらから
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

String変数の比較

String変数を比較する場合の注意点 Javaの学習で、String型変数を比較する場合の処理が分かり辛かったので復習のために記事にしてみます。 関係演算子(==)で比較してみる String型変数を関係演算子(==)で比較する場合の挙動を下記のコードで一度確認してみましょう。 String a = "hoge"; String b = new String("hoge"); String c = "hoge"; String d = b.intern(); System.out.println("a = b : " + (a == b)); System.out.println("a = c : " + (a == c)); System.out.println("a = d : " + (a == d)); System.out.println("b = d : " + (b == d)); 変数aはnewを使用していないため、Stringが管理する領域に文字列("hoge")が作成され、参照されます。 変数bはnewを使用しているため、ヒープ領域に文字列("hoge")が作成され、参照されます。 変数cは変数aと同じく、newを使用していないため、Stringが管理する領域の文字列("hoge")を参照を参照します。 ※変数aの作成時にStringが管理する領域に"hoge"が生成されているため、変数cの作成時は同じものを参照するようになります。 変数dは変数bに対してintern()メソッドを使用した値を代入していますが、intern()メソッドは参照先ではなく参照先の値を返しているため、Stringが管理する領域に同じ文字列が存在しないか確認し、存在する場合はその領域を返します。 実行結果 a = b : false a = c : true a = d : true b = d : false 実行結果 1行目:変数aとbが比較されますが、参照先が違うため(aはStringの領域、bはnewで作成されたため別の領域)falseになります。 2行目:変数aとcは同じ参照先(Stringの領域の"hoge")なので、trueになります。 3行目:変数aとdは同じ参照先(Stringの領域の"hoge")なので、trueになります。 4行目:変数bとdされていますが、参照先が違うため(bはnewで作成された領域、dはStringの領域)falseになります。 このように参照型変数を関係演算子(==)で比較する場合、参照先の値ではなく参照先が比較され、同じ参照先の場合にtrueを返します。 equals()メソッドを利用してみる 関係演算子(==)を利用するだけではString型変数の参照先の値では比較できないのでequals()メソッドを利用して参照先の値で比較を行ってみます。 String a = "hoge"; String b = new String("hoge"); String c = "hoge"; String d = b.intern(); System.out.println("a.equals(b) : " + (a.equals(b) ) ); System.out.println("a.equals(c) : " + (a.equals(c) ) ); System.out.println("a.equals(d) : " + (a.equals(d) ) ); System.out.println("b.equals(d) : " + (b.equals(d) ) ); 実行結果 a.equals(b) : true a.equals(c) : true a.equals(d) : true b.equals(d) : true equals()メソッドでは参照先の値を比較しているため、全てtrueとなります。 まとめ 参照型変数(String等)を関係演算子(==)で比較すると参照先の値ではなく、参照先が一致しているかを比較する。 参照型変数をnewを使って生成すると、値が同じものでも別の領域に値が生成されて参照される。 String型変数の参照先の値を比較したい場合はequals()メソッドを利用すれば比較できる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ObjectMapperを試してみた

概要 JavaのclassとJsonの変換に関して試したことを列挙します! 利用例 JavaクラスからJson文字列への変換 import lombok.AllArgsConstructor; import lombok.Getter; @AllArgsConstructor @Getter public class Response { String accessToken; String refreshToken; } @Test void オブジェクトをJsonに変換できる() throws Exception { // arrange var response = new Response("a", "b"); // act, assert assertEquals("{\"accessToken\":\"a\",\"refreshToken\":\"b\"}", new ObjectMapper().writeValueAsString(response)); } Json文字列からJavaクラスへの変換 import lombok.*; @AllArgsConstructor @Getter @EqualsAndHashCode @NoArgsConstructor public class Response { String accessToken; String refreshToken; } @Test void Jsonをオブジェクトに変換できる() throws Exception { // arrange var jsonString = "{\"accessToken\":\"a\",\"refreshToken\":\"b\"}"; // act, assert assertEquals(new Response("a", "b"), new ObjectMapper().readValue(jsonString, Response.class)); } JavaクラスからJson文字列への変換(スネークケース) import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import lombok.AllArgsConstructor; import lombok.Getter; @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) @AllArgsConstructor @Getter public class SnakeResponse { String accessToken; // フィールドをpublicにするのでもOK  String refreshToken; } @Test void オブジェクトをJsonにスネークケースで変換できる() throws Exception { // arrange var response = new SnakeResponse("a", "b"); // act, assert assertEquals("{\"access_token\":\"a\",\"refresh_token\":\"b\"}", new ObjectMapper().writeValueAsString(response)); } PropertyNamingStrategyのバリエーション Strategy 結果 LowerDotCaseStrategy {"access.token":"a","refresh.token":"b"} SnakeCaseStrategy {"access_token":"a","refresh_token":"b"} KebabCaseStrategy {"access-token":"a","refresh-token":"b"} LowerCaseStrategy {"accesstoken":"a","refreshtoken":"b"} UpperCamelCaseStrategy {"AccessToken":"a","RefreshToken":"b"} nullがある場合は表示しない @AllArgsConstructor @Getter @EqualsAndHashCode @NoArgsConstructor @JsonInclude(JsonInclude.Include.NON_NULL) public class Response { String accessToken; String refreshToken; } @Test void オブジェクトにnullがある場合_そのプロパティは表示しない() throws Exception { // arrange var response = new Response("a", null); // act, assert assertEquals("{\"accessToken\":\"a\"}", new ObjectMapper().writeValueAsString(response)); } エラー フィールドが見えない時のエラー No serializer found for class Response and no properties discovered to create BeanSerializerはgetterをつければ解消できます。 @AllArgsConstructor //@Getter // ここがないからエラー もちろん自分でgetter作ってもOK public class Response { String accessToken; // フィールドをpublicにするのでもOK  String refreshToken; } @Test void オブジェクトをJsonに変換できる() throws Exception { // arrange var response = new Response("a", "b"); // act, assert assertEquals("{\"accessToken\":\"a\",\"refreshToken\":\"b\"}", new ObjectMapper().writeValueAsString(response)); } コンストラクタがないエラー Cannot construct instance of Response (no Creators, like default constructor, exist): cannot deserialize from Object value @AllArgsConstructor @Getter @EqualsAndHashCode //@NoArgsConstructor // これがない public class Response { String accessToken; String refreshToken; } @Test void Jsonをオブジェクトに変換できる() throws Exception { // arrange var jsonString = "{\"accessToken\":\"a\",\"refreshToken\":\"b\"}"; // act, assert assertEquals(new Response("a", "b"), new ObjectMapper().readValue(jsonString, Response.class)); }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

配列に要素を追加するプログラム(Java)

はじめに Javaで配列に要素を追加するプログラムをメソッドの形にまとめて投稿したいと思います.「Java 配列 要素 追加」と検索しても、Pythonのappend()などのように要素を追加すると,それに伴って大きさも増えてくれるメソッドなどがなさそうなのかなと思ったので,備忘録も兼ねて残します. JavaのList<>とか使えって.add()などを利用すれば,やりたいことは実現できそうなのですが,List<>を使わずに動的な要素追加ってできないだろうかと考えて書いてみました. コード 確認用のコード 実行したら要素を追加する前の配列と追加した後の配列を標準出力するプログラムです. Main.java class Main{ public static void main(String[] args){ // 追加対象の配列 int[] oldArr = {0, 1, 2, 3, 4}; // 追加したい要素 int newElement = 5; // 追加後の配列を格納する配列 int[] newArr; // forを回すための制御変数 int n; // 追加前の配列を表示 System.out.println("追加前の配列"); for(n = 0 ; n < oldArr.length ; n++){ System.out.println(oldArr[n]); } // 要素を追加する関数を実行 // ここで配列に要素が追加されてます. newArr = AddElement(oldArr, newElement); // 追加後の配列を表示 System.out.println("追加後の配列"); for(n = 0 ; n < newArr.length ; n++){ System.out.println(newArr[n]); } } // 追加前の配列と追加したい要素を受け取って,追加後の配列を返すメソッド public static int[] AddElement(int[] oldArr, int newElement){ // 追加後の配列を宣言 // 大きさは追加前の配列の大きさより1だけ大きい int[] newArr = new int[oldArr.length + 1]; // 追加後の配列に追加前の配列をコピー // ここの処理が肝 System.arraycopy(oldArr, 0, newArr, 0, oldArr.length); // 追加後の配列の末尾に追加したい要素を代入 newArr[oldArr.length] = newElement; // 追加後の配列を返却 return newArr; } } 「要素を追加して追加後の配列を返すメソッド」だけを利用する場合は,下のようにAddElement()の部分をコピペしていただければと思います. AddElement.java class AddElement{ public static int[] AddElement(int[] oldArr, int newElement){ int[] newArr = new int[oldArr.length + 1]; System.arraycopy(oldArr, 0, newArr, 0, oldArr.length); newArr[oldArr.length] = newElement; return newArr; } } また,今回は配列の中身の変数型がintですが,必要でしたらint[]のintの部分をStringやdoubleなどに変えて文字列配列やdouble配列などに変えてください. 実行結果 実行するとこんな感じになります. 追加前の配列 0 1 2 3 4 追加後の配列 0 1 2 3 4 5 おわりに JavaのList以外で配列に要素を追加するプログラムを書けないかなと思って書いてみました.もしかしたらList<>を呼び出してList<>を配列にキャストして返した方が速いプログラムかもしれませんが,すみませんそこまでは試してないです泣 「Java 配列 要素 追加」で検索しても「Listを使わずにやる方法ってないのかなあ」とか「要素の大きさを追加に伴って自動的に増やしたいんだけど別の方法ないのかな」といった風に感じられていらっしゃる方に貢献出来たらと思います.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JDKソースコードレベルからのJVMクラスロードモデルの徹底解説

クラスのロードと実行のプロセス全体 javaコマンドを使用して特定のクラスのmain関数を実行してプログラムを開始する場合、最初にクラスローダーを介してメインクラスをJVMにロードする必要があります。 Math.java package com.rocdocs.jvm; public class Math { public static final int initData = 666; public static User user = new User(); public int compute() { //各メソッドは、スタックフレームメモリ(領域)に対応します。 int a = 1; int b = 2; int c = (a + b) * 10; return c; } public static void main(String[] args) { Math math = new Math(); math.compute(); } } Javaコマンドを使用してコードを実行する一般的なプロセスは下図のとおりです: loadClassのクラス読み込みプロセスには、次の手順があります: ロード>>検証>>準備>>分析>>初期化>>使用>>アンインストール ロード:ハードディスク上でバイトコードファイルを見つけ、IOを介して読み取ります。クラスのmain()メソッド、新しいオブジェクトなど、およびこのクラスの代表を呼び出すなど、クラスが使用されるときにロードされます。ロードフェーズ中にメモリに生成されますjava.lang.Classオブジェクトは、メソッド領域でこのクラスのさまざまなデータのアクセスエントリとして使用されます。 検証:バイトコードファイルの正確さを検証します。 準備:クラスの静的変数にメモリを割り当て、デフォルト値を割り当てます。 解決策:シンボリック参照を直接参照に置き換えます。この段階で、一部の静的メソッド(main()メソッドなどのシンボル参照)は、データが格納されているメモリへのポインターまたはハンドル(直接参照)に置き換えられます。いわゆる静的リンクプロセス(クラスのロード中に完了)、動的リンクはプログラムの実行中に実行され、シンボル参照を直接参照に置き換えます。動的リンクについては、次のレッスンで説明します。 初期化:クラスの静的変数を指定された値に初期化し、静的コードブロックを実行します。 クラスがメソッド領域にロードされると、主にランタイム定数プール、タイプ情報、フィールド情報、メソッド情報、クラスローダーへの参照、対応するクラスインスタンスへの参照およびその他の情報が含まれます。 クラスローダー参照:このクラスのクラスローダーインスタンスへの参照 対応するクラスインスタンスへの参照:クラスローダーがクラス情報をロードしてメソッド領域に配置した後、対応するクラスタイプのオブジェクトインスタンスを作成し、開発者のエントリおよびエントリとしてヒープ(ヒープ)に配置します。メソッド領域エントリポイントのクラス定義にアクセスします。 動作中にメインクラスで他のクラスが使用されている場合、これらのクラスは徐々にロードされることに注意してください。 jarパッケージまたはwarパッケージのクラスは、一度にロードされるのではなく、使用されたときにのみロードされます。 public class DynamicLoadTest { static { System.out.println("------------load DynamicLoadTest------------"); } public static void main(String[] args) { new A(); System.out.println("------------*load test------------"); B b = null; //ここで新しいB()が実行されない限り、Bはロードされません。 } } class A { static { System.out.println("------------load A------------"); } public A() { System.out.println("------------initial A------------"); } } class B { static { System.out.println("------------load B------------"); } public B() { System.out.println("------------initial B------------"); } } 実行結果: ------------load DynamicLoadTest------------ ------------load A------------ ------------initial A------------ ------------load test------------ クラス​ローダーと委任モデル 上記のクラスロードプロセスは、主にクラスローダーを介して実現されます。Javaには次のタイプのクラスローダーがあります。 ブートクラスローダー:rt.jar、charsets.jarなどのJVM操作をサポートするJREのlibディレクトリにコアクラスライブラリをロードする役割を果たします。 拡張クラスローダー:JVM操作をサポートするJREのlibディレクトリの下のext拡張ディレクトリにJARパッケージのロードを担当します。 アプリケーションクラスローダー:ClassPathパスでクラスパッケージをロードし、主に自分で作成したクラスをロードします。 カスタムローダー:ユーザー定義のパスでクラスパッケージのロードを担当します。 クラスローダーの例を見ましょう。 JDKClassLoaderTest.java public class JDKClassLoaderTest { public static void main(String[] args) { System.out.println(String.class.getClassLoader()); System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName()); System.out.println(JDKClassLoaderTest.class.getClassLoader().getClass().getName()); System.out.println(); ClassLoader appClassLoader = ClassLoader.getSystemClassLoader(); ClassLoader extClassloader = appClassLoader.getParent(); ClassLoader bootstrapLoader = extClassloader.getParent(); System.out.println("the bootstrapLoader : " + bootstrapLoader); System.out.println("the extClassloader : " + extClassloader); System.out.println("the appClassLoader : " + appClassLoader); System.out.println(); System.out.println("bootstrapLoaderは下記のファイルをロード:"); URL[] urls = Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i]); } System.out.println(); System.out.println("extClassloaderは下記のファイルをロード:"); System.out.println(System.getProperty("java.ext.dirs")); System.out.println(); System.out.println("appClassLoadeは下記のファイルをロード:"); System.out.println(System.getProperty("java.class.path")); } } 実行結果: null sun.misc.Launcher$ExtClassLoader sun.misc.Launcher$AppClassLoader the bootstrapLoader : null the extClassloader : sun.misc.Launcher$ExtClassLoader@3764951d the appClassLoader : sun.misc.Launcher$AppClassLoader@14dad5dc bootstrapLoaderは下記のファイルをロード: file:/c:/workspace/java/jdk1.8.0_45/jre/lib/resources.jar file:/c:/workspace/java/jdk1.8.0_45/jre/lib/rt.jar file:/c:/workspace/java/jdk1.8.0_45/jre/lib/sunrsasign.jar file:/c:/workspace/java/jdk1.8.0_45/jre/lib/jsse.jar file:/c:/workspace/java/jdk1.8.0_45/jre/lib/jce.jar file:/c:/workspace/java/jdk1.8.0_45/jre/lib/charsets.jar file:/c:/workspace/java/jdk1.8.0_45/jre/lib/jfr.jar file:/c:/workspace/java/jdk1.8.0_45/jre/classes extClassloaderは下記のファイルをロード: c:\workspace\java\jdk1.8.0_45\jre\lib\ext;c:\java\lib\ext appClassLoaderは下記のファイルをロード: c:\workspace\java\jdk1.8.0_45\jre\lib\charsets.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\deploy.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\localedata.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\sunec.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\javaws.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\jce.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\jfr.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\jfxswt.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\jsse.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\management-agent.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\plugin.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\resources.jar;c:\workspace\java\jdk1.8.0_45\jre\lib\rt.jar;c:\ideaProjects\project-all\target\classes;C:\Users\roc\.m2\repository\org\apache\zookeeper\zookeeper\3.4.12\zookeeper-3.4.12.jar;C:\Users\roc\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar;C:\Users\roc\.m2\repository\org\slf4j\slf4j-log4j12\1.7.25\slf4j-log4j12-1.7.25.jar;C:\Users\roc\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\roc\.m2\repository\jline\jline\0.9.94\jline-0.9.94.jar;C:\Users\roc\.m2\repository\org\apache\yetus\audience-annotations\0.5.0\audience-annotations-0.5.0.jar;C:\Users\roc\.m2\repository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;C:\Users\roc\.m2\repository\com\google\guava\guava\22.0\guava-22.0.jar;C:\Users\roc\.m2\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305-1.3.9.jar;C:\Users\roc\.m2\repository\com\google\errorprone\error_prone_annotations\2.0.18\error_prone_annotations-2.0.18.jar;C:\Users\roc\.m2\repository\com\google\j2objc\j2objc-annotations\1.1\j2objc-annotations-1.1.jar;C:\Users\roc\.m2\repository\org\codehaus\mojo\animal-sniffer-annotations\1.14\animal-sniffer-annotations-1.14.jar;c:\dev\IntelliJ IDEA 2018.3.2\lib\idea_rt.jar クラスローダーの初期化プロセス クラスの実行とロードのプロセス全体の図を参照して、JVMランチャーインスタンスsun.misc.Launcherが作成されることが分かります。 Launcher構築メソッド内に、sun.misc.Launcher.ExtClassLoader(拡張クラスローダー)とsun.misc.Launcher.AppClassLoader(アプリケーションクラスローダー)の2つのクラスローダーが作成されます。 デフォルトでは、JVMはLauncherのgetClassLoader()メソッドによって返されるクラスローダーAppClassLoaderのインスタンスを使用して、アプリケーションをロードします。 //Launcherのコンストラクタ public Launcher() { Launcher.ExtClassLoader var1; try { //拡張クラスローダーを構築し、構築プロセス中にその親ローダーをnullに設定する var1 = Launcher.ExtClassLoader.getExtClassLoader(); } catch (IOException var10) { throw new InternalError("Could not create extension class loader", var10); } try { //アプリケーションクラスローダーを構築し、構築プロセス中にその親ローダーをExtClassLoaderに設定する。 //Launcherのローダー属性値はAppClassLoaderです。通常、このクラスローダーを使用して独自のアプリケーションをロードする。 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); } catch (IOException var9) { throw new InternalError("Could not create application class loader", var9); } Thread.currentThread().setContextClassLoader(this.loader); String var2 = System.getProperty("java.security.manager"); ...... //気にする必要のないコードは省略 } 委任モデル 以下に示すように、JVMクラスローダーには親子階層があります。 実際には、クラスをロードするための委任モデルがあります。特定のクラスをロードするとき、最初に親ローダーにターゲットクラスの検索を委託し、次に、上位の親ローダーが見つからない場合は読み込みを委託します。すべての親ローダーが独自のロードクラスパス内ターゲットクラスが見つからない場合は、独自のクラスロードパス内のターゲットクラスを検索してロードします。 たとえば、Mathクラスは最初にロードするアプリケーションクラスローダーを見つけ、アプリケーションクラスローダーは最初に拡張クラスローダーにロードを委託し、次に拡張クラスローダーはブートクラスローダーに委託し、トップレベルのブートクラスローダーは独自のクラスロードパスを長時間検索し、Mathクラスが見つからなかった場合、Mathクラスをロードする要求に戻り、拡張クラスローダーは応答を受信した後に自身をロードします。独自のクラスで検索した後長い間パスをロードしていると、Mathクラスが見つかりません。アプリケーションクラスローダーへのMathクラスのロード要求に戻ると、アプリケーションクラスローダーは独自のクラスロードパスでMathクラスを探し、ロードします。それが見つかるとそれ自体。 一言で、委任モデルとは、先ずは親クラスローダーがロードする、失敗だったら、子クラスローダーがロードすることである。 アプリケーションクラスローダーAppClassLoaderローディングクラスの委任モデルのソースコードを見てみましょう。AppClassLoaderのloadClassメソッドは、最終的にその親クラスClassLoaderのloadClassメソッドを呼び出します。メソッドの一般的なロジックは次のとおりです。 まず、指定した名前のクラスがロードされているかどうかを確認します。ロードされている場合は、再度ロードして直接戻る必要はありません。 このクラスがロードされていない場合は、親ローダーがあるかどうかを判断します。親ローダーがある場合は、親ローダーによってロードされます(つまり、parent.loadClass(name、false);を呼び出します)。または、ブートストラップクラスローダーを呼び出してロードします。 親ローダーもブートストラップクラスローダーも指定されたクラスを見つけられない場合は、現在のクラスローダーのfindClassメソッドを呼び出して、クラスのロードを完了します。 //委任モデルがClassLoaderのloadClassメソッドで実現された protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 現在のクラスローダーがクラスをロードしたかどうかを確認する Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { //現在のローダーの親ローダーが空でない場合は、親ローダーを委任してクラスをロードする c = parent.loadClass(name, false); } else { //現在のローダーの親ローダーが空の場合は、ブートクラスローダーを委任してクラスをロードする c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //URLClassLoaderのfindClassメソッドを呼び出して、ローダーのクラスパスでクラスを検索してロードする c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } } なぜ委任モデルを設計するのですか? サンドボックスのセキュリティモデル:自分で作成したjava.lang.String.classは読み込まれないため、コアAPIライブラリが自由に改ざんされるのを防ぐことができます。 クラスの繰り返しロードを回避します。父親がすでにクラスをロードしている場合、ロードされたクラスの一意性を確保するために子ClassLoaderを再度ロードする必要はありません。 クラスの読み込みの例を見てください。 package java.lang; public class String { public static void main(String[] args) { System.out.println("------------My String Class------------"); } } 実行結果: Error: The main method cannot be found in the class java.lang.String, please define the main method as: public static void main(String[] args) Otherwise the JavaFX application class must extend javafx.application.Application 委任モデルの全責任 「全責任」とは、ClassLoderがクラスをロードするときに、別のClassLoderが明示的に使用されていない限り、クラスが依存し参照するクラスもこのClassLoderによってロードされることを意味します。 カスタムクラスローダーの例 カスタムクラスローダーは、java.lang.ClassLoaderクラスを継承するだけで済みます。このクラスには、委任モデルを実装するloadClass(String、boolean)とデフォルトの実装であるfindClassの2つのコアメソッドがあります。空のメソッドなので、カスタムクラスローダーは主にfindClassメソッドをオーバーライドすることです。 MyClassLoaderTest.java public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); //defineClassは、バイト配列をClassオブジェクトに変換します。このバイト配列は、クラスファイルを読み取った後の最後のバイト配列です。 return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } } public static void main(String args[]) throws Exception { //カスタムクラスローダーを初期化するには、最初に親クラスClassLoaderが初期化され、カスタムクラスローダーの親ローダーがアプリケーションクラスローダーAppClassLoaderに設定されます。 MyClassLoader classLoader = new MyClassLoader("c:/workspace/test"); Class clazz = classLoader.loadClass("com.rocdocs.jvm.User1"); Object obj = clazz.newInstance(); Method method = clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); } } 実行結果: =======カスタムローダーローディングクラス呼び出しメソッド======= com.rocdocs.jvm.MyClassLoaderTest$MyClassLoader 委任モデルを破る サンドボックスセキュリティモデルの別の例を見てみましょう。委任モデルを解除し、カスタムクラスローダーを使用してjava.lang.String.classの独自の実装をロードしてみてください。 MyClassLoaderTest.java public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } /** * クラスのロードメソッドをオーバーライドし、独自のロードロジックを実装し、ロードを親に委任しないでください * @param name * @param resolve * @return * @throws ClassNotFoundException */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } } public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader("c:/workspace/test"); //自分でクラスロードモデルを書き直して、自分で作成したjava.lang.String.classをロードしてみてください Class clazz = classLoader.loadClass("java.lang.String"); Object obj = clazz.newInstance(); Method method= clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader().getClass().getName()); } } 実行結果: java.lang.SecurityException: Prohibited package name: java.lang at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659) at java.lang.ClassLoader.defineClass(ClassLoader.java:758) Tomcatのクラスローダーは委任モデルですか 例としてTomcatクラスのロードを取り上げます。Tomcatがデフォルトの親委任クラスのロードモデルを使用している場合、それは機能しますか? 考えてみましょう。TomcatはWebコンテナなので、どのような問題が解決しないといけないでしょうか。 Webコンテナは、2つのアプリケーションをデプロイする必要がある場合があります。異なるアプリケーションは、同じサードパーティクラスライブラリの異なるバージョンに依存する場合があります。同じクラスライブラリが同じサーバー上に1つのコピーしかない必要はありません。したがって、次のことを行う必要があります。各アプリケーションクラスライブラリが独立していることを確認し、相互に分離します。 同じWebコンテナにデプロイされた同じクラスライブラリの同じバージョンを共有できます。 それ以外の場合、サーバーに10個のアプリケーションがある場合は、同じクラスライブラリの10個のコピーを仮想マシンにロードする必要があります。 Webコンテナには、独自の依存クラスライブラリもあります。これをアプリケーションクラスライブラリと混同しないでください。 セキュリティ上の考慮事項に基づいて、コンテナのクラスライブラリはプログラムのクラスライブラリから分離する必要があります。 Webコンテナはjspの変更をサポートする必要があります。仮想マシンで実行する前にjspファイルをクラスファイルにコンパイルする必要があることはわかっています。ただし、プログラムの実行後にjspを変更するのが一般的です。Webコンテナ再起動せずにjspの変更をサポートする必要があります。 もう一度質問を見てみましょう。Tomcatはデフォルトのロード委任モデルを使用できますか? 答えはいいえだ。 どうして? 第1点は、デフォルトのクラスローダーモデルを使用する場合、同じクラスライブラリの2つの異なるバージョンをロードできないことです。デフォルトのクラス加算器は、バージョンに関係なく、完全修飾クラス名のみを考慮します。 1つだけです。 2番目は、デフォルトのクラスローダーが達成可能であるということです。これは、その責任が一意性を確保することであるためです。 3番目は最初の質問と同じです。 4番目をもう一度見てみましょう。jspファイルのホットロードを実現する方法を考えます。jspファイルは実際にはクラスファイルです。変更されてもクラス名が同じである場合、クラスローダーは直接既存のメソッド領域はい、変更されたjspは再ロードされません。 じゃあ何をすればいいの? このjspファイルのクラスローダーを直接アンロードできるので、各jspファイルが一意のクラスローダーに対応していると考えておく必要があります。jspファイルが変更されると、jspクラスローダーが直接アンロードされます。 クラスローダーを再作成し、jspファイルをリロードします。 Tomcatカスタムローダーの詳細な説明 Tomcatのいくつかの主要なクラスローダー: commonLoader:Tomcatの最も基本的なクラスローダーであるロードパスのクラスには、Tomcatコンテナ自体と各Webappからアクセスできます。 catalinaLoader:Tomcatコンテナのプライベートクラスローダー。ロードパスのクラスはWebappに表示されません。 sharedLoader:各Webappによって共有されるクラスローダー。ロードパス内のクラスはすべてのWebappに表示されますが、Tomcatコンテナには表示されません。 WebappClassLoader:各Webappプライベートクラスローダー、ロードパス内のクラスは、warパッケージ内の関連クラスのロードなど、現在のWebappにのみ表示されます。各warパッケージアプリケーションには、異なる戦争などの相互分離を実現するための独自のWebappClassLoaderがあります。パッケージアプリケーションはさまざまなSpringバージョンを導入しているため、実装はそれぞれのSpringバージョンをロードできます。 これは、図の委任関係からわかります。 CommonClassLoaderがロードできるクラスは、CatalinaClassLoaderとSharedClassLoaderで使用できるため、パブリッククラスライブラリの共有が実現されますが、CatalinaClassLoaderとSharedClassLoaderがロードできるクラスは互いに分離されています。 WebAppClassLoaderは、SharedClassLoaderにロードされたクラスを使用できますが、WebAppClassLoaderの各インスタンスは互いに分離されています。 JasperLoaderのロード範囲は、このJSPファイルによってコンパイルされた.Classファイルのみです。その目的は破棄されます。WebコンテナがJSPファイルが変更されたことを検出すると、JasperLoaderの現在のインスタンスを置き換えます。ホットロードJSPファイルの機能は、新しいJspクラスローダーを作成することで実現されます。 tomcatは、Javaが推奨する委任モデルに違反していますか? 答えは:違反です。 明らかに、tomcatはこの方法で実装されていません。分離を実現するために、tomcatはこの規則に従いません。各webappClassLoaderは、独自のディレクトリにクラスファイルをロードし、親クラスローダーに渡さないため、委任モデルが無効になります。 TomcatのwebappClassLoaderの実現をシミュレートして、独自のwarパッケージをロードします。アプリケーション内の異なるバージョンのクラスは、相互の共存と分離を実現します。 MyClassLoaderTest.java public class MyClassLoaderTest { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } /** * クラスのロードメソッドをオーバーライドし、独自のロードロジックを実装し、ロードを親に委任しないにする * @param name * @param resolve * @return * @throws ClassNotFoundException */ protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded Class<?> c = findLoadedClass(name); if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); //カスタマイズされていないクラスは、委任された親によって引き続き読み込まれます if (!name.startsWith("com.rocdocs.jvm")){ c = this.getParent().loadClass(name); }else{ c = findClass(name); } // this is the defining class loader; record the stats sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } if (resolve) { resolveClass(c); } return c; } } } public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader("c:/workspace/test"); Class clazz = classLoader.loadClass("com.rocdocs.jvm.User1"); Object obj = clazz.newInstance(); Method method= clazz.getDeclaredMethod("sout", null); method.invoke(obj, null); System.out.println(clazz.getClassLoader()); System.out.println(); MyClassLoader classLoader1 = new MyClassLoader("c:/workspace/test1"); Class clazz1 = classLoader1.loadClass("com.rocdocs.jvm.User1"); Object obj1 = clazz1.newInstance(); Method method1= clazz1.getDeclaredMethod("sout", null); method1.invoke(obj1, null); System.out.println(clazz1.getClassLoader()); } } 実行結果: =======カスタムローダーローディングクラス呼び出しメソッド======= com.rocdocs.jvm.MyClassLoaderTest$MyClassLoader@266474c2 =======別のUser1バージョン:カスタムローダーローディングクラス呼び出しメソッド======= com.rocdocs.jvm.MyClassLoaderTest$MyClassLoader@66d3c617 注:同じJVMでは、クラスローダーが異なる可能性があるため、同じパッケージ名とクラス名を持つ2つのクラスオブジェクトが共存できます。したがって、クラスパッケージInを除いて、2つのクラスオブジェクトが同じであるかどうかを確認してください。名前とクラス名が同じであるかどうかに加えて、同じと見なされるには、同じクラスローダーが必要です。 TomcatのJasperLoaderホットローディングをシミュレートします 原則:バックグラウンドの起動スレッドがjspファイルの変更を監視します。変更が見つかった場合、jspに対応するサーブレットクラスのローダー参照(gcroot)が見つかり、新しいJasperLoaderローダーが参照に割り当てられます。新しいjsp対応のサーブレットクラスがロードされますローダーはgcrootによって参照されないため、次のgcで破棄されます。 Userクラスのソースコードを添付します。 User.java package com.rocdocs.jvm; public class User { private int id; private String name; public User() { } public User(int id, String name) { super(); this.id = id; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void sout() { System.out.println("=======カスタムローダーローディングクラス呼び出しメソッド======="); } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSP/ServletアプリケーションをAzure WebApps Tomcatの複数コンテキストにデプロイする

JSP/ServletアプリケーションをAzure WebApps Tomcatの複数コンテキストにデプロイする 前回は Spring Boot アプリケーション を Azure WebApps にデプロイしたわけですが、そんなモダンなフレームワークなんか使ってない場合も多いと思います。というわけで今回は、世の中で多く稼働しているであろう Tomcat 上で動くJSP/ServletアプリケーションをAzure WebAppsにデプロイするポイントなどを解説します。 Tomcatですので複数のWebアプリケーション (war) をデプロイできます。単純にルートコンテキストにWebアプリケーションをデプロイするのは簡単なのですが、複数のコンテキストにWebアプリケーションをデプロイしようとするとちょっとしたコツが必要なので、そこを解説してみます。 実は2年ほど前にも一度調べていて、以下に備忘録代わりのサンプルを置いてあります。このサンプルはこのままで動きますが、 Maven webapps pluginのバージョンを最新にすると動かなくなります。 m-moris/azure-java-sample-howto-deploy-webapps: How to deploy webapps with java その際にエラーがでるので、それを元にググりまくると解決策がわかるのですが、あとでリンクする文書含めてきちんと説明されてないなという印象です。 プロジェクトの準備 MavenでシンプルなWebアプリケーションを作成します。index.jsp のみですが、挙動を確認するだけですので、これで十分でしょう。 mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.4 コンテキストパスを表示するようにだけ修正しておきます。 <body> <h1>Hello Azure!!</h1> <h2> ContextPath = ${pageContext.request.contextPath} </h2> </body> ルートコンテキストへのデプロイ(なにも設定しない場合) 前回の記事と同じくWebAppの構成をします。ここでは、Linux/Java11/Tomcat9 にします。 mvn com.microsoft.azure:azure-webapp-maven-plugin:1.13.0:config リージョンを japaneastに修正してデプロイします。 mvn clean package azure-webapp:deploy ブラウザ確認すると、ルートコンテキストにデプロイされており、 Kudu(※)で確認すると app.war が /home/site/wwwroot にデプロイされています。ここに app.war があると、それがルートコンテキストにデプロイされたことになるようです。 ブラウザでのアクセス Kudu 罠ではあるのですが、この状態になると後述する手順で正しくコンテキストパス毎にデプロイしても、正しく動作してくれません。 /home/site/wwwroot にある app.war が何やら邪魔しているくさい(どういう原理?)。KudoからSSHでapp.warを消すと正しく動作するので、ここにapp.warがあることがなにやら関係している臭いです。 ※ WebAppsの管理コンソール的なもので、ポータルから高度なツールで起動できる。WebAppsが動いているVMの中身を参照できるが、操作はできない。 正しくルートコンテキストにデプロイする 実は <targetPath> 要素というものがあり、これを指定することで希望するロケーションにデプロイできます。 以下のサンプルのように <targetPath> 要素でデプロイ先を指定できるので、webapps/ 配下に ROOT.war にデプロイすればルートコンテキストになります。 <build> <finalName>ROOT</finalName> <!-- ※ --> <plugins> <plugin> <groupId>com.microsoft.azure</groupId> <artifactId>azure-webapp-maven-plugin</artifactId> <version>1.13.0</version> <configuration> <schemaVersion>v2</schemaVersion> <subscriptionId>xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</subscriptionId> <resourceGroup>sample-jsp-88888</resourceGroup> <appName>sample-jsp-88888</appName> <pricingTier>B2</pricingTier> <region>japaneast</region> <runtime> <os>Linux</os> <javaVersion>Java 11</javaVersion> <webContainer>Tomcat 9.0</webContainer> </runtime> <deployment> <resources> <resource> <directory>${project.basedir}/target</directory> <targetPath>webapps/${project.build.finalName}</targetPath> <!-- ※ --> <includes> <include>*.war</include> </includes> </resource> </resources> </deployment> </configuration> </plugin> </plugins> </build> Kuduで参照すると、webapps/ROOT に展開されている事が分ります。 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34017/5ec5ae5a-4ec9-a38a-26fd-64bb4da2602f.png ブラウザからもアクセスできます。 次に ROOT を <finalName>sample</finalName> にでも変更してデプロイしてみましょう。 再びKuduで確認すると sampleが展開されていることが確認できます。 ブラウザからも正しくアクセスできることが確認できます。 まとめ <targetPath>を指定すれば正しく複数Webアプリケーションをデプロイできることが確認できました。あまり丁寧に解説しているドキュメントを見たことがないので、はまっている人の一助になればと。 一応、このあたりに記述はあるのですが、ちっさすぎて見逃しちゃいますよねぇ。 https://docs.microsoft.com/ja-jp/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?* view=azure-java-stable#resource https://github.com/microsoft/azure-maven-plugins/wiki/Azure-Web-App:-Configuration-Details#resource また、ここにのサンプルにも記述がありますが、Pluginバージョンが古いので注意が必要です。 https://docs.microsoft.com/ja-jp/java/api/overview/azure/maven/docs/web-app-samples?view=azure-java-stable さらに補足 プラットフォームがWindows の場合も同じかと思いますが、昔検証したときは Windows版のTomcatですと、war を展開しない仕様だったり、微妙にLinuxの場合と設定が違ったりもしたのでちょっと注意が必要です。時間があったら、後で確認するかもしれませんが。 ではでは。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Garadleで外部ライブラリを使用しているJavaの実行ファイルを出力する

概要 Javaのプログラムをjarなどのファイルにして実行する際に、外部のライブラリを使用したコードが含まれているものに対応したい時があります。その際には必要なjarを用意してクラスパスに設定する必要があるのですが、手動で設定していくのは少し面倒です。Gradleには外部ライブラリを含めて実行形式のファイルを出力する機能があるので、今回紹介します。 対応方法 Gradle使い方メモの記事にある通り、distZipを実行すると依存の外部ライブラリjarを含めて、ファイルを出力してくれます。また実行スクリプトも出力してくれるので、起動するときはこれを使えば良さそうです。 実行結果 こちらに私の方で作成したddd-outputというJavaのアプリケーションでのdistZipの実行結果を配置しているので、中身を紹介します。 【binのファイル】 Linux(Mac)用のシェルとWindows用のbatファイルが出力されています。 シェルの中身を見ると、クラスパスに外部ライブラリのjarを含めたファイルがclasspathに設定されているのが分かります。 【libのファイル】 こちらに今回作成したアプリケーションのjar(ddd-output.jar)と、使用している外部ライブラリのjarが出力されています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

jenv+caskでJDKのバージョン管理(Mac)

何をしたか MacのローカルでJavaのバージョン管理 本題 手順概要 ・homebrew-caskでJDKをインストール ・jenv を使ってバージョンの切り替え レッツトライ caskでJDKのインストール ・cask = homebrewの拡張機能 ・/usr/libexec/java_homeでインストールのJDKを確認できる(すごい) 手順ログ # -----------setup------------- # tap:公式以外のリポジトリを追加 $ brew tap caskroom/versions # 引数なしでtapしたもの確認できる $ brew tap # -----------cask-------------- # リストを見て適宜インストール $ brew search java $ brew cask install java # Java8が見当たらない。↓ぐぐった結果コレでOK $ brew cask install adoptopenjdk8 # 確認 $ /usr/libexec/java_home -V jenvで複数バージョン管理 jenv = pyenvのJava版。しらなかった・・・ 手順ログ # -----------setup------------- # jenvインストール $ brew install jenv # .bash_profileにパスを通す export JENV_ROOT="$HOME/.jenv" if [ -d "${JENV_ROOT}" ]; then export PATH="$JENV_ROOT/bin:$PATH" eval "$(jenv init -)" fi # .jenvフォルダを作らないとaddできない $ mkdir ~/.jenv $ mkdir ~/.jenv/versions $ source ~/.bash_profile # -----------jenv------------- # 確認。はじめはsystemのみ $ jenv versions # 追加したいバージョンをadd $ jenv add $(/usr/libexec/java_home -v 1.8) $ jenv add $(/usr/libexec/java_home -v 11) $ jenv add $(/usr/libexec/java_home -v 12) # 操作はpyenvと同じかんじ $ jenv global 1.8 $ jenv local 12 参考 MacでJDKのバージョンを切り替える https://qiita.com/mas0061/items/2fe9333f045800d00b5c /usr/libexec/java_home がいい子すぎる件 https://qiita.com/obr_y/items/5bf16d22bb2d9c0781f5 jEnvのセットアップ&操作方法(Mac) https://qiita.com/uhooi/items/9a6747084bcfd4df07a6 homebrew-caskでJava8をインストールする https://qiita.com/d_forest/items/290bb05bb929e5d74647
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む