- 投稿日:2020-02-08T22:18:12+09:00
ダウンロードできてたCSVファイルが、突然ページ表示されるようになった。
突貫工事で、複数人が入力したデータを、CSV形式でファイルダウンロードするWebアプリケーションをリリースしました。
利用したのは以下のOSSです。
- Spring Boot 2.2.4.RELEASE
- 組み込みのTomcat 9.0.30
- Chrome 79.0.3945.130
運用開始当日は特に問題がなく、退社して友達と飲んでたら、何やら自分のスマホが騒がしい。。。着信に出てみると「ダウンロードができない」との連絡が。詳細を確認すると「3時間前まではダウンロードを行うとCSVファイルが出力されていたのに、突然、ブラウザ上にCSVが表示されるようになった」とのこと。
友人に誤り、飲みを切り上げ、原因の調査を開始。一応、こう修正すれば動くというが判ったので、まずはサービス優先でリリースしました。しかし、原理が不明なままなので、継続調査を実施しました。
修正の内容
以下は、今回の問題となったダウンロード処理の簡略化したコードと、修正後のコードです。
- 修正前
@PostMapping fun export(response: HttpServletResponse) { /** * CSVをresponse.outputStreamに書き込む。 */ response.contentType = "text/csv" response.setHeader("Content-Disposition", "attachment; filename=export.csv") response.outputStream.flush() }
- 修正後
@PostMapping fun export(response: HttpServletResponse) { response.contentType = "text/csv" response.setHeader("Content-Disposition", "attachment; filename=export.csv") /** * CSVをresponse.outputStreamに書き込む。 */ response.outputStream.flush() }要はContent-TypeとContent-Dispositionが、outputStreamへの書き込みの後か前かです。
その後の調査ではContent-Dispositionが前にあれば、ファイルダウンロードされることが判りました。なぜoutputStreamへの書き込みの後にContent-Dispositionの設定を行ったかと言えば、たまたま参考にした記事がこうだったのと、ヘッダの設定とボディ(ストリーム)への書き込みは独立した処理だと思っていたという理由です。
原因調査
ただ、outputStreamへの書き込みの後にContent-Dispositionを設定しても、しばらくは正常にファイルダウンロードができていたので、CSV出力のサイズが影響していることが推測できます。そこで画面にデータサイズを入力し、そのサイズのCSV出力を行う再現プログラムを作成してみました。
@PostMapping fun export(@RequestParam size: Int, response: HttpServletResponse) { (1..size).forEach { response.outputStream.print("a") } response.contentType = "text/csv" response.setHeader("Content-Disposition", "attachment; filename=export.csv") response.outputStream.flush() }すると、サイズが8KByteだとChromeの開発者ツールで確認した応答ヘッダは
Connection: keep-alive Content-Disposition: attachment; filename=export.csv Content-Type: text/csv Date: Sat, 08 Feb 2020 10:26:07 GMT Keep-Alive: timeout=60 Transfer-Encoding: chunkedでしたが9KByteだと
Connection: keep-alive Date: Sat, 08 Feb 2020 10:27:48 GMT Keep-Alive: timeout=60 Transfer-Encoding: chunkedのようにContent-Dispositionが消えてしまうではありませんか!突き詰めて行くと8191byteだと前者で、8192byteだと後者になります。
2の累乗である8192が設定された何かを超えたことが影響していると思います。考察
おそらくは以下のようなことがTomcatで発生しているのだと考えています。
- HTTPボディへの出力が8191byte以下ならHTTPヘッダと一緒にデータを送り出せる。
- けど、HTTPボディへの出力が8192byte以上になると、そこまでのHTTPヘッダ情報を送信してから、8191byte単位にHTTPボディを送信する。
- なのでHTTPボディへの出力後に、HTTPヘッダの設定を行っても、既にHTTPヘッダが送信済みなために、その設定は破棄されてしまった。
原因調査2
そこで手っ取り早くtcpdumpでパケットを監視しながら、デバッガで8190byteから1byteずつ書き込みをステップ実行してみたところ、8192byteを書き込んだところでブラウザにHTTPヘッダとボディが送信されていることが確認できました。
ついでに、8192byteを書き込時にステップインしていくと、以下のコードがありました。
org.apache.catalina.connector.OutputBufferpublic void append(byte src[], int off, int len) throws IOException { if (bb.remaining() == 0) { appendByteArray(src, off, len); } else { int n = transfer(src, off, len, bb); len = len - n; off = off + n; if (isFull(bb)) { flushByteBuffer(); appendByteArray(src, off, len); } } }bbはByteBuffer型のメンバ変数で、appendByteArrayおよびtransferメソッド内でbbに送信データを蓄積しています。
isFullメソッドではbbに蓄積されたサイズが許容量に達するとtrueになり、flushByteBufferメソッドでブラウザへ送信します。flushByteBufferメソッドの中を更に追うと、
org.apache.coyote.http11.Http11OutputBuffer@Override public int doWrite(ByteBuffer chunk) throws IOException { if (!response.isCommitted()) { // Send the connector a request for commit. The connector should // then validate the headers, send them (using sendHeaders) and // set the filters accordingly. response.action(ActionCode.COMMIT, null); } if (lastActiveFilter == -1) { return outputStreamOutputBuffer.doWrite(chunk); } else { return activeFilters[lastActiveFilter].doWrite(chunk); } }コミット済みでなければ、コミット処理を行う中でHTTPヘッダを送信して、コミット済みフラグをセットします。
それ以降は、HTTPボディの書き込みが行われます。考察2
ということで安易に作ってしまったコードが、たまたま意図した動作をしていたから気づかずにリリースしてしまいましたが、さまざまなサイズの処理を行うTomcat側の苦労を考えれば当然の実装で、そこにわざわざハマりにいってしまったというお話でした。
(2020/2/8) 投稿。
(2020/2/9) 原因調査で事象が前後で切り替わるサイズの誤記を修正。原因調査2と考察を追記。
- 投稿日:2020-02-08T15:02:01+09:00
SpringBootアプリケーションの運用を楽にするための機能、SpringBoot Actuatorのご紹介
急いでいる人へのまとめ
- SpringBoot Actuatorを使うと各種情報が簡単にとれる
- 最小限の機能を使うには、依存ライブラリの追加と設定のみ
- システム稼働情報の参照、変更ができるようになるのでセキュリティには注意
システム運用で必要な機能とは?
システムは作って終わりではなく、安定稼働させたり、どれぐらい使われているかなどの運用も大事ですよね。
DevOpsや最近のアジャイルでも、如何にリリース後のフィードバックを受けて次の戦略を取っていくかを大事にしています。フィードバックとは利用者から、「こうしてほしい」「こんな機能がほしい、いらない」だけではなく、システム運用者から「この機能が使われてないんだけど」とか「この機能が急に使われ始めた」という提供者側からの気づきも大事になります。後者の今のシステムで必要な情報を取るための機能がSpringBootではSpringBoot Actuatorという機能が用意されていますので、そのご紹介となります。
SpringBoot Actuatorとは?
SpringBootの拡張機能で、各種情報が簡単に取れます。
代表的なものは
- アプリが稼働しているか?(ヘルスチェック)
- JVMの設定内容
- アプリケーションの設定内容
といった情報です。詳細は以下リンク先に記載があります。
https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html
How to Get Started
簡単なので、Spring Initializr を使います。
SpringBootのバージョンは今回は2.3.0 M1を使います。
DependenciesにSpring Webと、Spring Boot Actuatorを選択しておきます。
設定が終わったらGenarateを押下してソースコードの雛形をダウンロードします。
終わったら、unzipして、適当なIDEで開きましょう。pom.xmlを見ると、SpringBoot Actuatorに関する依存ライブラリが記載されていることがわかります。
pom.xml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>この機能で習得できる情報は、脆弱性を突いてくる攻撃者にとっても有益な情報となり得るため、デフォルトでは機能OFFになっています。有効にするためにアプリケーションの設定を変更します。
application.propertiesmanagement.endpoints.enabled-by-default=true management.endpoints.web.exposure.include=*management.endpoints.enabled-by-defaultはデフォルトで情報取得可否を設定するもの、
management.endpoints.web.exposure.includeはRESTエンドポイントを有効にする設定です。
前者はtrueとしてデフォルト取得可能に、後者は全てのエンドポイントに有効にするという意味で* に設定します。設定終わったら、ビルド&起動します。通常のSpringBootのアプリ通りで大丈夫です。
ビルドは
mvn package
で、起動はjava -jar target/demo-0.0.1-SNAPSHOT.jar
でOKです。起動したら、
http://localhost:8080/actuator/
にアクセスしてみます。
すると、エンドポイント一覧がJSONで取得できます。curl http://localhost:8080/actuator | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 1659 0 1659 0 0 162k 0 --:--:-- --:--:-- --:--:-- 162k { "_links": { "self": { "href": "http://localhost:8080/actuator", "templated": false }, "beans": { "href": "http://localhost:8080/actuator/beans", "templated": false }, "caches-cache": { "href": "http://localhost:8080/actuator/caches/{cache}", "templated": true }, "caches": { "href": "http://localhost:8080/actuator/caches", "templated": false }, "health-path": { "href": "http://localhost:8080/actuator/health/{*path}", "templated": true }, "health": { "href": "http://localhost:8080/actuator/health", "templated": false }, "info": { "href": "http://localhost:8080/actuator/info", "templated": false }, "conditions": { "href": "http://localhost:8080/actuator/conditions", "templated": false }, "shutdown": { "href": "http://localhost:8080/actuator/shutdown", "templated": false }, "configprops": { "href": "http://localhost:8080/actuator/configprops", "templated": false }, "env": { "href": "http://localhost:8080/actuator/env", "templated": false }, "env-toMatch": { "href": "http://localhost:8080/actuator/env/{toMatch}", "templated": true }, "loggers-name": { "href": "http://localhost:8080/actuator/loggers/{name}", "templated": true }, "loggers": { "href": "http://localhost:8080/actuator/loggers", "templated": false }, "heapdump": { "href": "http://localhost:8080/actuator/heapdump", "templated": false }, "threaddump": { "href": "http://localhost:8080/actuator/threaddump", "templated": false }, "metrics-requiredMetricName": { "href": "http://localhost:8080/actuator/metrics/{requiredMetricName}", "templated": true }, "metrics": { "href": "http://localhost:8080/actuator/metrics", "templated": false }, "scheduledtasks": { "href": "http://localhost:8080/actuator/scheduledtasks", "templated": false }, "mappings": { "href": "http://localhost:8080/actuator/mappings", "templated": false } } }量が多いので、全て解説はしませんが、例えば、healthであればヘルスチェック機能として、このエンドポイントでアプリの生死状態が取得できるといったことを示しています。
curl http://localhost:8080/actuator/health | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 15 0 15 0 0 35 0 --:--:-- --:--:-- --:--:-- 35 { "status": "UP" }もう一つ、環境情報を取得するには、envというエンドポイントで公開されています。
curl http://localhost:8080/actuator/env | jq % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 12493 0 12493 0 0 154k 0 --:--:-- --:--:-- --:--:-- 154k { "activeProfiles": [], "propertySources": [ { "name": "server.ports", "properties": { "local.server.port": { "value": 8080 } } }, { "name": "servletContextInitParams", "properties": {} }, { "name": "systemProperties", "properties": { "java.runtime.name": { "value": "OpenJDK Runtime Environment" }, "java.protocol.handler.pkgs": { "value": "org.springframework.boot.loader" }, "sun.boot.library.path": { "value": "/Users/user/.sdkman/candidates/java/8.0.212-amzn/jre/lib" }, "java.vm.version": { "value": "25.212-b04" }, "gopherProxySet": { "value": "false" }, "java.vm.vendor": { "value": "Amazon.com Inc." }, "java.vendor.url": { "value": "https://aws.amazon.com/corretto/" }, "path.separator": { "value": ":" }, "java.vm.name": { "value": "OpenJDK 64-Bit Server VM" }, "file.encoding.pkg": { "value": "sun.io" }, "user.country": { "value": "JP" }, "sun.java.launcher": { "value": "SUN_STANDARD" }, "sun.os.patch.level": { "value": "unknown" }, "PID": { "value": "59317" }, "java.vm.specification.name": { "value": "Java Virtual Machine Specification" }, "user.dir": { "value": "/Users/user/oper/spring/spring-docker" }, "java.runtime.version": { "value": "1.8.0_212-b04" }, "java.awt.graphicsenv": { "value": "sun.awt.CGraphicsEnvironment" }, "java.endorsed.dirs": { "value": "/Users/user/.sdkman/candidates/java/8.0.212-amzn/jre/lib/endorsed" }, "os.arch": { "value": "x86_64" }, "java.io.tmpdir": { "value": "/var/folders/1m/v_pnk3kd3hj0558d1z3nsnzh0000gn/T/" }, "line.separator": { "value": "\n" }, "java.vm.specification.vendor": { "value": "Oracle Corporation" }, "os.name": { "value": "Mac OS X" }, "sun.jnu.encoding": { "value": "UTF-8" }, 以下省略.こちらを見ると、OSが何か、Java実装やバージョン、文字エンコーディングは何か?などの情報が取れます。
その他にもBean登録されているものの一覧取得ができるBeansや、設定されている内容を取得できるconfigpropsなどが普段のトラブルシュート時の環境確認としては便利です。
応用編
これまではデフォルトで用意されているEndpointの紹介にとどめましたが、カスタマイズも可能です。アプリで特別に返したい内容、があればエンドポイントを作ってJSONで簡単に返すことができます。
ここでの例は割愛しますので、やりたい方は公式ガイドを見てください。まとめ
いかがでしたでしょうか?DevOpsで必要などの機能がどれだけ使われているかの稼働統計情報取得のやり方までは今回辿り着けませんでしたが、環境情報取得までは簡単に出来そうだ、と思ってもらえたら幸いです。
みなさんお手持ちの(?)SpringBootアプリ、SpringBoot Actuator未導入の際は、是非入れてみてください。
- 投稿日:2020-02-08T14:49:03+09:00
ID を格納する変数を int/long でなく ID 型にする (Java)
概要
ID を持ちまわる際、通常なら int, long, String などに格納するわけだが、これだと下記のような場合にちぐはぐになって分かりづらくなる可能性がある。
public someMethod(int hogeId, int fugaId) { ... }これをこうできればケアレスミスを防げる。(ちぐはぐにするとIDEがエラーとなって教えてくれる。)
public someMethod(HogeId hogeId, FugaId fugaId) { ... }注意ポイント
これは分かりやすくなる一方で、気を付けるポイントがある。
- Jackson で JSON に/から変換可能であること
- MyBatis でパラメータ/結果として扱えること
- Spring MVC の URL パラメータや
@RestController
の 戻り値 として扱えること- Id のクラスを沢山作ることになるので個々には面倒な記載が要らないこと(親クラスに実装もたせる)
- パフォーマンスが損なわれないこと
結論
下記で実現できる。
PiyoId.javapackage your.package; import your.package.LongId; import lombok.EqualsAndHashCode; @EqualsAndHashCode(callSuper = true) class PiyoId extends LongId { public PiyoId(String id) { super(id); } public PiyoId(long id) { super(id); } }LongId.javapackage your.package; import com.fasterxml.jackson.annotation.JsonValue; import lombok.EqualsAndHashCode; @EqualsAndHashCode public abstract class LongId { private final long id; public LongId(String id) { this.id = Long.parseLong(id); } public LongIdBase(long id) { this.id = id; } @JsonValue public long getId() { return this.id; } public String toString() { return this.getClass().getSimpleName() + "(" + this.id + ")"; } }LongIdTypeHandler.javapackage your.package; import java.sql.CallableStatement; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import your.package.LongIdBase; import org.apache.ibatis.type.BaseTypeHandler; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.MappedTypes; @MappedTypes(value = LongId.class) public class LongIdTypeHandler<E extends LongId> extends BaseTypeHandler<E> { // private final Class<E> type; public LongIdTypeHandler(Class<E> type) { if (type == null) throw new IllegalArgumentException("Type argument cannot be null"); // this.type = type; // ここで受け取れるのは LongId クラスであり、具象クラスではないので、type を保持しても実クラスを生成するのには使えない。 } private E createInstance() { throw new UnsupportedOperationException("Should specify long type instead of the subclass of LongId in the <constructor> of the mapper xml."); // try { // // Cannot know the concrete class... // return type.getDeclaredConstructor(Long.class).newInstance(id); // } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { // throw new RuntimeException(e); // } } @Override public void setNonNullParameter(PreparedStatement ps, int num, E parameter, JdbcType jdbcType) throws SQLException { ps.setLong(num, parameter.getId()); } @Override public E getNullableResult(ResultSet rs, String columnName) throws SQLException { return createInstance(); } @Override public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return createInstance(); } @Override public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return createInstance(); } }詳細
Jackson 対応
シームレスに変換できるためには次のいずれかである必要があります。
JSON -> Object への変換
下記のいずれか:
1. デフォルトコンストラクタ+setters
2.@JsonCreator
つきコンストラクタ
3. 合致する引数付きコンストラクタObject -> JSON への変換
下記のいずれか:
4. getters から判断
5.@JsonValue
つきのメソッド実行結論
3と5に対応させる
MyBatis 対応
Object を MyBatisパラメータ で渡せるように
下記の両方に対応する必要がある:
1. IDクラスがパラメータである場合
parameterType="com.~.PiyoId"
と指定して#{id}
という記述で取り出せるようにしたい。- これは
PiyoId
にlong getId()
というメソッドが定義されていれば良い。2. IDクラスを文字するクラスを引数で受け取る場合
parameterType="com.~.Piyo"
と指定して#{id}
という記述で取り出せるようにしたい。- これには TypeHandler の定義が必要。ただし、
setNonNullParameter
のみ実装する。getNullableResult
は具象クラスが解らず new できないので使えない。 MyBatisのResultMapでObject化できるように- DBから INT で来たパラメータが PiyoId 型として扱えるようにしたい。
- これは long や int を引数に持ったコンストラクタがあれば良い。
- もしも
<constractor>
句でIDクラスをもったオブジェクトを生成するなら下記の点に注意(つまりコンストラクタ側も long で受け取るようにする):これじゃなく<resultMap id="Piyo" type="yoru.package.Piyo"> <constructor> <idArg column="id" javaType="yoru.package.PiyoId"/> <arg column="xid" javaType="String"/> </constructor> </resultMap>これ<resultMap id="Piyo" type="yoru.package.Piyo"> <constructor> <idArg column="id" javaType="_long"/> <arg column="xid" javaType="String"/> </constructor> </resultMap>Spring 対応
Spring MVC の
Controller
のパラメータ
- 下記のケースのいずれであっても 123 のような数値で来たパラメータを
PiyoId
型として受け取りたい。
@PathVariable("piyoId") PiyoId piyoId
とした場合@RequestBody PiyoForm form
や@ModelAttribute PiyoForm form
として受けとる場合で、form
の中でPiyoId
型で受け取る場合- 通常ならエラー:
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'your.package.PiyoId': no matching editors or conversion strategy found
- 結論: IDクラスに String を受け取るコンストラクタがあればできる。
Spring MVC の
@RestController
の戻り値
PiyoId
が混ざっていたら数値として JSON 化したい。- 結論: これは前述の Jackson 対応していれば大丈夫。
パフォーマンスが損なわれないこと
- ID 作るのに new が必要になったり、ハッシュ値計算が複雑になるケースがあったが、1000万回回しても両者ともに 1.5 秒程度でほぼ差はなし。long 型だと Boxing の負荷があるから結局トントン?
検証コード// 後で書く
- 投稿日:2020-02-08T14:08:31+09:00
Spring boot サンプルプログラムでポートを使えなくなった話
ポートに誰かが居座っている
Identify and stop the process that's listening on port 8080 or configure this application to listen on another port.なんだこいつは。一回目は起動していたのに。
文脈か察するに、ポートがすでに使われているみたいだ。本当かどうか確認してみる
cmdから
netstat -anで確認してみる。
確かに、8080がLISTENINGになっている。
これは別のアプリケーションが独占しているという認識でいいのだろうか。いったん前提をそれとして作業を、
1. 動いているアプリを突き止めて終了する(仮定:多分前動かしたjavaがまだ居座っている?)
2. 1の仮定が正しい場合、停止した後にちゃんとどいてもらうような設定をする
の2つに絞って作業をしてみようかな。動いているアプリを突き止めて終了する
今度はcmdから誰が居座っているか、ちゃんと確認する。
netstat -nao | find "8080"
来た・・・‼3688・・・‼こいつが・・・諸悪の根源・・・‼
ここでkillコマンドでバッサリと切ってしまうのが最も早いみたいですが、タスクマネージャから正体を確認してみます。cmd上から確認できる方法とかあるんですかね。まあいっか。
余計なことはしすぎる方がいいってスピッツから教わりました。
案の定javaさんでしたね~
タスクから終了させまつ
また再起動後、同じ現象がおきるかな・・・と思いきや、スムーズに再起動に成功したので、これでいいのかな?いったん解決ということで
またポートがらみで問題が起きたら書き起こします
- 投稿日:2020-02-08T02:21:25+09:00
Resultsetのnext()を、次があるかの判定メソッドと勘違いしていた件
事象
とあるコードで、SELECTクエリを投げ、
その取得結果をResultSet型の変数rsetに格納している箇所がありました。コード読解のためrsetの要素数を確認したいと思ったので、
以下のコードを途中に追記して実行しました。example.javaint testIndex = 0; while(rset.next()){ System.out.println("rsetの要素数を確認したい"); System.out.println(++testIndex); }そうしたら、(それまでは正常終了していたのですが、)
その後ろの処理で、以下の例外が発生しました。java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
読解のはずが壊してしまっていた
おかしいなと思って見てみたら、next()の理解が誤っていました。
現在の位置から次の1行へ進んでカーソルを当てる機能もあるのですね。上記のコードで、最後の要素にカーソルがあたったままループを抜けたので、
後続のrsetで、次にカーソルがあたる要素がなく、
上記例外発生になってしまっていました。ResultSetのnext()に気を付けたいと同時に、
読解のために一時追記したコードが悪さをすることもあるのだということを学びました。参考にさせて頂いた記事
- 投稿日:2020-02-08T00:14:45+09:00
Eclipseのヒープサイズを増加させる(メモリ不足エラーを解消する)
はじめに
Eclipseのプラグインを追加するとき、メモリ不足エラーと表示され実行できなかった。
そこでヒープサイズを増加させ、問題を解決した。環境
Eclipse Photon Release 4.8.0
試みたが失敗したこと
Eclipseのインストール先にある"eclipse.ini"を書き換える。
-startup plugins/org.eclipse.equinox.launcher_1.3.0.v20120522-1813.jar --launcher.library plugins/org.eclipse.equinox.launcher.win32.win32.x86_1.1.200.v20120913-144807 -product org.eclipse.epp.package.jee.product --launcher.defaultAction openFile --launcher.XXMaxPermSize 256M -vmargs -Dosgi.requiredJavaVersion=1.5 -Dhelp.lucene.tokenizer=standard -Xms256m -Xmx512m -Xverify:none -javaagent:dropins/MergeDoc/eclipse/plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar以上のコード内の
-Xms256m
-Xmx512mの2箇所を書き換えるようなのだが、バージョンの違いのためか、
ファイル自体はあるものの内容が記載のものと一致せず、書き換え以前の問題。試みて成功したこと
友人の助言に従い、
自身の"eclipse.ini"のコードの末尾に以下の内容を追加した。-vmargs -Xms512m -Xmx1024m"eclipse.ini"全体のコードとしては以下のようになる。
-product org.eclipse.epp.package.jee.product --launcher.defaultAction openFile --launcher.appendVmargs -vmargs -Dosgi.requiredJavaVersion=1.8 -XX:+UseG1GC -XX:+UseStringDeduplication --add-modules=ALL-SYSTEM -Dosgi.dataAreaRequiresExplicitInit=true -Xverify:none -javaagent:dropins/MergeDoc/eclipse/plugins/jp.sourceforge.mergedoc.pleiades/pleiades.jar -vmargs -Xms512m -Xmx1024mこれによってヒープサイズが確保され、目的のプラグインを追加できた。