RECENT POSTS
General
パワーブロック SP EXP 90LBを買ってしまいました。
今まで体力維持のためにBowflexアジャスタブルダンベルの類似商品を使っていたのですが扱う重量が大きくなってきて問題が生じてきました。
本来使わないウェイトが引っかかって付いてきて気付かずに落下させて怪我をしそうになりました。
床に大きなへこみを作ってしまいました。
また、受け皿に戻すのもスムーズにいかなくなりました。
重いウェイトを早く戻したいのに大変です。
このようなことが起こり始めたのと扱う重量もMaxになってきたのでステップアップ目的で新しいアジャスタブルダンベルを購入する決意をしました。
まず、安全第一で故障のないアジャスタブルダンベルで調べたところパワーブロック SP EXP 90LBがヒットしました。
ただし、非常に高価です。
たかがダンベルに126,300円もお金をつぎ込んでいいものか?
類似商品なら40,000円前後で買えるし評価もそんなに悪くはない。
随分迷ったあげく、類似商品はピンが旧型の金属製のもので斜めにささることがあるのと、その金属製のピンが反対側に飛び出していてウェアに引っかかることがあるらしい。
あと、グリップ部に問題のでる個体もあるようです。
これはどうでも良いことですが手を入れるところが類似商品のほうは旧型と同じでコアと呼ばれるところに丸いフレームが2本あって狭いです。
悩んだ末に出した答えは元祖のパワーブロック SP EXP 90LBを購入することにしました。
そんなこんなで本日パワーブロック SP EXP 90LBが届きました。
STAGE 1 SETの箱が二つ、STAGE 2 SET、STAGE 3 SETの箱がそれぞれ一つ。
本当ならSTAGE 3 SETは必要ないと思うのでその分安く購入できると良かったのですがそういう訳にはいかないので部屋のオブジェとして活躍してもらいます。
ひょっとしたらいつの日かSTAGE 3 SETの高重量を必要とするかもしれないと夢をみることにします。(^_^;)
ちなみにアメリカアマゾンではこれらがバラ売りされていました。
価格も安かったのでアメリカアマゾンで購入しようかと思ったけどこんな重いものの配送トラブルとかあると面倒なので正規代理店で購入しました。
箱から中身を取り出してみました。
マッスルコンテスト・ジャパンという薄い雑誌と保証書、取り扱い説明書が入っていました。
コアとウェイトももちろん入ってます。
このコアのレバーを操作することによって二本の丸いフレームに入っているウェイトを抜き差し可能とします。
このレバーの位置ではストッパーが出ていてウェイトを抜くことはできません。
下の写真のようにレバーを下方向に下げると先ほどのストッパーが引っ込みウェイトを抜き差し可能となります。
このウェイトの重さ一本約一キロなのでこれを抜き差しすることによって微妙な重量変更ができます。
ただ、これを入れて使っていると重心の位置が一般的なダンベルと比べるとかなりずれてしまいます。
特にカール系のトレーニングでは顕著に違和感を感じます。
これは私だけかもしれませんがいままで使っていたものと比べると同じ重量なのに重く感じます。
体重計で計測したところ重量は同じでした。
なのにプレス系、ストレッチ系、カール系のトレーニング全てにおいて重く感じます。
トレーニングがやりにくいとかじゃありません。ただ同じ重量なのに重く感じます。
慣れてないだけなんだろうか?
あと何年生きられるか解らないけど健康と体力維持のためにボチボチと頑張っていこう!
2019年1月26日5:35 PM |
Java NetBeans
お正月休みも今日で終わってしまいます。
何処にも出かけず家でごろごろしていました。(^_^;)
居場所に困るのでこうやってパソコンでプログラムを組んで時間を無駄に使ってます。
今日は PhantomReference を試してみます。
その前に前回、前々回と PhantomReference の兄弟の SoftReference と WeakReference の御浚いをしておきましょう。
SoftReference はリファレントがソフト到達可能となっても使用頻度とヒープの状況から、ガーベージ・コレクタがリファレントを解放すべきだと判断するまで GC 対象にならない。
Weak Reference はリファレントが弱到達可能となったら GC 対象となる。
言葉での説明より簡単なプログラムと組んで実行してみたほうが理解しやすいです。
では、非常に解りやすいプログラムを組んでみました。
プログラムの中にいる”ぼく”はあなたの場合、SoftReference タイプでしょうか?
それとも Weak Reference タイプでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
|
package jp.yucchi.softlovereference; import java.lang.ref.Reference; import java.lang.ref.SoftReference; /** * * @author Yucchi */ public class SoftLoveReference { public static void main(String[] args) { var softLoveReference = new SoftLoveReference(); // suzu をソフト到達可能オブジェクトにする。(boku から suzu の強参照を削除) softLoveReference.boku.suzu = null; System.out.println("--- 僕からすずへの愛を無くした。 ---"); // ソフト到達可能となった suzu はすぐにガーベジ・コレクト対象とならずに // ヒープの空き容量が不足したときに回収対象となる。 System.gc(); // リファレントを取得 var love = softLoveReference.boku.yui.suzu; if (love.get() != null) { // ソフト到達可能オブジェクト suzu はまだ消えていない。 love.get().isLove(); // リファレントをクリアする。 love.clear(); if (love.get() == null) { // ソフト到達可能オブジェクト suzu は消えている。 System.out.println("ガッキー、今までごめんね。僕のすずへの愛は完全に消えた。" + System.lineSeparator() + "これからはガッキーお前だけを愛する!"); } } else { // ソフト到達可能オブジェクトが消えている。 System.out.println("僕のすずへの愛は完全に消えた。"); } } Boku boku; public SoftLoveReference() { boku = new Boku(); boku.yui = new Yui(); boku.suzu = new Suzu(); // yui から suzu はソフト参照 boku.yui.suzu = new SoftReference<>(boku.suzu); } private static class Boku { Boku() { System.out.println("僕は二人の素敵な女性を愛してしまった。"); } Yui yui; Suzu suzu; } private static class Yui { Yui() { System.out.println("僕はガッキーを愛してる。"); } // 参照型フィールド Reference<Suzu> suzu = null; } private static class Suzu { Suzu() { System.out.println("僕はすずを愛してる。"); } void isLove() { System.out.println("ごめんね。ガッキー・・・ 僕のすずへの愛はまだ完全に消えていない。"); } } } |
このプログラムの実行結果はつぎのようになります。
僕は二人の素敵な女性を愛してしまった。
僕はガッキーを愛してる。
僕はすずを愛してる。
— 僕からすずへの愛を無くした。 —
ごめんね。ガッキー・・・ 僕のすずへの愛はまだ完全に消えていない。
ガッキー、今までごめんね。僕のすずへの愛は完全に消えた。
これからはガッキーお前だけを愛する!
次は Weak Reference を使ったプログラムです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
|
package jp.yucchi.weaklovereference; import java.lang.ref.Reference; import java.lang.ref.WeakReference; /** * * @author Yucchi */ public class WeakLoveReference { public static void main(String[] args) { var weakLoveReference = new WeakLoveReference(); // suzu を弱到達可能オブジェクトにする。(boku から suzu の強参照を削除) weakLoveReference.boku.suzu = null; System.out.println("--- 僕からすずへの愛を無くした。 ---"); // 弱到達可能となった suzu はガーベジ・コレクトの回収体制となる。 System.gc(); // リファレントを取得 var love = weakLoveReference.boku.yui.suzu; // 弱到達可能オブジェクト suzu は消えている。 if (love.get() != null) { // true の部分は実行されることはない。 love.get().isLove(); love.clear(); if (love.get() == null) { System.out.println("ガッキー、今までごめんね。僕のすずへの愛は完全に消えた。" + System.lineSeparator() + "これからはガッキーお前だけを愛する!"); } } else { // 弱到達可能オブジェクトが消えている。 System.out.println("僕のすずへの愛は完全に消えた。 ガッキー、愛してる!"); } } Boku boku; public WeakLoveReference() { boku = new Boku(); boku.yui = new Yui(); boku.suzu = new Suzu(); // yui から suzu 弱参照 boku.yui.suzu = new WeakReference<>(boku.suzu); } private static class Boku { Boku() { System.out.println("僕は二人の素敵な女性を愛してしまった。"); } Yui yui; Suzu suzu; } private static class Yui { Yui() { System.out.println("僕はガッキーを愛してる。"); } // 参照型フィールド Reference<Suzu> suzu = null; } private static class Suzu { Suzu() { System.out.println("僕はすずを愛してる。"); } void isLove() { System.out.println("ごめんね。ガッキー・・・ 僕のすずへの愛はまだ完全に消えていない。"); } } } |
このプログラムの実行結果はつぎのようになります。
僕は二人の素敵な女性を愛してしまった。
僕はガッキーを愛してる。
僕はすずを愛してる。
— 僕からすずへの愛を無くした。 —
僕のすずへの愛は完全に消えた。 ガッキー、愛してる!
プログラムの詳細はコメントと前回、前々回のエントリーを参照してくださいませ。
さて、あなたはどちらのタイプでしたでしょう?
それでは PhantomReference を試してみましょう!
PhantomReference を使うには モジュール java.base の java.lang.Object java.lang.ref.ReferenceQueue<T> クラスが必要となります。
そういえば、モジュール java.base の java.lang.Object java.lang.ref.Reference<T> のことも忘れていたのでこれらを先にざっくりと調べます。
参照オブジェクトための抽象基底クラスである Reference<T> クラスから調べます。
クラスReference<T>
このクラスは、すべての参照オブジェクトに対して共通のオペレーションを定義します。
参照オブジェクトはガベージ・コレクタと密接に連携して実装されるので、このクラスを直接サブクラス化することはできません。
メソッドは6個あります。
public void clear()
この参照オブジェクトをクリアします。 このメソッドを呼び出しても、このオブジェクトはキューに入りません。
このメソッドはJavaコードによってのみ呼び出されます。ガベージ・コレクタが参照をクリアするときは、このメソッドを呼び出さずに直接行います。
protected Object clone()throws CloneNotSupportedException
常時 CloneNotSupportedExceptionをスローします。 Referenceは、効果的にクローニングできません。 かわりに新規のReferenceを構築します。
public boolean enqueue()
この参照オブジェクトをクリアし、それが登録されているキューに登録オブジェクトを追加します。
このメソッドはJavaコードによってのみ呼び出されます。ガベージ・コレクタが参照をキューに入れるときは、このメソッドを呼び出さずに直接行います。
戻り値:この参照オブジェクトがキューに入れられた場合はtrue。すでにキューに入れられているか、作成時にキューに登録されなかった場合はfalse
public T get()
参照オブジェクトのリファレントを返します。 プログラムまたはガベージ・コレクタによって、この参照オブジェクトがすでにクリアされている場合、このメソッドはnullを返します。
戻り値:この参照が表すオブジェクト。この参照オブジェクトがクリアされている場合はnull
public boolean isEnqueued()
この参照オブジェクトが、プログラムまたはガベージ・コレクタによってキューに入れられているかどうかを判定します。
この参照オブジェクトが生成されたときにキューに登録されていない場合、このメソッドは常にfalseを返します。
戻り値:この参照オブジェクトがキューに入れられている場合にだけtrue
public static void reachabilityFence(Object ref)
指定された参照によって参照されるオブジェクトが、オブジェクトが到達不能になる可能性のあるプログラムの以前のアクションに関係なく、「強く到達可能な」のままであることを保証します。
このため、参照されたオブジェクトは、少なくともこのメソッドが呼び出されるまでガベージ・コレクションによって再要求できません。
次はモジュール java.base の java.lang.Object java.lang.ref.ReferenceQueue<T> を調べてみます。
クラスReferenceQueue<T>
参照キューです。到達可能性が適切に変更されたことが検出されると、登録されている参照オブジェクトはガベージ・コレクタによって参照キューに追加されます。
コンストラクタは引数の無いものが一つだけあります。
public ReferenceQueue()
新しい参照オブジェクト・キューを構築します。
メソッドは3個あります。
public Reference<? extends T> poll()
このキューをポーリングして、参照オブジェクトが利用可能かどうかを確認します。
参照オブジェクトが遅延なしで利用可能な場合、それがキューから削除されて、返されます。 それ以外の場合、このメソッドは即座にnullを返します。
戻り値:利用可能な参照オブジェクトがあった場合はその参照オブジェクト、そうでない場合はnull
public Reference<? extends T> remove()throws InterruptedException
このキューの次の参照オブジェクトを削除します。参照オブジェクトが利用可能になるまでブロックされます。
戻り値:参照オブジェクト。1つが利用可能になるまでブロックを行う
例外:InterruptedException – 待機中に割り込まれた場合
public Reference<? extends T> remove(long timeout)throws IllegalArgumentException, InterruptedException
このキューの次の参照オブジェクトを削除します。参照オブジェクトが利用可能になるか、指定されたタイム・アウトの期限が切れるまでブロックします。
このメソッドはリアルタイム保証を行いません。Object.wait(long)メソッドを呼び出した場合と同様にタイム・アウトのスケジュールを作成します。
パラメータ:timeout – 値が正の場合、このキューに参照が追加されるのを待つ間、timeoutミリ秒の間ブロックされる。 0の場合、無期限にブロックされる。
戻り値:指定されたタイム・アウト期間に利用可能になった場合は参照オブジェクト、そうでない場合はnull
例外:IllegalArgumentException – timeout引数の値が負の場合
InterruptedException – タイム・アウト待機中に割り込まれた場合
それでは次はモジュール java.base の java.lang.Object java.lang.ref.Reference<T> java.lang.ref.PhantomReference<T> クラスを調べます。
クラスPhantomReference<T>
ファントム参照オブジェクトです。ファントム参照オブジェクトがキューに入れられるのは、キューに入れておかないとそれらのリファレントが再生される可能性があるとコレクタが判断したときです。
幻の参照は、死後のクリーンアップ・アクションをスケジュールするために最も頻繁に使用されます。
ある時点でオブジェクトが「ファントム到達可能」であるとガベージ・コレクタが判断したとします。
その時点で、そのオブジェクトへのすべてのファントム参照と、そのオブジェクトに到達可能な他のファントム到達可能オブジェクトへのすべてのファントム参照を原子的にクリアします。
同時に、または後で、参照キューに登録されたそれらの新たにクリアされたファントム参照をエンキューします。
再生可能なオブジェクトをそのままにしておくために、ファントム参照のリファレントを取り出すことはできません。ファントム参照のgetメソッドは、常にnullを返します。
コンストラクタは引数を二つ取るものが一つだけあります。
public PhantomReference(T referent, ReferenceQueue<? super T> q)
指定されたオブジェクトを参照し、指定されたキューに登録されている新しいファントム参照を作成します。
nullキューでファントム・リファレンスを作成することは可能ですが、そのようなリファレンスはまったく役に立たない: そのgetメソッドは常にnullを返し、キューを持たないのでエンキューされません。
パラメータ:referent – 新しいファントム参照が参照するオブジェクト
q – 参照が登録されるキュー。登録が必要ない場合はnull
メソッドは一つだけあります。
public T get()
参照オブジェクトのリファレントを返します。
ファントム参照のリファレントは常にアクセス不可能なため、このメソッドは常にnullを返します。
戻り値:null
これで PhantomReference を使うための API をみてみました。
と、思いきや JDK 9 から java.lang.Object protected void finalize() throws Throwable が Deprecated になってしまいました。
代わりにモジュール java.base の java.lang.Object java.lang.ref.Cleaner クラスを使うことが推奨されているのでこちらもざっくりとみてみます。
クラスCleaner
Cleanerは、一連のオブジェクト参照と対応するクリーニング・アクションを管理します。
クリーナは、オブジェクトが有線で到達可能になったことがクリーナに通知された後で実行するために、クリーニング動作はregisteredです。
クリーナは、PhantomReferenceとReferenceQueueを使用して、reachabilityが変更されたときに通知を受け取ります。
各クリーナは、独立して動作し、保留中のクリーニング動作を管理し、クリーナがすでに使用されていないときにスレッドおよび終了を処理します。
オブジェクト参照と対応するクリーニング・アクションを登録すると、Cleanableが返されます。 最も効率的な使用法は、オブジェクトが閉じられたり不要になったときに、cleanメソッドを明示的に呼び出すことです。
クリーニング・アクションは、すでに明示的にクリーニングされていない限り、オブジェクトがファントム到達可能になったときに呼び出されるRunnableです。
クリーニング動作は、登録されているオブジェクトを参照してはならないことに注意してください。 そうであれば、オブジェクトは想像を絶するものにならず、クリーニング動作は自動的に呼び出されません。
クリーニング・アクションの実行は、クリーナに関連するスレッドによって実行されます。
クリーニング・アクションによってスローされたすべての例外は無視されます。
クリーナおよびその他のクリーニング・アクションは、クリーニング・アクションの例外の影響を受けません。
スレッドは、登録されているすべてのクリーニング処理が完了し、クリーナ自体がガベージ・コレクタによって再利用されるまで実行されます。
System.exit中のクリーナの動作は実装固有です。 クリーニング動作の呼び出しの有無は保証されていません。
ほかで指定がない場合、null引数をコンストラクタまたはこのクラスのメソッドへ渡すと、NullPointerExceptionがスローされます。
ネストされたクラスが一つあります。
public static interface Cleaner.Cleanable
Cleanableは、Cleanerに登録されているオブジェクトとクリーニング・アクションを表します。
ネストされたクラスにメソッドが一つだけあります。
void clean()
クリーニング可能なものの登録を解除し、クリーニング動作を呼び出します。 クリーニング可能なクリーニング動作は、cleanの呼び出し回数に関係なく、最大で1回呼び出されます。
Cleaner クラスにはメソッドが3個あります。
public static Cleaner create()
新しいCleanerを返します。
クリーナは、ファントム到達可能オブジェクトを処理し、クリーニング・アクションを呼び出すdaemon threadを作成します。
スレッドの「コンテキスト・クラス・ローダー」はsystem class loaderに設定されます。
スレッドはパーミッションを持たず、SecurityManager is setの場合にのみ実行されます。
ファントムに到達し、登録されたすべてのクリーニング動作が完了すると、クリーナは終了します。
戻り値:新しいCleaner
例外:SecurityException – 現在のスレッドがスレッドの作成または開始を許可されていない場合。
public static Cleaner create(ThreadFactory threadFactory)
ThreadFactoryからThreadを使用して新しいCleanerを返します。
スレッド・ファクトリのnewThreadメソッドからのスレッドはdaemon threadに設定され、ファントム到達可能オブジェクトを処理し、クリーン・アクションを起動します。
各呼び出しで、thread factoryは、クリーニング・アクションを実行するのに適したスレッドを提供する必要があります。
ファントムに到達し、登録されたすべてのクリーニング動作が完了すると、クリーナは終了します。
パラメータ:threadFactory – 新しいThreadを返してクリーニング・アクションを処理するThreadFactory
戻り値:新しいCleaner例外:IllegalThreadStateException – スレッド・ファクトリからのスレッドがnot a new threadの場合。
SecurityException – 現在のスレッドがスレッドの作成または開始を許可されていない場合。
public Cleaner.Cleanable register(Object obj, Runnable action)
オブジェクトにファントム到達可能になったときに実行するオブジェクトとクリーニング・アクションを登録します。
クリーニング動作の動作については、上記の「APIノート」を参照してください。
パラメータ:obj – 監視するオブジェクト
action – オブジェクトがファントム到達可能になったときに呼び出すRunnable
戻り値:Cleanableインスタンス
これで今度こそ PhantomReference を使う為の必要最小限の情報をざっくりと API ドキュメントから学んだ。
簡単に PhantomReference ってけっきょくどうなの?ってのを考えてみると
ファントム到達可能オブジェクトを ReferenceQueue に入れる。
注意:) ファントム到達可能オブジェクトの ReferenceQueue への挿入はガーベジ・コレクトと非同期で行われます。よって GC が働いてすぐに挿入されると考えてはいけないようです。
Reference<T> クラスの三つあるメソッド poll()、remove()、remove(long timeout) の何れかによって ReferenceQueue に入れられたオブジェクトを取り出してクリーンアップする。
これとは別に finalize() か Cleaner クラスを使ってクリーンアップする方法もあります。
クリーンアップの際はリソースの解放処理が必要であれば必ず実行するようにしておくのが無難かもしれない。
正しく理解できているかは怪しいけどプログラムを組んでみることにする。
とりあえず、前回、前々回に組んだ SoftReference と WeakReference の PhantomReference バージョンとしてみよう。
まず、JDK 1.8 対応のものから組んで、最新の JDK 11 で使える Cleaner クラスを使ったものに変更してみる。
そんなこんなで次のようなプログラムを組んでみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
|
package jp.yucchi.phantomexterminationclassic; import java.lang.management.GarbageCollectorMXBean; import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; import java.lang.management.MemoryUsage; import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.ArrayList; import java.util.List; /** * * @author Yucchi */ public class PhantomExterminationClassic { private static final ReferenceQueue<MemoryConsumer> REFEREN_CEQUEU = new ReferenceQueue<>(); public static void main(String[] args) { System.out.println(System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch")); System.out.println("JDK " + System.getProperty("java.version") + System.lineSeparator()); List<PhantomReference<MemoryConsumer>> list = new ArrayList<>(); for (int i = 0; i < 1_000; i++) { MemoryConsumer memoryConsumer = new MemoryConsumer(); PhantomReference<MemoryConsumer> phantomReference = new PhantomReference<>(memoryConsumer, REFEREN_CEQUEU); list.add(phantomReference); checkStatus(); } } private static void checkStatus() { MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean(); MemoryUsage usage = memoryBean.getHeapMemoryUsage(); long usedMemory = usage.getUsed(); List<GarbageCollectorMXBean> gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); StringBuilder gcStatus = new StringBuilder(); gcMXBeans.forEach(e -> { gcStatus .append(e.getName()) .append(" ") .append("GC Count: ") .append(e.getCollectionCount()) .append(" ") .append("GCTime = ") .append(e.getCollectionTime()) .append(" "); }); System.out.print("UsedMemory: " + usedMemory + " "); System.out.println(gcStatus); // Reference<?> ref; // while ((ref = REFEREN_CEQUEU.poll()) != null) { // System.out.println(" --- POLL:" + ref + " ---"); // if (Double.parseDouble(System.getProperty("java.specification.version")) < 1.9) { // ref.clear(); // } // } } private static class MemoryConsumer { private byte[] buff; MemoryConsumer() { this.buff = new byte[100 * 1024 * 1024]; } @Override protected void finalize() { System.out.println(" --- finalize ---"); this.buff = null; } } } |
このプログラムは finalize() メソッドをオーバーライドして利用しています。
finalize() メソッド内で this.buff = null; として OutOfMemoryError を回避しています。(このようなことは JDK 11 では必要ではありませんでした。)
Reference<T> クラスの poll() メソッドはコメントアウトして実行できないようにしてあります。
ちなみに finalize() メソッドも使わなければ OutOfMemoryError でプログラムは途中で止まってしまいます。
プログラムの実行結果は次のようになります。
Windows 10 10.0 amd64
JDK 1.8.0_144
UsedMemory: 115595064 PS Scavenge GC Count: 0 GCTime = 0 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 241927592 PS Scavenge GC Count: 0 GCTime = 0 PS MarkSweep GC Count: 0 GCTime = 0
— finalize —
— finalize —
UsedMemory: 321118712 PS Scavenge GC Count: 1 GCTime = 128 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 430987664 PS Scavenge GC Count: 1 GCTime = 128 PS MarkSweep GC Count: 0 GCTime = 0
— finalize —
— finalize —
UsedMemory: 525473600 PS Scavenge GC Count: 2 GCTime = 257 PS MarkSweep GC Count: 0 GCTime = 0
略
UsedMemory: 6299540808 PS Scavenge GC Count: 26 GCTime = 30760 PS MarkSweep GC Count: 24 GCTime = 6659
UsedMemory: 6404398424 PS Scavenge GC Count: 26 GCTime = 30760 PS MarkSweep GC Count: 24 GCTime = 6659
UsedMemory: 6509256040 PS Scavenge GC Count: 26 GCTime = 30760 PS MarkSweep GC Count: 24 GCTime = 6659
UsedMemory: 6614113656 PS Scavenge GC Count: 26 GCTime = 30760 PS MarkSweep GC Count: 24 GCTime = 6659
57秒ほどで終了しました。
ちょっと遅いですね。
NetBeans のプロファイラで確認してみます。
JDK 1.8 の標準のガーベジ・コレクタってこんなものなのか?
それでは先ほどのプログラムを Reference<T> クラスの poll() メソッドを使うように変更した実行結果は次のようになりました。
Windows 10 10.0 amd64
JDK 1.8.0_144
UsedMemory: 115595064 PS Scavenge GC Count: 0 GCTime = 0 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 241927592 PS Scavenge GC Count: 0 GCTime = 0 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 315733616 PS Scavenge GC Count: 1 GCTime = 129 PS MarkSweep GC Count: 0 GCTime = 0
— POLL:java.lang.ref.PhantomReference@548c4f57 —
— POLL:java.lang.ref.PhantomReference@1218025c —
UsedMemory: 425602568 PS Scavenge GC Count: 1 GCTime = 129 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 525440640 PS Scavenge GC Count: 2 GCTime = 260 PS MarkSweep GC Count: 0 GCTime = 0
— POLL:java.lang.ref.PhantomReference@816f27d —
— POLL:java.lang.ref.PhantomReference@87aac27 —
UsedMemory: 640519896 PS Scavenge GC Count: 2 GCTime = 260 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 745377512 PS Scavenge GC Count: 2 GCTime = 260 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 850235128 PS Scavenge GC Count: 2 GCTime = 260 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 955092744 PS Scavenge GC Count: 2 GCTime = 260 PS MarkSweep GC Count: 0 GCTime = 0
UsedMemory: 629860128 PS Scavenge GC Count: 3 GCTime = 569 PS MarkSweep GC Count: 1 GCTime = 43
— POLL:java.lang.ref.PhantomReference@3e3abc88 —
略
— POLL:java.lang.ref.PhantomReference@cb644e —
UsedMemory: 5880074344 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 5984931960 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 6089789576 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 6194647192 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 6299504808 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 6404362424 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 6509220040 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 6614077656 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 6718935272 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
UsedMemory: 6823792888 PS Scavenge GC Count: 26 GCTime = 30258 PS MarkSweep GC Count: 24 GCTime = 6566
56秒で終了しました。
finalize() メソッドを使ったのとほぼ同じですね。
ちなみに64行目の ref.clear() メソッドを実行して参照オブジェクトをクリアしないと OutOfMemoryError でプログラムは途中で止まってしまいます。
JDK 11 では clear() メソッドで参照オブジェクトをクリアしなくてもOutOfMemoryError でプログラムで止まることはありません。
いちおう NetBeans のプロファイラで確認します。
大きな違いはなさそうです。
それでは JDK 11 に切り替えて finalize() メソッド を利用したプログラムを実行してみます。
JDK 1.8 との比較のためにあえて finalize() メソッド を使ってます。あとで Cleaner クラスを使ったものも試してみます。
プログラムの実行結果は次のようになりました。
Windows 10 10.0 amd64
JDK 11.0.1
UsedMemory: 113246208 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 222298112 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 331350016 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 440401920 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
— finalize —
— finalize —
— finalize —
— finalize —
UsedMemory: 546494784 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 655546688 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
— finalize —
— finalize —
UsedMemory: 328435288 G1 Young Generation GC Count: 2 GCTime = 7 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 437487192 G1 Young Generation GC Count: 2 GCTime = 7 G1 Old Generation GC Count: 0 GCTime = 0
略
— finalize —
— finalize —
— finalize —
UsedMemory: 2073652944 G1 Young Generation GC Count: 106 GCTime = 244 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 2182704848 G1 Young Generation GC Count: 106 GCTime = 244 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 2291756752 G1 Young Generation GC Count: 106 GCTime = 244 G1 Old Generation GC Count: 0 GCTime = 0
15秒で終了しました。
JDK 11 のガーベジ・コレクタ優秀ですね。
確か JDK 9 から G1GC が標準になったはず。
これも NetBeans のプロファイラで覗いてみます。
この違いを目の当たりにすると JDK 1.8 はもう使えない。(^_^;)
では、Reference<T> クラスの poll() メソッドを使うように変更して実行してみます。
プログラムの実行結果は次のようになります。
Windows 10 10.0 amd64
JDK 11.0.1
UsedMemory: 113246208 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 222298112 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 331350016 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 440401920 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 110281288 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
— POLL:java.lang.ref.PhantomReference@d8355a8 —
— POLL:java.lang.ref.PhantomReference@5e5792a0 —
— POLL:java.lang.ref.PhantomReference@26653222 —
— POLL:java.lang.ref.PhantomReference@3532ec19 —
UsedMemory: 219333192 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 328385096 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 437437000 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 110345216 G1 Young Generation GC Count: 2 GCTime = 7 G1 Old Generation GC Count: 0 GCTime = 0
— POLL:java.lang.ref.PhantomReference@68c4039c —
略
— POLL:java.lang.ref.PhantomReference@5db250b4 —
— POLL:java.lang.ref.PhantomReference@223f3642 —
UsedMemory: 219733600 G1 Young Generation GC Count: 99 GCTime = 208 G1 Old Generation GC Count: 0 GCTime = 0
これも15秒で終了しました。
NetBeans のプロファイラで覗いてみます。
こちらのほうが僅かだけど良いかもしれない。
JDK 1.8 と JDK 11 の違いはガーベジ・コレクタが変更されたくらいだと思うのですけど他にも何か変更があるのかもしれません。
ちょっと気になるので JDK 1.8 と JDK 11 のスレッドを確認してみます。
両方とも Reference<T> クラスの poll() メソッドを使用した場合です。
まず、JDK 1.8 から
次は JDK 11です。
JDK 11 のほうに Common-Cleaner という見慣れないスレッドがありますね。
これがパフォーマンスアップの要因の一つなのかな。
GC 関連のことはあまり気にしたことないのでよく解りません。(>_<。)
名前からして勝手にクリーンアップしてくれそうなので finalize() メソッド、Reference<T> クラスの poll() メソッド両方ともコメントアウトして無効にして実行してみました。
OutOfMemoryError でプログラムが止まりまし・・・ あれっ! 止まらない。
プログラムは15秒で終了しました。
Windows 10 10.0 amd64
JDK 11.0.1
UsedMemory: 113246208 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 222298112 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 331350016 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 440401920 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 110281920 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 219333824 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 328385728 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 437437632 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 110325528 G1 Young Generation GC Count: 2 GCTime = 6 G1 Old Generation GC Count: 0 GCTime = 0
略
UsedMemory: 1092084152 G1 Young Generation GC Count: 98 GCTime = 200 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1201136056 G1 Young Generation GC Count: 98 GCTime = 200 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 110617168 G1 Young Generation GC Count: 99 GCTime = 202 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 219669072 G1 Young Generation GC Count: 99 GCTime = 202 G1 Old Generation GC Count: 0 GCTime = 0
BUILD SUCCESSFUL (total time: 15 seconds)
いったいどういうことなんだ?
クリーンアップの際にリソースの解放処理が必要で無いのなら何もしなくてもいいのか?
JDK 1.8 で Reference<T> クラスの poll() メソッドを使う場合は clear() メソッドで参照オブジェクトをクリアして次の GC で解放されるという最低でも GC が二回必要だったのに・・・
ガーベジ・コレクタ凄く賢くなっている。(私が PhantomReference の使い方を間違ってるかもしれない)
このあたりのことを調べ出すとガーベジ・コレクタ沼にはまって苦しむことになるので誰か親切な人が詳しく解りやすい解説記事をネット上にアップしてくれるのを待つことにする。
それでは最後に Cleaner クラス を使ってみます。
この Cleaner クラスもファイナライザ同様にあまり使うのを好ましく思われていません。
詳細は「Effective Java 第3版」の第2章 オブジェクトの生成と消滅 項目 8 ファイナライザとクリーナーを避ける をご覧ください。
それでは Cleaner クラスを使ってみます。
これまでのプログラムを JDK 10 以降の新しいものを使うことを前提にして組み直しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
|
package jp.yucchi.phantomextermination; import java.lang.management.ManagementFactory; import java.lang.ref.Cleaner; import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.util.ArrayList; import java.util.List; /** * * @author Yucchi */ public class PhantomExtermination { private static final ReferenceQueue<MemoryConsumer> REFEREN_CEQUEU = new ReferenceQueue<>(); public static void main(String[] args) { System.out.println(System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch")); System.out.println("JDK " + System.getProperty("java.version") + System.lineSeparator()); List<PhantomReference<MemoryConsumer>> list = new ArrayList<>(); for (int i = 0; i < 1_000; i++) { var memoryConsumer = new MemoryConsumer(); var cleaner = Cleaner.create(); cleaner.register(memoryConsumer, new CleanerRunnable(memoryConsumer.buff, i)); var phantomReference = new PhantomReference<>(memoryConsumer, REFEREN_CEQUEU); list.add(phantomReference); checkStatus(); } } private static void checkStatus() { var memoryBean = ManagementFactory.getMemoryMXBean(); var usage = memoryBean.getHeapMemoryUsage(); var usedMemory = usage.getUsed(); var gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); var gcStatus = new StringBuilder(); gcMXBeans.forEach(e -> { gcStatus .append(e.getName()) .append(" ") .append("GC Count: ") .append(e.getCollectionCount()) .append(" ") .append("GCTime = ") .append(e.getCollectionTime()) .append(" "); }); System.out.print("UsedMemory: " + usedMemory + " "); System.out.println(gcStatus); // Reference<?> ref; // while ((ref = REFEREN_CEQUEU.poll()) != null) { // System.out.println(" --- POLL:" + ref + " ---"); // } } private static class CleanerRunnable implements Runnable { private int ObjectNumber; private byte[] buff; CleanerRunnable(int ObjectNumber) { } private CleanerRunnable(byte[] buff, int ObjectNumber) { this.ObjectNumber = ObjectNumber; this.buff = buff; } @Override public void run() { System.out.println("Cleaning: " + ObjectNumber++); buff = null; } } private static class MemoryConsumer { private final byte[] buff; MemoryConsumer() { this.buff = new byte[100 * 1024 * 1024]; } } <font face="メイリオ"></font> } |
30行目で Cleaner を生成、31行目でファントム到達可能になったときに実行するオブジェクトとクリーニング・アクションを登録します。
var cleaner = Cleaner.create();
cleaner.register(memoryConsumer, new CleanerRunnable(memoryConsumer.buff, i));
クリーニング・アクションの登録でnew CleanerRunnable(memoryConsumer.buff, i) と無駄なことをしています。
これはお遊びですので引数無しでクリーニング・アクションの run() メソッド内のゴニョゴニョしたコードは無くて空でも問題無いです。
84行目の buff = null; もあっても無くてもほぼ変わりはありません。(少しは変わるかもと思って試しただけです)
たったこれだけです!非常に便利です。
これでファイナライザ攻撃の心配はなくなります。
プログラムの実行結果は次のようになります。
Windows 10 10.0 amd64
JDK 11.0.1
UsedMemory: 113246208 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 222298112 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 331350016 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 440401920 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 554883024 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 663934928 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 772986832 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 882038736 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 991090640 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1096034664 G1 Young Generation GC Count: 2 GCTime = 5 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1205086568 G1 Young Generation GC Count: 2 GCTime = 5 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1314138472 G1 Young Generation GC Count: 2 GCTime = 5 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1419045944 G1 Young Generation GC Count: 3 GCTime = 8 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1532292152 G1 Young Generation GC Count: 3 GCTime = 8 G1 Old Generation GC Count: 0 GCTime = 0
Cleaning: 6
Cleaning: 1
Cleaning: 5
Cleaning: 3
Cleaning: 7
Cleaning: 0
Cleaning: 2
Cleaning: 4
Cleaning: 9
UsedMemory: 1645538360 G1 Young Generation GC Count: 3 GCTime = 8 G1 Old Generation GC Count: 0 GCTime = 0
Cleaning: 11
Cleaning: 10
Cleaning: 8
UsedMemory: 1754590264 G1 Young Generation GC Count: 3 GCTime = 8 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1863642168 G1 Young Generation GC Count: 3 GCTime = 8 G1 Old Generation GC Count: 0 GCTime = 0
Cleaning: 16
Cleaning: 15
Cleaning: 13
Cleaning: 14
Cleaning: 12
UsedMemory: 655688552 G1 Young Generation GC Count: 4 GCTime = 12 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 764740456 G1 Young Generation GC Count: 4 GCTime = 12 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 873792360 G1 Young Generation GC Count: 4 GCTime = 12 G1 Old Generation GC Count: 0 GCTime = 0
Cleaning: 19
Cleaning: 17
Cleaning: 18
UsedMemory: 437556936 G1 Young Generation GC Count: 5 GCTime = 15 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 546608840 G1 Young Generation GC Count: 5 GCTime = 15 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 655660744 G1 Young Generation GC Count: 5 GCTime = 15 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 764712648 G1 Young Generation GC Count: 5 GCTime = 15 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 873764552 G1 Young Generation GC Count: 5 GCTime = 15 G1 Old Generation GC Count: 0 GCTime = 0
Cleaning: 20
Cleaning: 21
Cleaning: 24
略
UsedMemory: 1314754576 G1 Young Generation GC Count: 170 GCTime = 285 G1 Old Generation GC Count: 0 GCTime = 0
Cleaning: 993
Cleaning: 992
Cleaning: 997
Cleaning: 995
Cleaning: 996
Cleaning: 991
Cleaning: 994
UsedMemory: 882748320 G1 Young Generation GC Count: 171 GCTime = 287 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 991800224 G1 Young Generation GC Count: 171 GCTime = 287 G1 Old Generation GC Count: 0 GCTime = 0
16秒で終了しました。
Cleaner クラス使うと便利ですね。
ファントム到達可能になったときに実行するオブジェクトとクリーニング・アクションを登録するだけだし、それにクリーニング・アクション用の専用スレッドが起動される。
ファイナライザを使って何かしようとすると自前でスレッドを用意したりしなくちゃいけない場合がある。
ファイナライザ攻撃によるセキュリティ上の問題も無い。
もう、ファイナライザを使う必要は無いような気がする。
どのみち近い将来使えなくなるしね。
パッケージ java.lang.ref を良く解らないままいろいろやってみたけど PhantomReference って扱いが難しいですね。
そもそもファントム到達オブジェクトって強参照、ソフト参照、弱参照のどれから辿っても到達できなくなってファイナライザ呼び出しが完了した状態ってところで使いどころが限定されそうな気がする。
ReferenceQueue に入れられた PhantomReference の get() メソッドは null を常時返すようになっている。
つまり、これって GC 対象ってことですよね。
だめだ、頭痛が痛くなりそうだ。(まさに今こんな感じです。)
PhantomReference ってただのクリーンアップ用にしか思えなくなってきた。(^_^;)
しかも、JDK 1.8 の頃のほうが不思議な動作をせず理解しやすいけど JDK 11 との違いは大きすぎる。
パフォーマンスアップや使い勝手が良くなるのはいいけど解りやすい情報が乏しいのはなんとかしてほしい。
今日でお正月休みも終わってしまうけどもっと Java を楽しめる一年になるようにがんばるぞい!
TAGS: Java,NetBeans |
2019年1月3日6:21 AM |
Java NetBeans
このエントリーは、SoftReference エントリーの続きです。
http://yucchi.jp/blog/?p=2675
SoftReference で組んだプログラムの実行結果が想像していたのと違っていたので今度は WeakReference で遊んでみます。
まず、WeakReference ってどんなものなのか調べてみます。
APIドキュメントには次のように記されてます。
弱参照オブジェクトです。弱参照オブジェクトは、その弱参照オブジェクトのリファレントがファイナライズ可能になり、ファイナライズされ、そして再生されることを阻止することはありません。
弱参照は、ほとんどの場合で正規化マッピングを実装するために使用されます。
ある時点で、オブジェクトが弱到達可能であると、ガベージ・コレクタが判断したとします。
その時点で、ガベージ・コレクタは、そのオブジェクトへの弱参照すべてと、強参照およびソフト参照のチェーンを経由してそのオブジェクトに到達できるような、ほかの弱到達可能なオブジェクトへの弱参照すべてを、原子的にクリアします。
同時に、ガベージ・コレクタは以前に弱到達可能なオブジェクトがすべてファイナライズ可能であることを宣言します。
同時にまたはあとで、ガベージ・コレクタは、参照キューに登録されているそれらの新しくクリアされた弱参照をキューに入れます。
WeakReference では弱到達可能になったら問答無用で何も考えずに GC が働くときに無条件で参照を解放されるってことかな。
SoftReference のように参照の使用頻度やヒープの状況を考慮して参照を解放するのとは大違いですね。
ちなみにこの WeakReference はこの特性をいかして WeakHashMap に使われていますね。
java.util.WeakHashMap: キーが弱可到達になったらエントリごと削除する Map
WeakReference クラスにはコンストラクタが二つあります。
WeakReference(T referent)
指定されたオブジェクトを参照する、新しい弱参照を作成します。新しい参照は、どのキューにも登録されません。
WeakReference(T referent, ReferenceQueue<? super T> q)
指定されたオブジェクトを参照し、指定されたキューに登録されている新しい弱参照を作成します。
パラメータ:referent – 新しい弱参照が参照するオブジェクトq – 参照が登録されるキュー。登録が必要ない場合はnull
メソッドは無いですね。
それではプログラムを組んで試してみます。
前回組んだプログラムと同じように無駄にメモリーを消費するインスタンスを1000個作っているだけです。
今回のプログラムには無駄にメモリーを消費するオブジェクトに finalize() メソッドを実装しました。
特にそれによって何かするわけでなく呼ばれたことを確認するためだけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
|
package jp.yucchi.weakreference_g1gc; import java.lang.management.ManagementFactory; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; /** * * @author Yucchi */ public class WeakReference_G1GC { private static final ReferenceQueue<MemoryConsumer> REFEREN_CEQUEU = new ReferenceQueue<>(); public static void main(String[] args) { System.out.println(System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch")); System.out.println("JDK " + System.getProperty("java.version") + System.lineSeparator()); List<WeakReference<MemoryConsumer>> list = new ArrayList<>(); for (int i = 0; i < 1_000; i++) { var memoryConsumer = new MemoryConsumer(); var weakReference = new WeakReference<>(memoryConsumer, REFEREN_CEQUEU); list.add(weakReference); checkStatus(); } } private static void checkStatus() { var memoryBean = ManagementFactory.getMemoryMXBean(); var usage = memoryBean.getHeapMemoryUsage(); var usedMemory = usage.getUsed(); var gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); var gcStatus = new StringBuilder(); gcMXBeans.forEach(e -> { gcStatus .append(e.getName()) .append(" ") .append("GC Count: ") .append(e.getCollectionCount()) .append(" ") .append("GCTime = ") .append(e.getCollectionTime()) .append(" "); }); System.out.print("UsedMemory: " + usedMemory + " "); System.out.println(gcStatus); Reference<?> ref; while ((ref = REFEREN_CEQUEU.poll()) != null) { System.out.println(" --- POLL:" + ref + " ---"); } } private static class MemoryConsumer { private final byte[] buff; MemoryConsumer() { this.buff = new byte[100 * 1024 * 1024]; } @Override protected void finalize() { System.out.println(" --- finalize() ---"); } } } |
プログラムの実行結果は次のようになりました。
Windows 10 10.0 amd64
JDK 11.0.1
UsedMemory: 113246208 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 222298112 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 331350016 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 440401920 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
— finalize() —
— finalize() —
— finalize() —
— finalize() —
UsedMemory: 546492432 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
— POLL:java.lang.ref.WeakReference@d8355a8 —
— POLL:java.lang.ref.WeakReference@59fa1d9b —
— POLL:java.lang.ref.WeakReference@28d25987 —
— POLL:java.lang.ref.WeakReference@4501b7af —
— finalize() —
UsedMemory: 219385672 G1 Young Generation GC Count: 2 GCTime = 6 G1 Old Generation GC Count: 0 GCTime = 0
— POLL:java.lang.ref.WeakReference@523884b2 —
UsedMemory: 332631880 G1 Young Generation GC Count: 2 GCTime = 6 G1 Old Generation GC Count: 0 GCTime = 0
略
UsedMemory: 1201289840 G1 Young Generation GC Count: 115 GCTime = 252 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1310341744 G1 Young Generation GC Count: 115 GCTime = 252 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 1419393648 G1 Young Generation GC Count: 115 GCTime = 252 G1 Old Generation GC Count: 0 GCTime = 0
SoftReference とは大違いです。
プログラムの処理時間も15秒と非常に短い時間です。
GC 累積時間もぜんぜん違います。
NetBeans のプロファイラで確認してみます。
SoftReference と比べると使用しているヒープメモリーサイズも全然少ないし、GC タイムも雲泥の差がある。
これを見るとなるだけ SoftReference は使いたくないと思ってしまう。
でも、SoftReference だったらソフト到達可能なオブジェクトへのすべてのソフト参照は、仮想マシンが OutOfMemoryError をスローする前にクリアされていることが保証されている。
SoftReference はメモリの容量に応じてスケールするキャッシュ用って感じかな。
WeakReference は弱参照オブジェクトが弱到達可能になったらガベージ・コレクタでクリアしたい時に使う(キャッシュしない)。
API ドキュメントの文章が難しく書かれているのであまり自信はないけどこのような違いがあるようだ。
プログラムの実行結果からもその様子が少しは解ります。
ちなみに SoftReference と WeakReference の違いはプログラムの出力結果のガベージ・コレクタの動作ではっきりと解ります。
WeakReference の弱到達可能なオブジェクトは Young Generation 領域に対するマイナー GC ですぐ回収されています。
それに対して SoftReference のソフト到達可能オブジェクトは G1 Old Generation 領域に移されヒープが不足して OutOfMemoryError をスローしないところまで回収されないようです。
SoftReference: UsedMemory: 6871416088 G1 Young Generation GC Count: 222 GCTime = 332 G1 Old Generation GC Count: 12 GCTime = 8964
WeakReference: UsedMemory: 1419393648 G1 Young Generation GC Count: 115 GCTime = 252 G1 Old Generation GC Count: 0 GCTime = 0
なかなか面白い仕組みになっていますね。
強参照しか普段使わない私にとって新鮮な気分です。
しかし、これって JDK 1.2 から追加された古い API のようです。(^_^;)
いったいどれだけのプログラマが意識して使っているのか、いや・・・使わざるを得なかったのか・・・
さて、次は PhantomReference を試してみよう。
たぶん、続く。
TAGS: Java,NetBeans |
2019年1月2日5:06 AM |
Java NetBeans
あけましておめでとうございます。
とうとう今年の5月で平成が終わってしまいます。
新しい元号がまだ発表されてないので一部の人は困っているようですがどうにかならないものなんでしょうか?
さて、どうにもならないことをどうかしようとすると頭が痛くなるから現実逃避のために Java で遊ぶとしよう。
SoftReference って聞いたことはあるけど実際にプログラムを組む上で使った記憶がないのでこれで遊んでみよう。
java.base モジュールの java.lang.ref パッケージにある public class SoftReference<T> extends Reference<T> クラスです。
API ドキュメントには下記のように記されてます。
メモリー要求に応じてガベージ・コレクタの判断でクリアされるソフト参照オブジェクトです。
ソフト参照は通常、メモリー・センシティブなキャッシュを実装するために使用されます。
ある時点で、オブジェクトがソフト到達可能であると、ガベージ・コレクタが判断したとします。
その時点で、ガベージ・コレクタは、そのオブジェクトへのソフト参照すべてと、強参照のチェーンを経由してそのオブジェクトに到達できるような、ソフト到達可能なほかのオブジェクトへのソフト参照すべてを原子的にクリアすることを選択できます。
同時にまたはあとで、ガベージ・コレクタは、参照キューに登録されているそれらの新しくクリアされたソフト参照をキューに入れます。
ソフト到達可能なオブジェクトへのすべてのソフト参照は、仮想マシンがOutOfMemoryErrorをスローする前にクリアされていることが保証されています。
そうでない場合、ソフト参照がクリアされる時点、またはさまざまなオブジェクトへの一連のソフト参照がクリアされる順序に制約はありません。
ただし、仮想マシンの実装は、最近作成されたソフト参照または最近使用されたソフト参照をクリアしないことが奨励されます。
このクラスの直接のインスタンスは、単純なキャッシュを実装するために使用できます。
このクラスまたは派生したサブクラスは、より洗練されたキャッシュを実装するために、もっと大きなデータ構造でも使用できます。
ソフト参照のリファレントが強到達可能であるかぎり、つまり実際に使用されているかぎり、ソフト参照はクリアされません。
このため、たとえば洗練されたキャッシュは、エントリへの強いリファレントを維持することで、もっとも新しく使用されたエントリが破棄されることを防ぎ、ほかのエントリはガベージ・コレクタの判断で破棄されるようにできます。
つまり、ガベージ・コレクタが参照の使用頻度とヒープの状態をみて参照の解放を勝手にやってくれるので OutOfMemoryError でプログラムが止まってしまうことがない。
ガベージ・コレクタが必死でがんばってくれる!私は感謝するぞ!
で、この SoftReference<T> クラスにはコンストラクタが2種類あります。
SoftReference(T referent)
指定されたオブジェクトを参照する新しいソフト参照を作成します。新しい参照は、どのキューにも登録されません。
パラメータ:referent – 新しいソフト参照が参照するオブジェクト
SoftReference(T referent, ReferenceQueue<? super T> q)
指定されたオブジェクトを参照し、指定されたキューに登録されている新しいソフト参照を作成します。
パラメータ:referent – 新しいソフト参照が参照するオブジェクトq – 参照が登録されるキュー。登録が必要ない場合はnull
メソッドは一つだけあります。
public T get()
参照オブジェクトのリファレントを返します。 プログラムまたはガベージ・コレクタによって、この参照オブジェクトがすでにクリアされている場合、このメソッドはnullを返します。
戻り値:この参照が表すオブジェクト。この参照オブジェクトがクリアされている場合はnull
もの凄く質素なクラスですね。
そう言うことでさっそくお試しプログラムを組んでみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
|
package jp.yucchi.softreference_g1gc; import java.lang.management.ManagementFactory; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.List; /** * * @author Yucchi */ public class SoftReference_G1GC { private static final ReferenceQueue<MemoryConsumer> REFEREN_CEQUEU = new ReferenceQueue<>(); public static void main(String[] args) { System.out.println(System.getProperty("os.name") + " " + System.getProperty("os.version") + " " + System.getProperty("os.arch")); System.out.println("JDK " + System.getProperty("java.version") + System.lineSeparator()); List<SoftReference<MemoryConsumer>> list = new ArrayList<>(); for (int i = 0; i < 1_000; i++) { var memoryConsumer = new MemoryConsumer(); var softReference = new SoftReference<>(memoryConsumer, REFEREN_CEQUEU); list.add(softReference); checkStatus(); } } private static void checkStatus() { var memoryBean = ManagementFactory.getMemoryMXBean(); var usage = memoryBean.getHeapMemoryUsage(); var usedMemory = usage.getUsed(); var gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans(); var gcStatus = new StringBuilder(); gcMXBeans.forEach(e -> { gcStatus .append(e.getName()) .append(" ") .append("GC Count: ") .append(e.getCollectionCount()) .append(" ") .append("GCTime = ") .append(e.getCollectionTime()) .append(" "); }); System.out.print("UsedMemory: " + usedMemory + " "); System.out.println(gcStatus); Reference<?> ref; while ((ref = REFEREN_CEQUEU.poll()) != null) { System.out.println(" --- POLL:" + ref + " ---"); } } private static class MemoryConsumer { private final byte[] buff; MemoryConsumer() { this.buff = new byte[100 * 1024 * 1024]; } } } |
無駄にメモリーを消費するインスタンスを1000個作っているだけです。
普通なら OutOfMemoryError でプログラムが止まってしまうはずですが SoftReference を使って賢いガベージ・コレクタの力で完走させようというものです。
プログラムの実行結果は次のようになりました。
Windows 10 10.0 amd64
JDK 11.0.1
UsedMemory: 113246208 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 222298112 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 331350016 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 440401920 G1 Young Generation GC Count: 0 GCTime = 0 G1 Old Generation GC Count: 0 GCTime = 0
UsedMemory: 546493104 G1 Young Generation GC Count: 1 GCTime = 3 G1 Old Generation GC Count: 0 GCTime = 0
略
UsedMemory: 110148312 G1 Young Generation GC Count: 57 GCTime = 109 G1 Old Generation GC Count: 2 GCTime = 1808
— POLL:java.lang.ref.SoftReference@59fa1d9b —
— POLL:java.lang.ref.SoftReference@28d25987 —
略
— POLL:java.lang.ref.SoftReference@527740a2 —
— POLL:java.lang.ref.SoftReference@13a5fe33 —
UsedMemory: 219243560 G1 Young Generation GC Count: 58 GCTime = 110 G1 Old Generation GC Count: 2 GCTime = 1808
UsedMemory: 328295464 G1 Young Generation GC Count: 58 GCTime = 110 G1 Old Generation GC Count: 2 GCTime = 1808
略
UsedMemory: 6762364184 G1 Young Generation GC Count: 222 GCTime = 332 G1 Old Generation GC Count: 12 GCTime = 8964
UsedMemory: 6871416088 G1 Young Generation GC Count: 222 GCTime = 332 G1 Old Generation GC Count: 12 GCTime = 8964
プログラムが終了するまで1分8秒もかかった。
私の7年前のパソコンが壊れたのかと思ってしまった。(^_^;)
NetBeans のプロファイラでどんなかんじなのか確認してみました。
確かに OutOfMemoryError でプログラムが止まらないようにガベージ・コレクタが頑張っているようだけどGC時間長くない?
プログラムの実行結果からも気のせいか GC の回数、累積時間が長いような気がする。
SoftReference ってこんなものなのかなぁ・・・
あっ、なにかプログラムに問題があるのかもしれない。
これは WeakReference で試して比較してみるしかない。
ということで次回に続く
TAGS: Java,NetBeans |
2019年1月1日4:38 PM |
JavaFX
このエントリーは じゃばえふえっくす Advent Calendar 2018 の12月22日分です。
JavaFX 8でPrintAPIが追加されて随分経ちます。
ネット上にもいろんな情報が共有されるようになりました。
そこではがき印刷をJavaFXのPrintAPIを使ってやってみようと思い立ちました。
宛名のほうは位置決めとか縦書きとかめんどくさいのでパスとします。(^_^;)
あっ・・・ めんどくさいじゃなくて解らないって言った方が近いもしれません。
縦書きなんかは一文字ずつ分解してFontMetricsを取得して構築するのかな?
さて、とりあえずはがき印刷のゴールは文面サイドだけにします。
シンプルにイメージファイルと賀詞を入れるだけのものとします。
まずJavaFXのPrintAPIについてざっくりと調べてみることにします。
javafx.graphicsモジュールにあるjavafx.printパッケージに九つのクラスと七つの列挙型があります。
クラス
JobSettings
PageLayout
PageRange
Paper
PaperSource
Printer
PrinterAttributes
PrinterJob
PrintResolution
列挙型
Collation
PageOrientation
PrintColor
Printer.MarginType
PrinterJob.JobStatus
PrintQuality
PrintSides
ぱっと見、馴染みやすそうな構成になっています。
まずPrinterクラスがどんなものなのか見てみます。
Printerクラス
このクラスにはデフォルトプリンター、全てのプリンター、プリンター名を取得するメソッドなどが用意されています。
それとプリンターのデフォルトのページ・レイアウトを取得するメソッドや指定されたパラメータを使って新しいPageLayoutインスタンスを取得するものがあります。
あとPrinterAttributesクラスを返すメソッドが一つあります。
public PrinterAttributes getPrinterAttributes()
プリンタの属性および機能をカプセル化する委譲オブジェクトを取得します。
このメソッドを利用してプリンターの各種属性値を取得可能となります。
それでは次に上記のメソッドで返されるプリンターの属性値をカプセルかするPrinterAttributesクラスを見てみます。
PrinterAttributesクラス
このクラスは、ジョブ印刷機能に関連するプリンタ属性とその他の属性をカプセル化します。
デフォルトプリンターのデフォルトの属性値の取得、サポートされている属性値を取得することができます。
丁合い設定、印刷部数、用紙の向き、用紙サイズ、給紙方法、色設定、品質設定、印刷解像度、両面印刷設定、最大部数、ページ範囲など。
属性値にはあらかじめ列挙型が次の7種類用意されています。
列挙型
Collation
PageOrientation
PrintColor
Printer.MarginType
PrinterJob.JobStatus
PrintQuality
PrintSides
それでは次にPaperクラスを見てみます。
Paperクラス
このクラスはプリンター用紙のサイズをカプセル化するクラスになります。
用紙のサイズを指定するのに20個のstaticフィールド値があらかじめ用意されています。
ただ、使用するプリンターでそれがサポートされているかの確認は必要でしょう。
あと、プリンター用紙のサイズ取得のメソッドと用紙の名前を取得するメソッドが用意されています。
サイズはポイント(1/72インチ)単位となっています。
DTPやってる人はご存じかもしれませんが一般人にはあまり馴染みのない単位です。
次はPaperSourceクラスを見ていきましょう。
PaperSourceクラス
プリンター用紙に使用される給紙トレイ、給紙方法を扱うクラスになります。
9個のstaticフィールド値があらかじめ用意されています。
あと給紙方法の名前を返すメソッドが一つ用意されています。
次はPrintResolutionクラスを見ます。
PrintResolutionクラス
プリンターのサポートされている解像度(送り方向と前後送り方向)を1インチ当たりのドット数(DPI)で表すクラスです。
用紙の前後送り方向の解像度(dpi単位)、用紙の送り方向の解像度(dpi単位)を返すメソッドが二つ用意されています。
次はPrinterJobクラスです。
PrinterJobクラス
PrinterJobは、JavaFXシーングラフ印刷のルートとなります。
これには、次のものが含まれます。
- プリンタの検出
- ジョブの作成
- サポートされるプリンタの機能に基づいたジョブの構成
- ページの設定
- ノード階層のページへのレンダリング。
APIドキュメントには非常にシンプルに印刷がされるように記述されています。
注意事項としては印刷と並行してノードを更新するのは避けてください。
と記述されています。
これはそうだろうなと誰もが思うだろうけど、ちょっと解りづらいというか・・・結局どうなの?って記述が下記のようにされてます。
FXアプリケーション・スレッドで印刷を実行する際の必要条件はありません。 ノードの印刷準備やジョブの呼出しはどのスレッドでも行うことができます。 ただし、アプリケーションUIの応答性に影響が及ばないように、FXアプリケーション・スレッドで実行される処理の量を最小限に抑えるのが一般的に望ましいと言えます。 したがって、印刷は新しいスレッドで実行し、実装内部のスケジューリングによって、FXスレッドで実行する必要のあるすべてのタスクがそのスレッドで実行されるようにすることをお薦めします。
FXアプリケーション・スレッドで印刷をできなくもないけど印刷用の新しいスレッドを用意して実行してくれってことでいいのかな?
今回のはがき印刷のプログラムではお勧めの印刷用の新しいスレッドを用意させていただくとしよう。
さて、このPrinterJobクラスには14個のメソッドと印刷ジョブのステータスをレポートする際に使われるネストされたstaticクラス列挙型PrinterJob.JobStatusがあります。
印刷ジョブのステータスを確認するにあたり、この列挙型PrinterJob.JobStatusの列挙型定数はありがたいです。
CANCELED ジョブはアプリケーションによって取り消されました。
DONE ジョブが印刷を開始し、その後endJob()を呼び出したところ、成功とレポートされました。
ERROR ジョブの実行中にエラーが発生しました。
NOT_STARTED 新しいジョブのステータス。
PRINTING ジョブは1ページ以上の印刷をリクエストし、まだ印刷を終了していません。
ジョブの一般的なライフサイクルは次のとおりです:
- ジョブは、ステータス
NOT_STARTED
で作成され、ダイアログなどでの構成中、このステータスにとどまります。
- ジョブは、最初のページが印刷されると、
PRINTING
状態になります。
- ジョブは、取り消されたり、エラーが発生したりすることなく正常に完了すると、
DONE
状態になります。 これでジョブが完了しました。
- エラーが発生した
ERROR
のジョブやCANCELED
のジョブも完了したと見なされます。
ジョブがライフサイクル中に前のステータスに戻ることはできず、現在のジョブ状態は実行可能な操作に影響を及ぼします。
たとえば、すでに印刷状態を過ぎて終了状態のいずれかになったジョブが再度印刷を開始することはできません。
PrinterJobクラス14個のメソッドはジョブの作成、取り消しステータス取得などのメソッドがあります。
あとジョブの構成オプションを取得するものや、ジョブに使われるプリンターを取得するもの、ジョブに使うプリンターの変更やネイティブな印刷ダイアログ、ページ設定ダイアログを表示するものなどがあります。
それではJobSettingsクラスを見ていきましょう。
JobSettingsクラス
JobSettingsクラスは、印刷ジョブの構成のほとんどをカプセル化します。
アプリケーションでJobSettingsインスタンスのセットを直接作成したり設定したりすることはありません(できません)。
印刷ジョブが作成された時点で、すでにそのジョブに1つインストールされています。
このクラスは印刷ジョブを構成するためのメソッドが30個あります。
ジョブの名前の設定、取得ページレイアウト、印刷品質、部数などだいたい必要なものがそろっています。
このJobSettingsクラスでジョブ構成を設定してもネイティブな印刷ダイアログを使用すれば設定を変更できてしまうようです。
次はPageLayoutクラスを見てみます。
PageLayoutクラス
PageLayoutは、コンテンツのレイアウトに必要な情報をカプセル化します。
8個のメソッドが用意されています。
それらを使ってページ・レイアウトのマージン(ポイント単位)、用紙、ページの印刷可能領域のデータ、向きを取得できます。
最後にPageRangeクラスを見ます。
PageRangeクラス
PageRangeは、印刷するジョブ印刷ストリーム・ページを選択または制限するために使用されます。
ページ番号は1から始まり、ユーザーの指定内容に対応します。
開始ページはゼロより大きく、終了ページ以下である必要があります。
開始と終了が同一の場合、範囲は単一ページを表します。
ジョブのページ数を超える値は、印刷時に単に無視されます。
各クラスの関連は下図のような感じですね。
APIドキュメントを読んでいるだけでは面白くないのでとりあえず超シンプルなものを組んで印刷してみよう。
テキストエリアにデフォルトプリンターの名前、プリンター一覧の表示、そして印刷ジョブに使用するプリンターの設定、プリンターの属性の一部を表示させます。
UIはこれから作成予定のはがき印刷のものを流用しているのでちょっと変なことなってますがそれはきにしないでくださいませ。
これらの処理をトグルボタンが押されたら開始するように次のようにプログラムを組んでみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
@FXML void handlePrinterInfoToggleButtonAction(ActionEvent event) { if (printerInfoToggleButton.isSelected()) { // デフォルトプリンター EPSON EP-806A Series var defaultPrinter = Printer.getDefaultPrinter(); printerTextArea.setText("DefaultPrinter: " + defaultPrinter.getName() + CR); // プリンター一覧 printerTextArea.appendText(CR + "[プリンター一覧]" + CR); Printer.getAllPrinters() .stream() .forEach(e -> { printerTextArea.appendText(e.getName() + CR); }); // プリンター選択 (デフォルトでないプリンターを選択しています) var targetPrinter = Printer.getAllPrinters() .stream() // プリンター一覧に存在するが指定できないのでデフォルトプリンターが返される。 // OSのプリンターの設定で"EP-808A"のようにリネームすれば指定できた。 .filter(p -> p.getName().matches("EP-808A Series(ネットワーク)")) .findFirst() .orElseGet(() -> { printerTextArea.appendText(CR + "指定されたプリンターが見つからなかったのでデフォルトプリンターを使います。"); return defaultPrinter; }); printerTextArea.appendText(CR + "使用するプリンター: " + targetPrinter.getName() + CR); // デフォルトプリンターの属性 var attributes = defaultPrinter.getPrinterAttributes(); printerTextArea.appendText(CR + "[サポートされる向き]" + CR); attributes.getSupportedPageOrientations() .forEach(a -> { printerTextArea.appendText(a.name() + CR); }); printerTextArea.appendText(CR + "[サポートされる給紙方法(給紙ビンや給紙トレイ)]" + CR); attributes.getSupportedPaperSources() .forEach(a -> { printerTextArea.appendText(a.getName() + CR); }); printerTextArea.setVisible(true); printerInfoToggleButton.setText("Hide Info"); } else { printerTextArea.clear(); printerTextArea.setVisible(false); printerInfoToggleButton.setText("Printer Info"); } } |
プログラムの実行結果は次のようになります。
まず、デフォルトプリンターを取得するためにPrinterクラスのstaticメソッドgetDefaultPrinter()を使います。
そしてデフォルトプリンターの名前を取得するためにPrinterクラスのString getName()メソッドを使いました。
次にプリンター一覧を表示させるために全てのプリンターを取得します。
PrinterクラスのstaticメソッドObservableSet<Printer> getAllPrinters()を使います。
プリンターの名前を取得するために先ほどと同様にPrinterクラスのString getName()メソッドを使います。
そしてプリンターの選択をしてみます。
現在のシステムに組み込まれているプリンターは下図のようになっています。
デフォルトプリンターは”EPSON EP-806A Series”で間違いないのでEP-808A Series(ネットワーク)を指定してみました。
ところが予期せぬ結果となりました。
なんと、プログラムのプリンター一覧の表示結果では確かにあるのに指定できません。
T orElseGet(Supplier<? extends T> supplier)でデフォルトプリンターが返されます。
これの原因は調べてないので解りませんが別のPCでも同じでしたのでJavaFX側の問題かもしれません。
この解決策はOS側のプリンター設定で”EP-808A”のようにリネームすれば指定できました。
次にデフォルトプリンターの属性を取得しています。
var attributes = defaultPrinter.getPrinterAttributes();
PrinterクラスのPrinterAttributes getPrinterAttributes()メソッドでプリンタの属性および機能をカプセル化する委譲オブジェクトを取得します。
そしてそれを使い各種属性値を取得表示させています。
PrinterAttributesクラスのSet<PageOrientation> getSupportedPageOrientations()でサポートされる向きを取得、
Set<PaperSource> getSupportedPaperSources()でサポートされる給紙方法(給紙ビンや給紙トレイ)を取得しています。
解りやすくフレンドリーなAPIがそろってます。
それではこれを印刷するコードを次のように追加しました。
注意: ステータスを確認するのにスレッドを少しスリープさせています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
|
private void printPrinterInfo(TextArea printerTextArea) { jobStatusLabel.setText("Create a printer job."); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // デフォルトプリンター var defaultPrinter = Printer.getDefaultPrinter(); // デフォルトプリンターを使用してプリンタージョブを作成 var job = PrinterJob.createPrinterJob(defaultPrinter); // ジョブステータス確認用 System.out.println("JobStatus: " + job.jobStatusProperty().asString().get()); jobStatusLabel.setText(job.jobStatusProperty().asString().get()); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // プリンタージョブステータス job.jobStatusProperty().addListener(e -> { System.out.println("JobStatus: " + job.jobStatusProperty().asString().get()); jobStatusLabel.setText(job.jobStatusProperty().asString().get()); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } }); // ジョブ設定 var jobSettings = job.getJobSettings(); // ジョブの名前 jobSettings.setJobName("Greeting"); if (job != null) { // ネット上でよく見る駄目な印刷ダイアログの使用方法 // これだとキャンセルしても印刷開始してしまう。 // job.showPrintDialog(anchorPane.getScene().getWindow()); // 印刷ダイアログ var nativeJob = job.showPrintDialog(anchorPane.getScene().getWindow()); // 印刷ダイアログでキャンセルした場合の処理 if (!nativeJob) { System.out.println("Job has been canceled."); job.cancelJob(); } // ノード(TextArea printerTextArea)をプリント boolean success = job.printPage(printerTextArea); if (success) { // プリンタのキューに正常にスプールされたら true job.endJob(); } else { System.out.println("Printing failed."); } } else { jobStatusLabel.setText("Could not create printer job."); } } private void jobSleep() { try { Thread.sleep(1_500); } catch (InterruptedException ex) { // ナイスキャッチ! Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex); } } |
PrinterJobクラスのstatic final PrinterJob createPrinterJob(Printer printer)メソッドでデフォルトプリンターを引数にとり新しいPrinterJobを作成します。
印刷に必要ではないけどJobStatus確認用にリスナーつけたり、ラベル、標準出力への表示などをしています。
PrinterJobクラスのsynchronized JobSettings getJobSettings()メソッドでジョブ構成オプション(部数、丁合いオプション、両面オプションなど)をカプセル化します。
初期値はプリンターの現在の設定に基づいています。
次にJobSettingsクラスのvoid setJobName(String name)メソッドでジョブの名前を設定します。
このプログラムではOSネイティブの印刷ダイアログを使用しました。
PrinterJobクラスのsynchronized boolean showPrintDialog(Window owner)を使います。
私はJavaFX 8がリリースされた当時このPrintAPIを早く試してみたい(いじってみたい)あまりにとんでもない間違いを犯していました。
当時、私は印刷ダイアログを呼び出して使うのに次のようにコードを書いていました。
job.showPrintDialog(anchorPane.getScene().getWindow());
これでも動くことは動くけどキャンセルしても印刷が実行されてしまいます。
twitterでこのことをつぶやいて何人かの方にリツイートされてしまい悪いことをしていまいました。
よくよく考えたらなんのためにboolean型の戻り値があるのかってことですね。
ネット上では印刷ダイアログを呼び出し使う方法がよくありますがほとんどキャンセル処理をしていません。
よって正しく使うためには印刷ダイアログとキャンセル処理は抱き合わせて使用することをお勧めします。
// 印刷ダイアログ
var nativeJob = job.showPrintDialog(anchorPane.getScene().getWindow());
// 印刷ダイアログでキャンセルした場合の処理
if (!nativeJob) {
System.out.println(“Job has been canceled.”);
job.cancelJob();
}
そして最後に印刷を開始するためにPrinterJobクラスのsynchronized boolean printPage(Node node)メソッドを使います。
引数のNodeが印刷対象となっています。
印刷に成功すればtrueが返されます。
印刷に成功したことを確認したらPrinterJobクラスのsynchronized boolean endJob()メソッドでジョブをプリンタのキューに正常にスプールされたらtrueを返します。
falseを返されたらプリンタージョブが失敗なのでそれなりの対処をします。
実際に印刷されたものがこのようになります。
簡単ですね!
これで基本的なPrinter APIを扱うことが可能となったのではがき印刷のプログラムを組むことにします。
下図のようなGUIのプログラムを組んでみました。
Printボタンをクリックすると印刷のためのワーカースレッドが起動して印刷をはじめます。
いちおうキャンセルボタンもつけました。
Printer Infoトグルボタンはシステムのプリンターの属性を表示させています。
Open Imageボタンは画像を変更するためのファイルチューザーを出します。
賀詞を入れるためのテキストフィールドとジョブステータス表示用のラベルも用意しました。
印刷に関するコードを下記に示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
|
private void printGreeting(Group greeting) { printTask = new Task<>() { private PageLayout pageLayout; @Override public Boolean call() { Platform.runLater(() -> { jobStatusLabel.setText("Create a printer job."); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // プリンタージョブを作成 // // デフォルトプリンターを使ってプリンタージョブを作成 // var job = PrinterJob.createPrinterJob(); // プリンターを指定してプリンタージョブを作成 var job = PrinterJob.createPrinterJob(targetPrinter); // ジョブステータス確認用 System.out.println("JobStatus: " + job.jobStatusProperty().asString().get()); Platform.runLater(() -> { jobStatusLabel.setText(job.jobStatusProperty().asString().get()); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // プリンタージョブステータス job.jobStatusProperty().addListener(e -> { System.out.println("JobStatus: " + job.jobStatusProperty().asString().get()); Platform.runLater(() -> { jobStatusLabel.setText(job.jobStatusProperty().asString().get()); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // JobStatus が PRINTING の状態で強制的にキャンセル if (CANCEL_DEBUG && PrinterJob.JobStatus.PRINTING == job.jobStatusProperty().get()) { System.out.println("JobStatus が PRINTING の状態で Cancel"); job.cancelJob(); // JobStatus: CANCELED -> JobStatus: ERROR となる。 } }); // ジョブ設定 var jobSettings = job.getJobSettings(); // ジョブの名前 jobSettings.setJobName("Greeting"); // 自動ではがきに印刷 jobSettings.setPaperSource(PaperSource.AUTOMATIC); // // この設定でもはがきに印刷 // jobSettings.setPaperSource(PaperSource.TOP); // ページレイアウト設定 pageLayout = targetPrinter.createPageLayout(Paper.JAPANESE_POSTCARD, PageOrientation.LANDSCAPE, Printer.MarginType.HARDWARE_MINIMUM); // ジョブにページレイアウトを設定 jobSettings.setPageLayout(pageLayout); // 印刷カラーを設定 jobSettings.setPrintColor(PrintColor.COLOR); // 印刷品質を設定 jobSettings.setPrintQuality(PrintQuality.NORMAL); // 印刷部数を設定 jobSettings.setCopies(1); // 部単位で印刷設定 jobSettings.setCollation(Collation.UNCOLLATED); if (job != null) { // // 用紙設定ダイアログ // var nativePageSetup = job.showPageSetupDialog(anchorPane.getScene().getWindow()); // // 用紙設定ダイアログキャンセル // if (!nativePageSetup) { // System.out.println("PageSetup has been canceled."); // job.cancelJob(); // printTask.cancel(); // } // // 印刷ダイアログ // var nativeJob = job.showPrintDialog(anchorPane.getScene().getWindow()); // // 印刷ダイアログでキャンセルした場合の処理 // if (!nativeJob) { // System.out.println("Job has been canceled."); // job.cancelJob(); // printTask.cancel(); // } // タスクのキャンセル処理 if (isCancelled()) { if (PrinterJob.JobStatus.PRINTING == job.jobStatusProperty().get() || PrinterJob.JobStatus.NOT_STARTED == job.jobStatusProperty().get()) { // プリンタージョブをキャンセル job.cancelJob(); System.out.println("Cancel printing."); return false; } else { System.out.println("Job has already been canceled."); } return false; } // ノード(Group greeting)をプリント boolean success = job.printPage(greeting); if (success) { // プリンタのキューに正常にスプールされたら true return job.endJob(); } else { System.out.println("Printing failed."); return false; } } else { Platform.runLater(() -> { jobStatusLabel.setText("Could not create printer job."); }); return false; } } private void jobSleep() { try { Thread.sleep(1_500); } catch (InterruptedException ex) { // ナイスキャッチ! if (isCancelled()) { System.out.println("Canceled by InterruptedException: sleep interrupted"); } else { Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex); } } } }; executorService.submit(printTask); // WorkerStateEvent を使ってWorkerオブジェクトの状態を監視 printTask.setOnScheduled(wse -> { System.out.println("WorkerState: Scheduled"); }); printTask.setOnRunning(wse -> { System.out.println("WorkerState: OnRunning"); }); printTask.setOnSucceeded(wse -> { System.out.println("WorkerState: Succeeded"); }); printTask.setOnCancelled(wse -> { System.out.println("WorkerState: Cancelled"); }); printTask.setOnFailed(wse -> { System.out.println("WorkerState: Failed"); }); // disableProperty と Workerオブジェクトの stateProperty をバインド gashiTextField.disableProperty().bind(printTask.stateProperty().isEqualTo(RUNNING)); printButton.disableProperty().bind(printTask.stateProperty().isEqualTo(RUNNING)); cancelButton.disableProperty().bind(printTask.stateProperty().isNotEqualTo(RUNNING)); openImageButton.disableProperty().bind(printTask.stateProperty().isEqualTo(RUNNING)); printerInfoToggleButton.disableProperty().bind(printTask.stateProperty().isEqualTo(RUNNING)); } |
コードが長くなっていますが基本的なことは先ほどのテキストエリアの文字列を印刷するものと何ら変わりはありません。
ただ、APIドキュメントでお勧めされているように印刷用のスレッドを別に起動させて印刷をしています。
今回のプログラムでは印刷ダイアログは使用せずJavaFXがはじめから用意してあるものを使用してみます。
まず、PrinterJobクラスのsynchronized JobSettings getJobSettings()を使いをカプセル化されたジョブ構成オプションを取得します。
JobSettingsクラスのsetterメソッドを使い設定します。
void setJobName(String string)メソッドで名前をつけます。
void setPaperSource(PaperSource ps)メソッドで給紙方法を設定します。
void setPageLayout(PageLayout pl)メソッドで使用するPageLayoutを設定します。
void setPrintColor(PrintColor pc)メソッドで印刷カラーを設定します。
void setPrintQuality(PrintQuality pq)メソッドで印刷品質を設定します。
final void setCopies(int i)メソッド印刷部数を設定します。
void setCollation(Collation cltn)メソッドで部単位で印刷設定をします。
カプセル化されているのでgetter、setterメソッドでアクセスできるのは楽ですね
ページレイアウトの設定はPrinterクラスのPageLayout createPageLayout(Paper paper, PageOrientation orient, Printer.MarginType mType)メソッドを使い設定します。
印刷に関するものはこの程度ですね。
ではこのプログラムを実行してみましょう。
プリンターの印刷される音が鳴り止んではがきをとりにいって唖然としました。
こんなふうに印刷されていました。
左上から少しだけ画像が切り取られて印刷されただけです。
これでは印刷はできたけどこれをもらった人はなんだろう?と首をかしげるに違いない!
プログラムを修正することにします。
何故このようになったのか?
はがきの印刷領域より画像が大きすぎたからと思われる。
左上の隅はJavaの2Dでは原点(0, 0)になるからそこから印刷可能な範囲だけ画像が印刷されたのだろう。
では残りの部分を印刷可能領域分だけ画像を位置変更して必要なだけ印刷すれば画像全体が印刷できるんじゃないか。
では早速コードを修正してみよう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
private void printGreeting(Group greeting) { printTask = new Task<>() { private PageLayout pageLayout; @Override public Boolean call() { Platform.runLater(() -> { jobStatusLabel.setText("Create a printer job."); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // プリンタージョブを作成 // // デフォルトプリンターを使ってプリンタージョブを作成 // var job = PrinterJob.createPrinterJob(); // プリンターを指定してプリンタージョブを作成 var job = PrinterJob.createPrinterJob(targetPrinter); // ジョブステータス確認用 System.out.println("JobStatus: " + job.jobStatusProperty().asString().get()); Platform.runLater(() -> { jobStatusLabel.setText(job.jobStatusProperty().asString().get()); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // プリンタージョブステータス job.jobStatusProperty().addListener(e -> { System.out.println("JobStatus: " + job.jobStatusProperty().asString().get()); Platform.runLater(() -> { jobStatusLabel.setText(job.jobStatusProperty().asString().get()); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // JobStatus が PRINTING の状態で強制的にキャンセル if (CANCEL_DEBUG && PrinterJob.JobStatus.PRINTING == job.jobStatusProperty().get()) { System.out.println("JobStatus が PRINTING の状態で Cancel"); job.cancelJob(); // JobStatus: CANCELED -> JobStatus: ERROR となる。 printTask.cancel(); } }); // ジョブ設定 var jobSettings = job.getJobSettings(); // ジョブの名前 jobSettings.setJobName("Greeting"); // 自動ではがきに印刷 jobSettings.setPaperSource(PaperSource.AUTOMATIC); // // この設定でもはがきに印刷 // jobSettings.setPaperSource(PaperSource.TOP); // ページレイアウト設定 pageLayout = targetPrinter.createPageLayout(Paper.JAPANESE_POSTCARD, PageOrientation.LANDSCAPE, Printer.MarginType.HARDWARE_MINIMUM); // ジョブにページレイアウトを設定 jobSettings.setPageLayout(pageLayout); // 印刷カラーを設定 jobSettings.setPrintColor(PrintColor.COLOR); // 印刷品質を設定 jobSettings.setPrintQuality(PrintQuality.NORMAL); // 印刷部数を設定 jobSettings.setCopies(1); // 部単位で印刷設定 jobSettings.setCollation(Collation.UNCOLLATED); // 印刷用紙に印刷可能な幅と高さ double printableWidth = pageLayout.getPrintableWidth(); double printableHeight = pageLayout.getPrintableHeight(); // 印刷エリアの設定(印刷対象ノードをそのままの大きさで設定) printAreaRectangle = new Rectangle(greeting.getBoundsInParent().getWidth(), greeting.getBoundsInParent().getHeight(), null); // 印刷エリアの位置と大きさ double printRectX = printAreaRectangle.getX(); double printRectY = printAreaRectangle.getY(); double printRectWidth = printAreaRectangle.getWidth(); double printRectHeight = printAreaRectangle.getHeight(); // 印刷に必要な用紙枚数(行、列) int rows = (int) Math.ceil(printRectHeight / printableHeight); int columns = (int) Math.ceil(printRectWidth / printableWidth); // ノードのクリップを保存 var oldClip = greeting.getClip(); var oldTransforms = new ArrayList<>(greeting.getTransforms()); // printAreaRectangle のエリアをクリップとして設定する greeting.setClip(new javafx.scene.shape.Rectangle(printRectX, printRectY, printRectWidth, printRectHeight)); // 0,0 に移動 greeting.getTransforms().add(new Translate(-printRectX, -printRectY)); // 印刷ページに適合するようにノードを移動する変換 var gridTransform = new Translate(); greeting.getTransforms().add(gridTransform); if (job != null) { // // 用紙設定ダイアログ // var nativePageSetup = job.showPageSetupDialog(anchorPane.getScene().getWindow()); // // 用紙設定ダイアログキャンセル // if (!nativePageSetup) { // System.out.println("PageSetup has been canceled."); // job.cancelJob(); // printTask.cancel(); // } // // 印刷ダイアログ // var nativeJob = job.showPrintDialog(anchorPane.getScene().getWindow()); // // 印刷ダイアログでキャンセルした場合の処理 // if (!nativeJob) { // System.out.println("Job has been canceled."); // job.cancelJob(); // printTask.cancel(); // } // タスクのキャンセル処理 if (isCancelled()) { if (PrinterJob.JobStatus.PRINTING == job.jobStatusProperty().get() || PrinterJob.JobStatus.NOT_STARTED == job.jobStatusProperty().get()) { // プリンタージョブをキャンセル job.cancelJob(); System.out.println("Cancel printing."); return false; } else { System.out.println("Job has already been canceled."); } return false; } boolean success = true; // 印刷ページごとに、ノードを移動 for (int row = 0; row < rows; row++) { for (int col = 0; col < columns; col++) { gridTransform.setX(-col * printableWidth); gridTransform.setY(-row * printableHeight); if (DEBUG) { jobSleep(); } success &= job.printPage(pageLayout, greeting); } } if (success) { // ノードを元に戻す restoreNode(greeting, oldTransforms, oldClip); return job.endJob(); } else { // ノードを元に戻す restoreNode(greeting, oldTransforms, oldClip); System.out.println("Printing failed."); return false; } } else { Platform.runLater(() -> { jobStatusLabel.setText("Could not create printer job."); }); // ノードを元に戻す restoreNode(greeting, oldTransforms, oldClip); return false; } } private void jobSleep() { try { Thread.sleep(1_500); } catch (InterruptedException ex) { // ナイスキャッチ! if (isCancelled()) { System.out.println("Canceled by InterruptedException: sleep interrupted"); } else { Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex); } } } // ノードを元に戻す private void restoreNode(Group greeting, List<Transform> oldTransforms, Node oldClip) { greeting.getTransforms().clear(); greeting.getTransforms().addAll(oldTransforms); greeting.setClip(oldClip); } }; executorService.submit(printTask); // WorkerStateEvent を使ってWorkerオブジェクトの状態を監視 printTask.setOnScheduled(wse -> { System.out.println("WorkerState: Scheduled"); }); printTask.setOnRunning(wse -> { System.out.println("WorkerState: OnRunning"); }); printTask.setOnSucceeded(wse -> { System.out.println("WorkerState: Succeeded"); }); printTask.setOnCancelled(wse -> { System.out.println("WorkerState: Cancelled"); }); printTask.setOnFailed(wse -> { System.out.println("WorkerState: Failed"); }); // disableProperty と Workerオブジェクトの stateProperty をバインド gashiTextField.disableProperty().bind(printTask.stateProperty().isEqualTo(RUNNING)); printButton.disableProperty().bind(printTask.stateProperty().isEqualTo(RUNNING)); cancelButton.disableProperty().bind(printTask.stateProperty().isNotEqualTo(RUNNING)); openImageButton.disableProperty().bind(printTask.stateProperty().isEqualTo(RUNNING)); printerInfoToggleButton.disableProperty().bind(printTask.stateProperty().isEqualTo(RUNNING)); } |
さぁ、これで理論上は問題ないはず!
さっそくプログラムを実行して印刷してみよう。
印刷結果はこうなりました。
無事に印刷できたけど9枚のはがきが消費されました。
これを年賀状でだしたら受け取った人はパズルをすることになります。(^_^;)
わたしはなんと愚かな発想をしてしまったのだろう・・・
印刷可能領域に合わせるように画像をスケーリングしてしまえば良いだけのことじゃないですか!
せっかくだからこの複数枚印刷の機能を残したまま、縮小印刷機能を実装することにします。
使う用紙の印刷可能領域に画像をあわせるだけだから簡単ですね。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
// Single page printing ///////////////////////////////////////////// if (scaling.get()) { if (job != null) { // 印刷対象ノードを印刷範囲内にスケーリング fit2PrintableSize(); // // 用紙設定ダイアログ // var nativePageSetup = job.showPageSetupDialog(anchorPane.getScene().getWindow()); // // 用紙設定ダイアログキャンセル // if (!nativePageSetup) { // System.out.println("PageSetup has been canceled."); // job.cancelJob(); // printTask.cancel(); // } // // 印刷ダイアログ // var nativeJob = job.showPrintDialog(anchorPane.getScene().getWindow()); // // 印刷ダイアログでキャンセルした場合の処理 // if (!nativeJob) { // System.out.println("Job has been canceled."); // job.cancelJob(); // printTask.cancel(); // } // タスクのキャンセル処理 if (isCancelled()) { if (PrinterJob.JobStatus.PRINTING == job.jobStatusProperty().get() || PrinterJob.JobStatus.NOT_STARTED == job.jobStatusProperty().get()) { // プリンタージョブをキャンセル job.cancelJob(); System.out.println("Cancel printing."); if (scaling.get()) { // 印刷対象ノードを元の大きさにスケーリング fit2ImageSize(); } return false; } else { System.out.println("Job has already been canceled."); } return false; } // ノード(Group greeting)をプリント boolean success = job.printPage(greeting); if (success) { // 印刷対象ノードを元の大きさにスケーリング fit2ImageSize(); // プリンタのキューに正常にスプールされたら true return job.endJob(); } else { System.out.println("Printing failed."); // 印刷対象ノードを元の大きさにスケーリング fit2ImageSize(); return false; } } else { Platform.runLater(() -> { jobStatusLabel.setText("Could not create printer job."); }); return false; } } |
複数枚印刷のコードに印刷領域にあわせて印刷対象のノードをスケーリングして印刷するだけのコードを使いするだけです。
どちらの印刷方法にするかはチェックボックスを使って分岐させています。
印刷が終わったら元の大きさにもどしています。もちろん成功、失敗、キャンセルでも。
この自動スケーリングで印刷した結果が下の画像になります。
これでクリスマスカード、年賀状もシンプルなものならJavaFXで印刷できるようになった。
完全に自己満足の世界にひたっている。
いちおう動いてるのとキャンセルしたときのGifアニメを貼っておきます。
下のGifアニメではJobStatusがNOT_STARTEDのときにキャンセルボタンによるキャンセルをかけています。
あともう一つ、キャンセルボタンではタイミング合わせるのが難しかったのでJobStatusがPRINTINGに変更されたら強制的キャンセルをするようにしてあります。
JobStatusがPRINTINGの時にキャンセルかけるとCANCELED -> ERROR となるんですね。
なんでCANCELEDで終了しないのだろう?これは印刷リクエストがされたあとだからERRORで終了という解釈でいいんだろうか?
最後にこのプログラムのコードを載せておきます。
|
module NewYearCard { requires javafx.controls; requires java.desktop; requires javafx.fxml; requires java.logging; exports jp.yucchi.newyearcard; opens jp.yucchi.newyearcard to javafx.fxml; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
|
<?xml version="1.0" encoding="UTF-8"?> <?import javafx.scene.Group?> <?import javafx.scene.control.Button?> <?import javafx.scene.control.CheckBox?> <?import javafx.scene.control.Label?> <?import javafx.scene.control.TextArea?> <?import javafx.scene.control.TextField?> <?import javafx.scene.control.ToggleButton?> <?import javafx.scene.image.ImageView?> <?import javafx.scene.layout.AnchorPane?> <?import javafx.scene.layout.StackPane?> <?import javafx.scene.text.Font?> <?import javafx.scene.text.Text?> <AnchorPane id="AnchorPane" fx:id="anchorPane" minHeight="730.0" minWidth="1000.0" prefHeight="730.0" prefWidth="1000.0" style="-fx-background-color: white;" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.yucchi.newyearcard.FXMLDocumentController"> <children> <Label id="GashiLabel" fx:id="gashiLabel" alignment="CENTER_RIGHT" layoutX="195.0" layoutY="730.0" prefHeight="31.0" prefWidth="36.0" text="賀詞: " AnchorPane.bottomAnchor="11.0" AnchorPane.leftAnchor="200.0"> <font> <Font size="14.0" /> </font> </Label> <TextField id="GashiTextField" fx:id="gashiTextField" alignment="CENTER_RIGHT" focusTraversable="false" layoutX="236.0" layoutY="730.0" prefHeight="31.0" prefWidth="270.0" text="Merry Christmas!" AnchorPane.bottomAnchor="11.0" AnchorPane.leftAnchor="236.0" AnchorPane.rightAnchor="493.6"> <font> <Font size="14.0" /> </font> </TextField> <Label id="StatusLabel" fx:id="statusLabel" layoutX="14.0" layoutY="731.0" prefHeight="31.0" prefWidth="46.0" text="Status:" AnchorPane.bottomAnchor="11.0" AnchorPane.leftAnchor="10.0"> <font> <Font size="14.0" /> </font> </Label> <Label id="JobStatusLabel" fx:id="jobStatusLabel" layoutX="59.0" layoutY="725.0" prefHeight="31.0" prefWidth="135.0" AnchorPane.bottomAnchor="11.0" AnchorPane.leftAnchor="60.0"> <font> <Font size="14.0" /> </font> </Label> <Button id="PtintButton" fx:id="printButton" layoutX="879.0" layoutY="731.0" mnemonicParsing="false" onAction="#handlePrintButtonAction" prefHeight="31.0" prefWidth="87.0" style="-fx-background-color: ghostwhite;" text="Print" AnchorPane.bottomAnchor="11.0" AnchorPane.rightAnchor="10.0"> <font> <Font size="14.0" /> </font> </Button> <Button id="CancelButton" fx:id="cancelButton" disable="true" layoutX="824.0" layoutY="731.0" mnemonicParsing="false" onAction="#handleCancelButtonAction" prefHeight="31.0" prefWidth="87.0" style="-fx-background-color: ghostwhite;" text="Cancel" AnchorPane.bottomAnchor="11.0" AnchorPane.rightAnchor="107.0" /> <CheckBox id="ScalingCheckBox" fx:id="scalingCheckBox" layoutX="511.0" layoutY="737.0" mnemonicParsing="false" prefHeight="20.0" prefWidth="70.0" selected="true" style="-fx-background-color: ghostwhite;" text="Scaling" AnchorPane.bottomAnchor="16.0" AnchorPane.rightAnchor="414.6"> <font> <Font size="14.0" /> </font> </CheckBox> <Button id="OpenImageButton" fx:id="openImageButton" layoutX="594.0" layoutY="730.0" mnemonicParsing="false" onAction="#handleOpenImageButtonAction" style="-fx-background-color: ghostwhite;" text="Open Image" AnchorPane.bottomAnchor="11.0" AnchorPane.rightAnchor="309.2"> <font> <Font size="14.0" /> </font> </Button> <ToggleButton id="PrinterInfoToggleButton" fx:id="printerInfoToggleButton" layoutX="703.0" layoutY="730.0" minHeight="31.0" minWidth="90.0" mnemonicParsing="false" onAction="#handlePrinterInfoToggleButtonAction" prefHeight="31.0" prefWidth="90.0" style="-fx-background-color: ghostwhite;" text="Printer Info" AnchorPane.bottomAnchor="11.0" AnchorPane.rightAnchor="207.4"> <font> <Font size="14.0" /> </font> </ToggleButton> <Group id="Greeting" fx:id="greeting"> <children> <StackPane id="StackPane" fx:id="stackPane" alignment="TOP_LEFT" prefHeight="675.0" prefWidth="1000.0"> <children> <ImageView id="ImageView" fx:id="imageView" pickOnBounds="true" preserveRatio="true" /> <Text id="GashiText" fx:id="gashiText" strokeType="OUTSIDE" strokeWidth="0.0" textAlignment="RIGHT" translateX="210.0" translateY="70.0" wrappingWidth="720.0"> <font> <Font size="50.0" /> </font> </Text> <TextArea id="PrinterTextArea" fx:id="printerTextArea" prefHeight="675.0" prefWidth="1000.0" visible="false"> <font> <Font size="14.0" /> </font> </TextArea> </children> </StackPane> </children> </Group> </children> </AnchorPane> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
|
package jp.yucchi.newyearcard; import javafx.print.Printer; import javafx.print.PrinterAttributes; /** * * @author Yucchi */ class PrinterInfomation { private static final String CR = System.lineSeparator(); String showPrinterDetails() { var printerDetails = new StringBuilder(); // デフォルトプリンター var defaultPrinter = Printer.getDefaultPrinter().getName(); printerDetails.append(CR).append("DefaultPrinter: ").append(defaultPrinter).append(CR); printerDetails.append(CR).append("[AllPrinters]").append(CR); Printer.getAllPrinters() .stream() .forEach(e -> { printerDetails.append(e.getName()).append(CR); }); printerDetails.append(CR).append("[PrinterAttributes]").append(CR); Printer.getAllPrinters() .stream() .forEach(e -> { printerDetails.append(e.getName()).append(CR); PrinterAttributes attributes = e.getPrinterAttributes(); printerDetails.append("サポートされている最大部数").append(CR).append(attributes.getMaxCopies()).append(CR); printerDetails.append("サポートされる丁合い設定").append(CR); attributes.getSupportedCollations() .forEach(a -> { printerDetails.append(a).append(CR); }); printerDetails.append("サポートされる向き").append(CR); attributes.getSupportedPageOrientations() .forEach(a -> { printerDetails.append(a).append(CR); }); printerDetails.append("サポートされる用紙サイズ").append(CR); attributes.getSupportedPapers() .forEach(a -> { printerDetails.append(a).append(CR); }); printerDetails.append("サポートされる給紙方法(給紙ビンや給紙トレイ)").append(CR); attributes.getSupportedPaperSources() .forEach(a -> { printerDetails.append(a).append(CR); }); printerDetails.append("サポートされる色設定").append(CR); attributes.getSupportedPrintColors() .forEach(a -> { printerDetails.append(a).append(CR); }); printerDetails.append("サポートされる品質設定").append(CR); attributes.getSupportedPrintQuality() .forEach(a -> { printerDetails.append(a).append(CR); }); printerDetails.append("サポートされる印刷解像度").append(CR); attributes.getSupportedPrintResolutions() .forEach(a -> { printerDetails.append(a).append(CR); }); printerDetails.append("サポートされる両面設定").append(CR); attributes.getSupportedPrintSides() .forEach(a -> { printerDetails.append(a).append(CR); }); printerDetails.append("ページ範囲がサポートされるかどうか").append(CR).append(attributes.supportsPageRanges()).append(CR).append(CR); }); return printerDetails.toString(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
package jp.yucchi.newyearcard; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; /** * * @author Yucchi */ public class NewYearCard extends Application { public static void main(String[] args) { launch(args); } @Override public void start(Stage stage) throws Exception { FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("FXMLDocument.fxml")); Parent root = fxmlLoader.load(); FXMLDocumentController controller = fxmlLoader.getController(); Scene scene = new Scene(root); stage.setScene(scene); stage.setOnCloseRequest(e -> { controller.stageClose(); stage.close(); }); stage.setTitle("NewYearCard"); stage.resizableProperty().setValue(Boolean.FALSE); stage.show(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555
|
package jp.yucchi.newyearcard; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.ResourceBundle; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.property.SimpleObjectProperty; import javafx.concurrent.Task; import static javafx.concurrent.Worker.State.RUNNING; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.Initializable; import javafx.geometry.Point2D; import javafx.print.Collation; import javafx.print.PageLayout; import javafx.print.PageOrientation; import javafx.print.Paper; import javafx.print.PaperSource; import javafx.print.PrintColor; import javafx.print.PrintQuality; import javafx.print.Printer; import javafx.print.PrinterJob; import javafx.scene.Cursor; import javafx.scene.Group; import javafx.scene.Node; import javafx.scene.control.Button; import javafx.scene.control.CheckBox; import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.control.ToggleButton; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; import javafx.scene.text.Text; import javafx.scene.transform.Scale; import javafx.scene.transform.Transform; import javafx.scene.transform.Translate; import javafx.stage.FileChooser; /** * * @author Yucchi */ public class FXMLDocumentController implements Initializable { private static final boolean DEBUG = true; private static final boolean CANCEL_DEBUG = true; private final ExecutorService executorService = Executors.newSingleThreadExecutor(); private Rectangle printAreaRectangle; @FXML private ResourceBundle resources; @FXML private URL location; @FXML private AnchorPane anchorPane; @FXML private Label gashiLabel; @FXML private TextField gashiTextField; @FXML private Label statusLabel; @FXML private Label jobStatusLabel; @FXML private Button printButton; @FXML private Button cancelButton; @FXML private CheckBox scalingCheckBox; @FXML private Button openImageButton; @FXML private ToggleButton printerInfoToggleButton; @FXML private Group greeting; @FXML private StackPane stackPane; @FXML private ImageView imageView; @FXML private Text gashiText; @FXML private TextArea printerTextArea; private Task<Boolean> printTask; private Printer targetPrinter; private final BooleanProperty scaling = new SimpleBooleanProperty(true); @FXML void initialize() { assert anchorPane != null : "fx:id=\"anchorPane\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert gashiLabel != null : "fx:id=\"gashiLabel\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert gashiTextField != null : "fx:id=\"gashiTextField\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert statusLabel != null : "fx:id=\"statusLabel\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert jobStatusLabel != null : "fx:id=\"jobStatusLabel\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert printButton != null : "fx:id=\"printButton\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert cancelButton != null : "fx:id=\"cancelButton\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert scalingCheckBox != null : "fx:id=\"scalingCheckBox\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert openImageButton != null : "fx:id=\"openImageButton\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert printerInfoToggleButton != null : "fx:id=\"printerInfoToggleButton\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert greeting != null : "fx:id=\"greeting\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert stackPane != null : "fx:id=\"stackPane\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert imageView != null : "fx:id=\"imageView\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert gashiText != null : "fx:id=\"gashiText\" was not injected: check your FXML file 'FXMLDocument.fxml'."; assert printerTextArea != null : "fx:id=\"printerTextArea\" was not injected: check your FXML file 'FXMLDocument.fxml'."; } @FXML private void handleOpenImageButtonAction(ActionEvent event) { var fileChooser = new FileChooser(); fileChooser.setTitle("Change Image"); fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter( "All Images", "*.jpg", "*.jpeg", "*.png", "*.bmp", "*.gif")); var image = fileChooser.showOpenDialog(anchorPane.getScene().getWindow()); if (image != null) { try { imageView.setImage(new Image(image.toURI().toURL().toString(), 1_000, 675, true, true, false)); } catch (MalformedURLException ex) { Logger.getLogger(FXMLDocumentController.class.getName()).log(Level.SEVERE, null, ex); } } } @FXML private void handlePrinterInfoToggleButtonAction(ActionEvent event) { if (printerInfoToggleButton.isSelected()) { var printerInformation = new PrinterInfomation(); var printerDetails = printerInformation.showPrinterDetails(); printerTextArea.setText(printerDetails); printerTextArea.setVisible(true); printerInfoToggleButton.setText("Hide Info"); } else { printerTextArea.clear(); printerTextArea.setVisible(false); printerInfoToggleButton.setText("Printer Info"); } } @FXML private void handlePrintButtonAction(ActionEvent event) { printGreeting(greeting); } @FXML private void handleCancelButtonAction(ActionEvent event) { if (printTask != null) { printTask.cancel(); } } @Override public void initialize(URL url, ResourceBundle rb) { scaling.bind(scalingCheckBox.selectedProperty()); // デフォルトプリンター var defaultPrinter = Printer.getDefaultPrinter(); // プリンター選択 (デフォルトでないプリンターを選択しています) targetPrinter = Printer.getAllPrinters() .stream() .filter(p -> p.getName().matches("EP-806A")) .findFirst() .orElseGet(() -> { return defaultPrinter; }); // イメージファイルを設定 imageView.setImage(new Image(this.getClass() .getResource("resources/ChristmasCard.jpg") .toExternalForm(), 1_000, 675, true, true, false)); // 賀詞テキストのバインドとテキスト設定 gashiText.textProperty().bind(gashiTextField.textProperty()); gashiText.setFill(Color.LIGHTPINK); gashiText.setStrokeWidth(2); gashiText.setStroke(Color.RED); // 賀詞テキストをドラッグ可能とする var anchor = new SimpleObjectProperty<>(new Point2D(gashiText.getTranslateX(), gashiText.getTranslateY())); gashiText.setOnMousePressed(me -> { gashiText.setCursor(Cursor.MOVE); double x = me.getSceneX() - anchor.get().getX(); double y = me.getSceneY() - anchor.get().getY(); anchor.set(new Point2D(x, y)); }); gashiText.setOnMouseDragged(me -> { double x = me.getSceneX() - anchor.get().getX(); double y = me.getSceneY() - anchor.get().getY(); gashiText.setTranslateX(x); gashiText.setTranslateY(y); }); gashiText.setOnMouseReleased(me -> { gashiText.setCursor(Cursor.HAND); double x = me.getSceneX() - anchor.get().getX(); double y = me.getSceneY() - anchor.get().getY(); anchor.set(new Point2D(x, y)); }); gashiText.setOnMouseEntered(me -> { gashiText.setCursor(Cursor.HAND); }); } private void printGreeting(Group greeting) { printTask = new Task<>() { private PageLayout pageLayout; @Override public Boolean call() { Platform.runLater(() -> { jobStatusLabel.setText("Create a printer job."); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // プリンタージョブを作成 // // デフォルトプリンターを使ってプリンタージョブを作成 // var job = PrinterJob.createPrinterJob(); // プリンターを指定してプリンタージョブを作成 var job = PrinterJob.createPrinterJob(targetPrinter); // ジョブステータス確認用 System.out.println("JobStatus: " + job.jobStatusProperty().asString().get()); Platform.runLater(() -> { jobStatusLabel.setText(job.jobStatusProperty().asString().get()); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // プリンタージョブステータス job.jobStatusProperty().addListener(e -> { System.out.println("JobStatus: " + job.jobStatusProperty().asString().get()); Platform.runLater(() -> { jobStatusLabel.setText(job.jobStatusProperty().asString().get()); }); // ジョブステータスラベル確認用スリープ if (DEBUG) { jobSleep(); } // JobStatus が PRINTING の状態で強制的にキャンセル if (CANCEL_DEBUG && PrinterJob.JobStatus.PRINTING == job.jobStatusProperty().get()) { System.out.println("JobStatus が PRINTING の状態で Cancel"); job.cancelJob(); // JobStatus: CANCELED -> JobStatus: ERROR となる。 printTask.cancel(); } }); // ジョブ設定 var jobSettings = job.getJobSettings(); // ジョブの名前 jobSettings.setJobName("Greeting"); // 自動ではがきに印刷 jobSettings.setPaperSource(PaperSource.AUTOMATIC); // // この設定でもはがきに印刷 // jobSettings.setPaperSource(PaperSource.TOP); // ページレイアウト設定 pageLayout = targetPrinter.createPageLayout(Paper.JAPANESE_POSTCARD, PageOrientation.LANDSCAPE, Printer.MarginType.HARDWARE_MINIMUM); // ジョブにページレイアウトを設定 jobSettings.setPageLayout(pageLayout); // 印刷カラーを設定 jobSettings.setPrintColor(PrintColor.COLOR); // 印刷品質を設定 jobSettings.setPrintQuality(PrintQuality.NORMAL); // 印刷部数を設定 jobSettings.setCopies(1); // 部単位で印刷設定 jobSettings.setCollation(Collation.UNCOLLATED); // Single page printing ///////////////////////////////////////////// if (scaling.get()) { if (job != null) { // 印刷対象ノードを印刷範囲内にスケーリング fit2PrintableSize(); // // 用紙設定ダイアログ // var nativePageSetup = job.showPageSetupDialog(anchorPane.getScene().getWindow()); // // 用紙設定ダイアログキャンセル // if (!nativePageSetup) { // System.out.println("PageSetup has been canceled."); // job.cancelJob(); // printTask.cancel(); // } // // 印刷ダイアログ // var nativeJob = job.showPrintDialog(anchorPane.getScene().getWindow()); // // 印刷ダイアログでキャンセルした場合の処理 // if (!nativeJob) { // System.out.println("Job has been canceled."); // job.cancelJob(); // printTask.cancel(); // } // タスクのキャンセル処理 if (isCancelled()) { if (PrinterJob.JobStatus.PRINTING == job.jobStatusProperty().get() || PrinterJob.JobStatus.NOT_STARTED == job.jobStatusProperty().get()) { // プリンタージョブをキャンセル job.cancelJob(); System |