tags: java, troubleshooting, monitor,jvm
Одним предложением: оказалось, что инструменты командной строки, поставляемые с jdk, настолько просты в использовании, что в этой статье мы подробно расскажем о них.
1. Введение
Самый удобный способ мониторинга Java-приложений — это прямое использование готовых инструментов, предоставляемых jdk. контролировать Java-приложения и диагностировать проблемы, поэтому такие инструменты являются важным средством мониторинга Java-приложений. Это также базовый навык, которым должен овладеть Java-разработчик.
2 Общие инструменты командной строки для мониторинга
Как правило, часто используемые инструменты командной строки включаютjps
,jinfo
,jmap
,jstack
,jstat
, эти инструменты находятся вJAVA_HOME/bin/
В каталоге краткое описание выглядит следующим образом:
-
jps
Просмотр идентификатора процесса Java -
jinfo
Просмотр и настройка параметров виртуальной машины -
jmap
Просмотр использования кучи и создание моментальных снимков кучи -
jstack
Просмотр состояния выполнения потока и создание моментального снимка потока -
jstat
Отображает текущие данные, такие как загрузка классов, память, сборка мусора и т. д. в процессе.
С помощью этих инструментов вы можете в основном понять статус изменения памяти Java-приложений, статус выполнения потоков и другую информацию, а затем предоставить основу для мониторинга приложений и диагностики проблем. Использование этих инструментов будет подробно объяснено ниже с примерами Пример кода, использованный в этой статьеjava-monitor-example
загружено вмой гитхаб,адрес:https://github.com/mianshenglee
.
3 Инструмент обработки запросовjps
3.1 jps
инструкция
Первый шаг для мониторинга Java-приложения — узнать, каким процессом является приложение и каковы его рабочие параметры.jps
Это инструмент, который может запрашивать процесс. Учащиеся, знакомые с Linux, вероятно, знают, как использовать процесс запросов.ps -ef|grep java
Такая команда jps также похожа, но она не использует поиск по имени, а находит все процессы Java, запущенные в текущем jdk, и ищет только процессы Java текущего пользователя, а не все процессы в текущей системе.
3.2 jps
использовать
В качестве инструмента командной строки вы можете использовать-help
Справка по просмотру параметров, вы также можете обратиться кофициальная документация:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jps.html
,следующее:
[root@test bin]# jps -help
usage: jps [-help]
jps [-q] [-mlvV] [<hostid>]
Definitions:
<hostid>: <hostname>[:<port>]
参数解释:
-q:只显示java进程的pid
-m:输出传递给main方法的参数,在嵌入式jvm上可能是null
-l:输出应用程序main class的完整package名 或者 应用程序的jar文件完整路径名
-v:输出传递给JVM的参数
Пример проектаjava-monitor-example
Для запуска на Linux-машине используйтеjps
Можно вывести следующую информацию:
- печатать только идентификатор процесса
[root@test bin]# jps -q
13680
14214
- Полное имя программы вывода и параметры JVM
[root@test bin]# jps -lv
13680 java-monitor-example-0.0.1-SNAPSHOT.jar -Xms128m -Xmx128m -Dserver.port=8083
14289 sun.tools.jps.Jps -Denv.class.path=.:/opt/jdk8/lib:/opt/jdk8/jre/lib -Dapplication.home=/opt/jdk8
В выходном контентеjava-monitor-example-0.0.1-SNAPSHOT.jar
да-l
полное название выхода,-Xms128m -Xmx128m -Dserver.port=8083
— это параметр, передаваемый JVM.
- Используйте команду в сценарии оболочки, чтобы получить идентификатор процесса Java и использовать его как переменную
JAVA_HOME="/opt/jdk8"
APP_MAINCLASS=java-monitor-example
#初始化psid变量(全局)
psid=0
#查看进程ID函数
checkpid() {
javaps=`$JAVA_HOME/bin/jps -l | grep $APP_MAINCLASS`
if [ -n "$javaps" ]; then
psid=`echo $javaps | awk '{print $1}'`
else
psid=0
fi
}
#调用函数后通过psid进行业务逻辑操作,如根据进程id杀进程
checkpid
echo "(pid=$psid)"
Приведенный выше сценарий больше подходит для обслуживающего и обслуживающего персонала, чтобы открывать и закрывать приложение, автоматически получать идентификатор процесса Java, а затем определять, запущена ли программа (запускается) в соответствии с идентификатором, или закрыть приложение (kill -9
).
4 Инструмент информации о конфигурацииjinfo
4.1 jinfo
инструкция
Знать номер процесса java-приложения — это первый шаг.В предыдущей статье «Мониторинг java-приложения (2) — секрет java-команды» мы уже знали, что есть много параметров запуска java. Перед мониторингом java-приложения вам нужно узнать его детали.Каковы параметры запуска. Затем вам нужно использоватьjinfo
инструмент.jinfo
Системные параметры и параметры JVM приложений JAVA могут быть выведены. jinfo также может изменять некоторые параметры виртуальной машины, которые можно настроить во время работы.Многие рабочие параметры не могут быть изменены.Если возникает исключение «нельзя изменить», это означает, что его нельзя изменить. Однако в официальном документе указано, что эту команду больше нельзя использовать в последующих версиях, а текущую JDK8 по-прежнему можно использовать.
4.2 jinfo
использовать
пройти через-help
Справка по просмотру параметров, вы также можете обратиться кjinfo
Описание официальной документации:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jinfo.html
,следующее:
[root@test bin]# jinfo -help
Usage:
jinfo [option] <pid>
(to connect to running process)
where <option> is one of:
-flag <name> to print the value of the named VM flag
-flag [+|-]<name> to enable or disable the named VM flag
-flag <name>=<value> to set the named VM flag to the given value
-flags to print VM flags
-sysprops to print Java system properties
<no option> to print both of the above
-h | -help to print this help message
использоватьjps
После получения идентификатора процесса приложения (PID примера — 13680), если вы напрямуюjps <pid>
Будут выведены все системные параметры и параметры JVM, остальные параметры описаны вhelp
Тоже четко сказано. Следующее объединено с примером кодаjava-monitor-example
Давай попрактикуемся:
- Получить начальное значение кучи приложения Java
[root@test bin]# jinfo -flag InitialHeapSize 13680
-XX:InitialHeapSize=134217728
- Посмотреть все параметры JVM
[root@test bin]# jinfo -flags 13680
Attaching to process ID 13680, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.51-b03
Non-default VM flags: -XX:CICompilerCount=2 -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=134217728 -XX:MaxNewSize=44564480 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=44564480 -XX:OldSize=89653248 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
Command line: -Xms128m -Xmx128m -Dserver.port=8083
Видимый, так как мы устанавливаем его при запуске-Xms
и-Xmx
, они соответствуют-XX:InitialHeapSize
и-XX:MaxHeapSize
ценность. Кроме того, параметр-Dserver.port
относится к системным параметрам, используйтеjinfo -sysprops 13680
Вы можете просмотреть системные параметры.
5 Средство просмотра динамической памятиjmap
5.1 jmap
инструкция
После запуска java-приложения оно запускается в JVM, а память — это то место, за которым нужно следить.jmap
Это такой инструмент, который может получить снимок кучи работающей jvm, включая общую ситуацию, гистограмму занятости кучи, и сделать дамп файла снимка для автономного анализа. В официальной документации указано, что эта команда больше не может использоваться в последующих версиях, а текущий JDK8 по-прежнему можно использовать.
5.2 jmap
использовать
пройти через-help
Справка по просмотру параметров, вы также можете обратиться кjmap
Описание официальной документации:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jmap.html
, описание справки выглядит следующим образом:
[root@test bin]# jmap -help
Usage:
jmap [option] <pid>
(to connect to running process)
where <option> is one of:
<none> to print same info as Solaris pmap
-heap to print java heap summary
-histo[:live] to print histogram of java object heap; if the "live"
suboption is specified, only count live objects
-clstats to print class loader statistics
-finalizerinfo to print information on objects awaiting finalization
-dump:<dump-options> to dump java heap in hprof binary format
dump-options:
live dump only live objects; if not specified,
all objects in the heap are dumped.
format=b binary format
file=<file> dump heap to <file>
Example: jmap -dump:live,format=b,file=heap.bin <pid>
-F force. Use with -dump:<dump-options> <pid> or -histo
to force a heap dump or histogram when <pid> does not
respond. The "live" suboption is not supported
in this mode.
-h | -help to print this help message
-J<flag> to pass <flag> directly to the runtime system
Как показано выше,jmap
Обычно используются следующие параметры-heap
,-histo
и-dump
, в сочетании с примеромjava-monitor-example
, описывается следующим образом:
- Вывести общее использование памяти jvm
[root@test bin]# jmap -heap 13680
Attaching to process ID 13680, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.51-b03
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 134217728 (128.0MB)
NewSize = 44564480 (42.5MB)
MaxNewSize = 44564480 (42.5MB)
OldSize = 89653248 (85.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 31981568 (30.5MB)
used = 5306632 (5.060798645019531MB)
free = 26674936 (25.43920135498047MB)
16.59278244268699% used
From Space:
capacity = 6291456 (6.0MB)
used = 1081440 (1.031341552734375MB)
free = 5210016 (4.968658447265625MB)
17.18902587890625% used
To Space:
capacity = 6291456 (6.0MB)
used = 0 (0.0MB)
free = 6291456 (6.0MB)
0.0% used
PS Old Generation
capacity = 89653248 (85.5MB)
used = 16615680 (15.845947265625MB)
free = 73037568 (69.654052734375MB)
18.533271655701753% used
18006 interned Strings occupying 2328928 bytes.
Из приведенной выше информации мы можем видеть текущее использование памяти кучи в JVM, включая молодое поколение (Eden
округ,From
округ,To
района) и старшее поколение.
- Просмотр имени класса, количества объектов, гистограммы занимаемого размера объекта
[root@test bin]# jmap -histo:live 13680|more
num #instances #bytes class name
----------------------------------------------
1: 36536 6462912 [C
2: 35557 853368 java.lang.String
3: 7456 826968 java.lang.Class
4: 20105 643360 java.util.concurrent.ConcurrentHashMap$Node
5: 1449 469024 [B
6: 6951 399280 [Ljava.lang.Object;
7: 9311 297952 java.util.HashMap$Node
8: 3122 274736 java.lang.reflect.Method
9: 2884 269112 [I
10: 6448 257920 java.util.LinkedHashMap$Entry
11: 2994 255160 [Ljava.util.HashMap$Node;
12: 15249 243984 java.lang.Object
.....
.....
Как показано выше, используйте-histo
Вывод включает порядковый номер, количество экземпляров, занятые байты и имя класса. Конкретные инструкции заключаются в следующем:
- Столбец экземпляров: показывает, сколько экземпляров имеет текущий класс.
- столбец байтов: указывает, сколько байтов в общей сложности занимает текущий экземпляр класса
- столбец имени класса: указывает имя текущего класса, интерпретация имени класса:
- B для байта
- C означает символ
- D для двойного
- F для поплавка
- я для инт
- J для долго
- Z для логического
- [Представляет массив, например [I эквивалентен int[]
- Объекты представлены [L+имя класса
- Сброс ситуации с памятью в локальный файл
[root@test bin]# jmap -dump:file=./heap.hprof 13680
Как показано выше, ситуация с кучей будет записана в текущий каталог.heap.hprof
файл, что касается того, как анализировать этот файл, вы можете использоватьjhat
, но в общем актуальной разработке jhat редко используется для непосредственного анализа файла дампа памяти, поэтому описывать его не буду. Это больше об использовании инструментовMAT
Для визуального просмотра, последующая статья будет наMAT
Использование инструмента подробно описано.
6 Инструмент запроса стека потоковjstack
6.1 jstack
инструкция
Эта команда выводит стек потока указанного Java-приложения.Для каждого кадра Java она выводит полное имя класса, имя метода, индекс байтового кода (BCI) и номер строки, которые можно использовать для обнаружения взаимоблокировок, остановок потока, процесса. потребление Устранение неполадок, связанных с сигнализацией высокой загрузки ЦП и т. д.
6.2 jstack
использовать
использовать-help
Справка по просмотру параметров, вы также можете обратиться кjstack
Описание официальной документации:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstack.html
, описание справки выглядит следующим образом:
[root@test bin]# jstack -help
Usage:
jstack [-l] <pid>
(to connect to running process)
jstack -F [-m] [-l] <pid>
Options:
-F 强制dump线程堆栈信息. 用于进程hung住, jstack <pid>命令没有响应的情况
-m 同时打印java和本地(native)线程栈信息,m是mixed mode的简写
-l 打印锁的额外信息
Объединить примерjava-monitor-example
, вы можете распечатать информацию о потоке (обычно распечатываемое содержимое записывается в файл, а затем анализируется) следующим образом:
- Распечатать текущую информацию о стеке потоков
[root@test bin]# jstack 13680
2019-08-16 23:18:18
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.51-b03 mixed mode):
"http-nio-8083-Acceptor-0" #39 daemon prio=5 os_prio=0 tid=0x00007f7520698800 nid=0x359a runnable [0x00007f7508bb7000]
java.lang.Thread.State: RUNNABLE
at sun.nio.ch.ServerSocketChannelImpl.accept0(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:422)
at sun.nio.ch.ServerSocketChannelImpl.accept(ServerSocketChannelImpl.java:250)
- locked <0x00000000f8c85380> (a java.lang.Object)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:448)
at org.apache.tomcat.util.net.NioEndpoint.serverSocketAccept(NioEndpoint.java:70)
at org.apache.tomcat.util.net.Acceptor.run(Acceptor.java:95)
at java.lang.Thread.run(Thread.java:745)
6.3 Анализ дампа потока
6.3.1 Состояние потока
использование стека потоков Javajstack
После дампа вы можете увидеть статус потока.Существует 6 типов статуса потока, вы можете обратиться кофициальная документация:https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr034.html
, ниже приводится описание его состояния:
- NEW
Тема создана new, но не начата (еще не начата),jstack
Информация о потоке для этого состояния не будет напечатана.
- RUNNABLE
Статус потока, который выполняет задачу под виртуальной машиной Java, но на самом деле это просто означает, что поток готов к запуску. Для одноядерного ЦП несколько потоков могут одновременно запускать только один поток, а остальные должны ждать планирования ЦП.
- BLOCKED
Поток находится в заблокированном состоянии и ожидает блокировки. Несколько потоков совместно используют блокировку. Когда поток использует эту блокировку для входа в блок или метод синхронизированного метода синхронизации, и потоку необходимо ввести этот блок кода синхронизации, а также необходимо эту блокировку, затем приводит к блокировке потока.
- WAITING
Состояние ожидания, поток в состоянии ожидания находится из-за выполнения любого из 3-х методов. 1.Object.wait
метод и не использует параметр тайм-аута; 2.Thread.join
метод без параметра тайм-аута 3.LockSupport.park
метод. Поток в состоянии ожидания ожидает, когда другой поток выполнит определенное действие. Поток находится в состоянии ожидания (ожидание, обычно ожидание завершения операции другими потоками (notify или notifyAll). Примечание.Object.wait()
Методы можно вызывать только из синхронизированного блока кода. называетсяwait()
способ, блокировка снимается.
- TIMED_WAITING
Поток ожидает указанное время, и вызов следующих методов может привести к тому, что поток окажется в этом состоянии: 1. Метод Thread.sleep 2.Object.wait
метод, со временем 3.Thread.join
метод, со временем 4.LockSupport.parkNanos
метод, со временем 5.LockSupport.parkUntil
метод, со временем. Уведомление,Thread.sleep
После вызова метод не снимает блокировку и по-прежнему занимает системные ресурсы.
- TERMINATED
Состояние, в котором поток прерван, поток завершил свою задачу.
На следующем рисунке вы можете увидеть изменение состояния потока:
6.3.2 Анализjstack
содержимое стека обратного потока
использовать с фронтаjstack
Чтобы выгрузить информацию, нам нужно знать следующую информацию:
-
"http-nio-8083-Acceptor-0" #39
: — это имя потока, поэтому обычно нам нужно установить имя, которое мы сможем распознать при создании потока. -
daemon
Указывает, является ли поток потоком демона -
prio
Представляет приоритет, который мы установили для потока -
os_prio
Указывает приоритет соответствующего потока операционной системы. Поскольку не все операционные системы поддерживают приоритет потока, для него может быть установлено значение 0. -
tid
идентификатор потока -
nid
Идентификатор локального потока операционной системы, соответствующий потоку, каждый поток java имеет соответствующий поток операционной системы, который находится в шестнадцатеричном формате, поэтому обычно после получения идентификатора потока в операционной системе его необходимо преобразовать в шестнадцатеричный, чтобы соответствовать превосходить. -
java.lang.Thread.State: RUNNABLE
Состояние выполнения, состояние потока было введено выше. Если он находится в состоянии ОЖИДАНИЕ, содержимое в круглых скобках объясняет причину ожидания. Например, описание парковки вызвано тем, что метод LockSupport.park вызывается, чтобы вызвать ожидающий. В обычной информации о стеке будет метка блокировки, например- locked <0x00000000f8c85380> (a java.lang.Object)
Указывает, что блокировка удерживается. - Для таких проблем, как пауза потока, использование ЦП и т. д., вы можете сосредоточиться на
wait
состояние потока - Для взаимоблокировок моментальный снимок стека потоков из дампа может напрямую сообщать о взаимоблокировках на уровне Java.
7 инструментов статистики JVMjstat
7.1 jstat
инструкция
jstat
которыйJVM Statistics Monitoring Tool
, то есть инструмент статистического мониторинга JVM, включая мониторинг текущих данных, таких как загрузка классов, память, сборка мусора и JIT-компиляция.На сервере без графики это предпочтительный инструмент для обнаружения проблем с производительностью виртуальной машины во время выполнения.
7.2 jstat
использовать
использовать-help
Справка по просмотру параметров, вы также можете обратиться кjstat
Описание официальной документации:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
, описание справки выглядит следующим образом:
[root@test bin]# jstat -help
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
Usage: jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
Definitions:
<option> An option reported by the -options option
<vmid> Virtual Machine Identifier. A vmid takes the following form:
<lvmid>[@<hostname>[:<port>]]
Where <lvmid> is the local vm identifier for the target
Java virtual machine, typically a process id; <hostname> is
the name of the host running the target Java virtual machine;
and <port> is the port number for the rmiregistry on the
target host. See the jvmstat documentation for a more complete
description of the Virtual Machine Identifier.
<lines> Number of samples between header lines.
<interval> Sampling interval. The following forms are allowed:
<n>["ms"|"s"]
Where <n> is an integer and the suffix specifies the units as
milliseconds("ms") or seconds("s"). The default units are "ms".
<count> Number of samples to take before terminating.
-J<flag> Pass <flag> directly to the runtime system.
Как показано выше,vmid
,interval
,count
— номер процесса, интервал печати (с или мс) и количество отпечатков, гдеoption
Параметры в основном следующие (вы также можете использовать командуjstat -option
Проверять):
-
-class
Статистическая информация о поведении загрузчика классов, например, сколько классов загружено в целом. -
-compile
Статистика поведения компилятора HotSpot Just-in-Time -
-gc
информация о куче при статистике jdk gc -
-gccapacity
Подсчитайте соответствующую емкость кучи разных поколений -
-gccause
Статистика gc (такая же, как -gcutil) и события, вызывающие gc -
-gcnew
При подсчете gc ситуация нового поколения -
-gcnewcapacity
При расчете gc емкость кучи нового поколения -
-gcold
Когда статистика gc, ситуация в районе пожилых людей -
-gcoldcapacity
При расчете gc емкость кучи старой области -
-gcpermcapacity
При расчете gc емкость кучи постоянной области -
-gcutil
Когда статистика gc, куча ситуации -
-printcompilation
статистика метода компиляции хотспота
Обычно мы используем-class
,-gc
,-gccause
и-gcutil
Больше, в основном используется для анализа использования класса и кучи и ситуации с gc.
7.3 Мониторинг GC JVM
Возьмите приведенный выше пример проектаjava-monitor-example
Например, он включает в себя функцию для проверки нехватки памяти (используя массив, цикл создания объектов до нехватки памяти). использоватьjstat -gc 13680 1000
То есть мониторить раз в секунду, звонить/monitor/user/oom
После интерфейса можно увидеть изменения кучи и сборщика мусора. Для удобства просмотра я помещаю вывод в возвышенное состояние и отображаю его следующим образом:
Выход журнала OOM сообщает об ошибке:
Содержание приведенного выше вывода, описание каждого столбца выглядит следующим образом:
-
S0C
Общая емкость (КБ) первого оставшегося в живых (s0) в текущем молодом поколении. -
S1C
Общая емкость (КБ) первого оставшегося в живых (s1) в текущем молодом поколении. -
S0U
s0 Используемая емкость (КБ). -
S1U
s1 используемая емкость (КБ). -
EC
Суммарная емкость области Эдема в текущем молодом поколении (КБ). -
EU
Используемая емкость области eden (КБ). -
OC
Суммарная емкость старого поколения (КБ). -
OU
Используемая емкость старого поколения (КБ). -
MC
Текущая общая емкость метапространства (КБ). -
MU
Текущая используемая емкость метапространства (КБ). -
CCSC
Размер емкости сжатого класса -
CCSU
Используемая мощность сжатого класса -
YGC
Общее количество GC Events в молодом поколении с момента запуска приложения до настоящего времени. -
YGCT
Общее время, затраченное на сборку мусора Young Generation с момента запуска приложения до настоящего времени. -
FGC
Общее количество полных событий GC с момента запуска приложения до настоящего времени. -
FGCT
Общее время, проведенное в режиме Full sc с момента запуска приложения до настоящего момента GCT Общее время сборки мусора с момента запуска приложения до настоящего времени -GCT
GCT=YGCT+FGCT
Как видно из строки 6 приведенного выше вывода,EC
иEU
,OC
иOU
Указывает, что память молодого поколения и старого поколения израсходована (равно значению емкости), и происходит OOM. В это время нужно принять меры по увеличению памяти (параметр -Xmx) или найти код, вызывающий модификацию OOM.
8 Резюме
Для мониторинга java-приложений в этой статье описывается и вводится использование инструментов командной строки, предоставляемых самим jdk, и полностью описывается просмотр процесса java-приложения, просмотр параметров запуска, просмотр состояния памяти, просмотр состояния потока, просмотр статистики памяти, д., в основномjps
,jinfo
,jmap
,jstack
,jstat
5 инструментов, в сочетании с примерами, я надеюсь, что Java-разработчики смогут освоить эти технологии.При мониторинге Java-приложений они смогут спокойно столкнуться с такими проблемами, как OOM, высокая загрузка ЦП и зависание потоков.
использованная литература
-
Справочная документация по инструментам JDK:
https://docs.oracle.com/javase/8/docs/technotes/tools/unix/
-
образец кода
java-monitor-example
:https://github.com/mianshenglee/my-example/tree/master/java-monitor-example
-
Онлайн-команды для устранения неполадок, которые должны освоить Java-разработчики:
https://www.hollischuang.com/archives/1561
-
Собственный инструмент мониторинга производительности Java:
http://www.tianshouzhi.com/api/tutorials/jvm/346