大樂透 AI 智慧分析平台
- 3月 16 週四 201720:38
文章列表
大樂透 AI 智慧分析平台
- 3月 15 週三 201722:42
jvm系列(四):jvm調優-命令大全(jps jstat jmap jhat jstack jinfo)
文章出處文章同步發布于github博客地址,閱讀效果更佳,歡迎品嘗
運用jvm自帶的命令可以方便的在生產監控和打印堆棧的日志信息幫忙我們來定位問題!雖然jvm調優成熟的工具已經有很多:jconsole、大名鼎鼎的VisualVM,IBM的Memory Analyzer等等,但是在生產環境出現問題的時候,一方面工具的使用會有所限制,另一方面喜歡裝X的我們,總喜歡在出現問題的時候在終端輸入一些命令來解決。所有的工具幾乎都是依賴于jdk的接口和底層的這些命令,研究這些命令的使用也讓我們更能了解jvm構成和特性。
Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo下面做一一介紹
jps
JVM Process Status Tool,顯示指定系統內所有的HotSpot虛擬機進程。
命令格式
jps [options] [hostid]option參數
- -l : 輸出主類全名或jar路徑
- -q : 只輸出LVMID
- -m : 輸出JVM啟動時傳遞給main()的參數
- -v : 輸出JVM啟動時顯示指定的JVM參數
其中[option]、[hostid]參數也可以不寫。
示例
$ jps -l -m
28920 org.apache.catalina.startup.Bootstrap start
11589 org.apache.catalina.startup.Bootstrap start
25816 sun.tools.jps.Jps -l -mjstat
jstat(JVM statistics Monitoring)是用于監視虛擬機運行時狀態信息的命令,它可以顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
命令格式
jstat [option] LVMID [interval] [count]參數
- [option] : 操作參數
- LVMID : 本地虛擬機進程ID
- [interval] : 連續輸出的時間間隔
- [count] : 連續輸出的次數
option 參數總覽
option 參數詳解
-class
監視類裝載、卸載數量、總空間以及耗費的時間
$ jstat -class 11589
Loaded Bytes Unloaded Bytes Time
7035 14506.3 0 0.0 3.67- Loaded : 加載class的數量
- Bytes : class字節大小
- Unloaded : 未加載class的數量
- Bytes : 未加載class的字節大小
- Time : 加載時間
-compiler
輸出JIT編譯過的方法數量耗時等
$ jstat -compiler 1262
Compiled Failed Invalid Time FailedType FailedMethod
2573 1 0 47.60 1 org/apache/catalina/loader/WebappClassLoader findResourceInternal - Compiled : 編譯數量
- Failed : 編譯失敗數量
- Invalid : 無效數量
- Time : 編譯耗時
- FailedType : 失敗類型
- FailedMethod : 失敗方法的全限定名
-gc
垃圾回收堆的行為統計,常用命令
$ jstat -gc 1262
S0C S1C S0U S1U EC EU OC OU PC PU YGC YGCT FGC FGCT GCT
26112.0 24064.0 6562.5 0.0 564224.0 76274.5 434176.0 388518.3 524288.0 42724.7 320 6.417 1 0.398 6.815C即Capacity 總容量,U即Used 已使用的容量
- S0C : survivor0區的總容量
- S1C : survivor1區的總容量
- S0U : survivor0區已使用的容量
- S1C : survivor1區已使用的容量
- EC : Eden區的總容量
- EU : Eden區已使用的容量
- OC : Old區的總容量
- OU : Old區已使用的容量
- PC 當前perm的容量 (KB)
- PU perm的使用 (KB)
- YGC : 新生代垃圾回收次數
- YGCT : 新生代垃圾回收時間
- FGC : 老年代垃圾回收次數
- FGCT : 老年代垃圾回收時間
- GCT : 垃圾回收總消耗時間
$ jstat -gc 1262 2000 20這個命令意思就是每隔2000ms輸出1262的gc情況,一共輸出20次
-gccapacity
同-gc,不過還會輸出Java堆各區域使用到的最大、最小空間
$ jstat -gccapacity 1262
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
614400.0 614400.0 614400.0 26112.0 24064.0 564224.0 434176.0 434176.0 434176.0 434176.0 524288.0 1048576.0 524288.0 524288.0 320 1 - NGCMN : 新生代占用的最小空間
- NGCMX : 新生代占用的最大空間
- OGCMN : 老年代占用的最小空間
- OGCMX : 老年代占用的最大空間
- OGC:當前年老代的容量 (KB)
- OC:當前年老代的空間 (KB)
- PGCMN : perm占用的最小空間
- PGCMX : perm占用的最大空間
-gcutil
同-gc,不過輸出的是已使用空間占總空間的百分比
$ jstat -gcutil 28920
S0 S1 E O P YGC YGCT FGC FGCT GCT
12.45 0.00 33.85 0.00 4.44 4 0.242 0 0.000 0.242-gccause
垃圾收集統計概述(同-gcutil),附加最近兩次垃圾回收事件的原因
$ jstat -gccause 28920
S0 S1 E O P YGC YGCT FGC FGCT GCT LGCC GCC
12.45 0.00 33.85 0.00 4.44 4 0.242 0 0.000 0.242 Allocation Failure No GC - LGCC:最近垃圾回收的原因
- GCC:當前垃圾回收的原因
-gcnew
統計新生代的行為
$ jstat -gcnew 28920
S0C S1C S0U S1U TT MTT DSS EC EU YGC YGCT
419392.0 419392.0 52231.8 0.0 6 6 209696.0 3355520.0 1172246.0 4 0.242- TT:Tenuring threshold(提升閾值)
- MTT:最大的tenuring threshold
- DSS:survivor區域大小 (KB)
-gcnewcapacity
新生代與其相應的內存空間的統計
$ jstat -gcnewcapacity 28920
NGCMN NGCMX NGC S0CMX S0C S1CMX S1C ECMX EC YGC FGC
4194304.0 4194304.0 4194304.0 419392.0 419392.0 419392.0 419392.0 3355520.0 3355520.0 4 0- NGC:當前年輕代的容量 (KB)
- S0CMX:最大的S0空間 (KB)
- S0C:當前S0空間 (KB)
- ECMX:最大eden空間 (KB)
- EC:當前eden空間 (KB)
-gcold
統計舊生代的行為
$ jstat -gcold 28920
PC PU OC OU YGC FGC FGCT GCT
1048576.0 46561.7 6291456.0 0.0 4 0 0.000 0.242-gcoldcapacity
統計舊生代的大小和空間
$ jstat -gcoldcapacity 28920
OGCMN OGCMX OGC OC YGC FGC FGCT GCT
6291456.0 6291456.0 6291456.0 6291456.0 4 0 0.000 0.242-gcpermcapacity
永生代行為統計
$ jstat -gcpermcapacity 28920
PGCMN PGCMX PGC PC YGC FGC FGCT GCT
1048576.0 2097152.0 1048576.0 1048576.0 4 0 0.000 0.242-printcompilation
hotspot編譯方法統計
$ jstat -printcompilation 28920
Compiled Size Type Method
1291 78 1 java/util/ArrayList indexOf- Compiled:被執行的編譯任務的數量
- Size:方法字節碼的字節數
- Type:編譯類型
- Method:編譯方法的類名和方法名。類名使用"/" 代替 "." 作為空間分隔符. 方法名是給出類的方法名. 格式是一致于HotSpot - XX:+PrintComplation 選項
jmap
jmap(JVM Memory Map)命令用于生成heap dump文件,如果不使用這個命令,還闊以使用-XX:+HeapDumpOnOutOfMemoryError參數來讓虛擬機出現OOM的時候·自動生成dump文件。
jmap不僅能生成dump文件,還闊以查詢finalize執行隊列、Java堆和永久代的詳細信息,如當前使用率、當前使用的是哪種收集器等。
命令格式
jmap [option] LVMIDoption參數
- dump : 生成堆轉儲快照
- finalizerinfo : 顯示在F-Queue隊列等待Finalizer線程執行finalizer方法的對象
- heap : 顯示Java堆詳細信息
- histo : 顯示堆中對象的統計信息
- permstat : to print permanent generation statistics
- F : 當-dump沒有響應時,強制生成dump快照
示例
-dump
常用格式
-dump::live,format=b,file=<filename> pid dump堆到文件,format指定輸出格式,live指明是活著的對象,file指定文件名
$ jmap -dump:live,format=b,file=dump.hprof 28920
Dumping heap to /home/xxx/dump.hprof ...
Heap dump file createddump.hprof這個后綴是為了后續可以直接用MAT(Memory Anlysis Tool)打開。
-finalizerinfo
打印等待回收對象的信息
$ jmap -finalizerinfo 28920
Attaching to process ID 28920, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
Number of objects pending for finalization: 0可以看到當前F-QUEUE隊列中并沒有等待Finalizer線程執行finalizer方法的對象。
-heap
打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情況,可以用此來判斷內存目前的使用情況以及垃圾回收情況
$ jmap -heap 28920
Attaching to process ID 28920, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)//GC 方式
Heap Configuration: //堆內存初始化配置
MinHeapFreeRatio = 0 //對應jvm啟動參數-XX:MinHeapFreeRatio設置JVM堆最小空閑比率(default 40)
MaxHeapFreeRatio = 100 //對應jvm啟動參數 -XX:MaxHeapFreeRatio設置JVM堆最大空閑比率(default 70)
MaxHeapSize = 2082471936 (1986.0MB) //對應jvm啟動參數-XX:MaxHeapSize=設置JVM堆的最大大小
NewSize = 1310720 (1.25MB)//對應jvm啟動參數-XX:NewSize=設置JVM堆的‘新生代’的默認大小
MaxNewSize = 17592186044415 MB//對應jvm啟動參數-XX:MaxNewSize=設置JVM堆的‘新生代’的最大大小
OldSize = 5439488 (5.1875MB)//對應jvm啟動參數-XX:OldSize=<value>:設置JVM堆的‘老生代’的大小
NewRatio = 2 //對應jvm啟動參數-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
SurvivorRatio = 8 //對應jvm啟動參數-XX:SurvivorRatio=設置年輕代中Eden區與Survivor區的大小比值
PermSize = 21757952 (20.75MB) //對應jvm啟動參數-XX:PermSize=<value>:設置JVM堆的‘永生代’的初始大小
MaxPermSize = 85983232 (82.0MB)//對應jvm啟動參數-XX:MaxPermSize=<value>:設置JVM堆的‘永生代’的最大大小
G1HeapRegionSize = 0 (0.0MB)
Heap Usage://堆內存使用情況
PS Young Generation
Eden Space://Eden區內存分布
capacity = 33030144 (31.5MB)//Eden區總容量
used = 1524040 (1.4534378051757812MB) //Eden區已使用
free = 31506104 (30.04656219482422MB) //Eden區剩余容量
4.614088270399305% used //Eden區使用比率
From Space: //其中一個Survivor區的內存分布
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
To Space: //另一個Survivor區的內存分布
capacity = 5242880 (5.0MB)
used = 0 (0.0MB)
free = 5242880 (5.0MB)
0.0% used
PS Old Generation //當前的Old區內存分布
capacity = 86507520 (82.5MB)
used = 0 (0.0MB)
free = 86507520 (82.5MB)
0.0% used
PS Perm Generation//當前的 “永生代” 內存分布
capacity = 22020096 (21.0MB)
used = 2496528 (2.3808746337890625MB)
free = 19523568 (18.619125366210938MB)
11.337498256138392% used
670 interned Strings occupying 43720 bytes.可以很清楚的看到Java堆中各個區域目前的情況。
-histo
打印堆的對象統計,包括對象數、內存大小等等 (因為在dump:live前會進行full gc,如果帶上live則只統計活對象,因此不加live的堆大小要大于加live堆的大小 )
$ jmap -histo:live 28920 | more
num #instances #bytes class name
----------------------------------------------
1: 83613 12012248 <constMethodKlass>
2: 23868 11450280 [B
3: 83613 10716064 <methodKlass>
4: 76287 10412128 [C
5: 8227 9021176 <constantPoolKlass>
6: 8227 5830256 <instanceKlassKlass>
7: 7031 5156480 <constantPoolCacheKlass>
8: 73627 1767048 java.lang.String
9: 2260 1348848 <methodDataKlass>
10: 8856 849296 java.lang.Class
....僅僅打印了前10行
xml class name是對象類型,說明如下:B byte
C char
D double
F float
I int
J long
Z boolean
[ 數組,如[I表示int[]
[L+類名 其他對象-permstat
打印Java堆內存的永久保存區域的類加載器的智能統計信息。對于每個類加載器而言,它的名稱、活躍度、地址、父類加載器、它所加載的類的數量和大小都會被打印。此外,包含的字符串數量和大小也會被打印。
$ jmap -permstat 28920
Attaching to process ID 28920, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness.liveness analysis may be inaccurate ...
class_loader classes bytes parent_loader alive? type
<bootstrap> 3111 18154296 null live <internal>
0x0000000600905cf8 1 1888 0x0000000600087f08 dead sun/reflect/DelegatingClassLoader@0x00000007800500a0
0x00000006008fcb48 1 1888 0x0000000600087f08 dead sun/reflect/DelegatingClassLoader@0x00000007800500a0
0x00000006016db798 0 0 0x00000006008d3fc0 dead java/util/ResourceBundle$RBClassLoader@0x0000000780626ec0
0x00000006008d6810 1 3056 null dead sun/reflect/DelegatingClassLoader@0x00000007800500a0-F
強制模式。如果指定的pid沒有響應,請使用jmap -dump或jmap -histo選項。此模式下,不支持live子選項。
jhat
jhat(JVM Heap Analysis Tool)命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果后,可以在瀏覽器中查看。在此要注意,一般不會直接在服務器上進行分析,因為jhat是一個耗時并且耗費硬件資源的過程,一般把服務器生成的dump文件復制到本地或其他機器上進行分析。
命令格式
jhat [dumpfile]參數
- -stack false|true
關閉對象分配調用棧跟蹤(tracking object allocation call stack)。 如果分配位置信息在堆轉儲中不可用. 則必須將此標志設置為 false. 默認值為 true.> - -refs false|true
關閉對象引用跟蹤(tracking of references to objects)。 默認值為 true. 默認情況下, 返回的指針是指向其他特定對象的對象,如反向鏈接或輸入引用(referrers or incoming references), 會統計/計算堆中的所有對象。> - -port port-number
設置 jhat HTTP server 的端口號. 默認值 7000.> - -exclude exclude-file
指定對象查詢時需要排除的數據成員列表文件(a file that lists data members that should be excluded from the reachable objects query)。 例如, 如果文件列列出了 java.lang.String.value , 那么當從某個特定對象 Object o 計算可達的對象列表時, 引用路徑涉及 java.lang.String.value 的都會被排除。> - -baseline exclude-file
指定一個基準堆轉儲(baseline heap dump)。 在兩個 heap dumps 中有相同 object ID 的對象會被標記為不是新的(marked as not being new). 其他對象被標記為新的(new). 在比較兩個不同的堆轉儲時很有用.> - -debug int
設置 debug 級別. 0 表示不輸出調試信息。 值越大則表示輸出更詳細的 debug 信息.> - -version
啟動后只顯示版本信息就退出> - -J< flag >
因為 jhat 命令實際上會啟動一個JVM來執行, 通過 -J 可以在啟動JVM時傳入一些啟動參數. 例如, -J-Xmx512m 則指定運行 jhat 的Java虛擬機使用的最大堆內存為 512 MB. 如果需要使用多個JVM啟動參數,則傳入多個 -Jxxxxxx.
示例
$ jhat -J-Xmx512m dump.hprof
eading from dump.hprof...
Dump file created Fri Mar 11 17:13:42 CST 2016
Snapshot read, resolving...
Resolving 271678 objects...
Chasing references, expect 54 dots......................................................
Eliminating duplicate references......................................................
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.中間的-J-Xmx512m是在dump快照很大的情況下分配512M內存去啟動HTTP服務器,運行完之后就可在瀏覽器打開Http://localhost:7000進行快照分析
堆快照分析主要在最后面的Heap Histogram里,里面根據class列出了dump的時候所有存活對象。
分析同樣一個dump快照,MAT需要的額外內存比jhat要小的多的多,所以建議使用MAT來進行分析,當然也看個人偏好。
分析
打開瀏覽器Http://localhost:7000,該頁面提供了幾個查詢功能可供使用:
All classes including platform
Show all members of the rootset
Show instance counts for all classes (including platform)
Show instance counts for all classes (excluding platform)
Show heap histogram
Show finalizer summary
Execute Object Query Language (OQL) query一般查看堆異常情況主要看這個兩個部分:
Show instance counts for all classes (excluding platform),平臺外的所有對象信息。如下圖:
Show heap histogram 以樹狀圖形式展示堆情況。如下圖:
具體排查時需要結合代碼,觀察是否大量應該被回收的對象在一直被引用或者是否有占用內存特別大的對象無法被回收。
一般情況,會down到客戶端用工具來分析
jstack
jstack用于生成java虛擬機當前時刻的線程快照。線程快照是當前java虛擬機內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等。 線程出現停頓的時候通過jstack來查看各個線程的調用堆棧,就可以知道沒有響應的線程到底在后臺做什么事情,或者等待什么資源。 如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發生問題。另外,jstack工具還可以附屬到正在運行的java程序中,看到當時運行的java程序的java stack和native stack的信息, 如果現在運行的java程序呈現hung的狀態,jstack是非常有用的。
命令格式
jstack [option] LVMIDoption參數
- -F : 當正常輸出請求不被響應時,強制輸出線程堆棧
- -l : 除堆棧外,顯示關于鎖的附加信息
- -m : 如果調用到本地方法的話,可以顯示C/C++的堆棧
示例
$ jstack -l 11494|more
2016-07-28 13:40:04
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.71-b01 mixed mode):
"Attach Listener" daemon prio=10 tid=0x00007febb0002000 nid=0x6b6f waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
"http-bio-8005-exec-2" daemon prio=10 tid=0x00007feb94028000 nid=0x7b8c waiting on condition [0x00007fea8f56e000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000cae09b80> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:186)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2043)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:104)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:32)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- None
.....分析
這里有一篇文章解釋的很好
分析打印出的文件內容
jinfo
jinfo(JVM Configuration info)這個命令作用是實時查看和調整虛擬機運行參數。
之前的jps -v口令只能查看到顯示指定的參數,如果想要查看未被顯示指定的參數的值就要使用jinfo口令
命令格式
jinfo [option] [args] LVMIDoption參數
- -flag : 輸出指定args參數的值
- -flags : 不需要args參數,輸出所有JVM參數的值
- -sysprops : 輸出系統屬性,等同于System.getProperties()
示例
$ jinfo -flag 11494
-XX:CMSInitiatingOccupancyFraction=80- 3月 15 週三 201722:42
spring boot(一):入門篇
文章出處構建微服務:Spring boot 入門篇
什么是spring boot
Spring Boot是由Pivotal團隊提供的全新框架,其設計目的是用來簡化新Spring應用的初始搭建以及開發過程。該框架使用了特定的方式來進行配置,從而使開發人員不再需要定義樣板化的配置。用我的話來理解,就是spring boot其實不是什么新的框架,它默認配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架(不知道這樣比喻是否合適)。
使用spring boot有什么好處
其實就是簡單、快速、方便!平時如果我們需要搭建一個spring web項目的時候需要怎么做呢?
1)配置web.xml,加載spring和spring mvc
2)配置數據庫連接、配置spring事務
3)配置加載配置文件的讀取,開啟注解
4)配置日志文件
...
配置完成之后部署tomcat 調試
...
現在非常流行微服務,如果我這個項目僅僅只是需要發送一個郵件,如果我的項目僅僅是生產一個積分;我都需要這樣折騰一遍!
但是如果使用spring boot呢?
很簡單,我僅僅只需要非常少的幾個配置就可以迅速方便的搭建起來一套web項目或者是構建一個微服務!
使用sping boot到底有多爽,用下面這幅圖來表達
快速入門
說了那么多,手癢癢的很,馬上來一發試試!
maven構建項目
1、訪問http://start.spring.io/
2、選擇構建工具Maven Project、Spring Boot版本1.3.6以及一些工程基本信息,點擊“Switch to the full version.”java版本選擇1.7,可參考下圖所示:
3、點擊Generate Project下載項目壓縮包
4、解壓后,使用eclipse,Import -> Existing Maven Projects -> Next ->選擇解壓后的文件夾-> Finsh,OK done!
項目結構介紹
如上圖所示,Spring Boot的基礎結構共三個文件:
l src/main/java 程序開發以及主程序入口
l src/main/resources 配置文件
l src/test/java 測試程序
另外,spingboot建議的目錄結果如下:
root package結構:com.example.myproject
com
+- example
+- myproject
+- Application.java
|
+- domain
| +- Customer.java
| +- CustomerRepository.java
|
+- service
| +- CustomerService.java
|
+- controller
| +- CustomerController.java
|1、Application.java 建議放到跟目錄下面,主要用于做一些框架配置
2、domain目錄主要用于實體(Entity)與數據訪問層(Repository)
3、service 層主要是業務類代碼
4、controller 負責頁面訪問控制
采用默認配置可以省去很多配置,當然也可以根據自己的喜歡來進行更改
最后,啟動Application main方法,至此一個java項目搭建好了!
引入web模塊
1、pom.xml中添加支持web的模塊:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
pom.xml文件中默認有兩個模塊:
spring-boot-starter:核心模塊,包括自動配置支持、日志和YAML;
spring-boot-starter-test:測試模塊,包括JUnit、Hamcrest、Mockito。
2、編寫controller內容
@RestController
public class HelloWorldController {
@RequestMapping("/hello")
public String index() {
return "Hello World";
}
}@RestController的意思就是controller里面的方法都以json格式輸出,不用再寫什么jackjson配置的了!
3、啟動主程序,打開瀏覽器訪問http://localhost:8080/hello,就可以看到效果了,有木有很簡單!
如何做單元測試
打開的src/test/下的測試入口,編寫簡單的http請求來測試;使用mockmvc進行,利用MockMvcResultHandlers.print()打印出執行結果。
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MockServletContext.class)
@WebAppConfiguration
public class HelloWorldControlerTests {
private MockMvc mvc;
@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(new HelloWorldController()).build();
}
@Test
public void getHello() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/hello").accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
}
}開發環境的調試
熱啟動在正常開發項目中已經很常見了吧,雖然平時開發web項目過程中,改動項目啟重啟總是報錯;但springBoot對調試支持很好,修改之后可以實時生效,需要添加以下的配置:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
</configuration>
</plugin>
</plugins>
</build>
該模塊在完整的打包環境下運行的時候會被禁用。如果你使用java -jar啟動應用或者用一個特定的classloader啟動,它會認為這是一個“生產環境”。
總結
使用spring boot可以非常方便、快速搭建項目,使我們不用關心框架之間的兼容性,適用版本等各種問題,我們想使用任何東西,僅僅添加一個配置就可以,所以使用sping boot非常適合構建微服務。
附演示demo地址:https://github.com/ityouknow/spring-boot-starter/tree/master/spring-boot-helloWorld
- 3月 15 週三 201722:42
jvm系列(一):java類的加載機制
文章出處java類的加載機制
類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然后在堆區創建一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位于堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,并且向Java程序員提供了訪問方法區內的數據結構的接口。
類加載器并不需要等到某個類被“首次主動使用”時再加載它,JVM規范允許類加載器在預料某個類將要被使用時就預先加載它,如果在預先加載的過程中遇到了.class文件缺失或存在錯誤,類加載器必須在程序首次主動使用該類時才報告錯誤(LinkageError錯誤)如果這個類一直沒有被程序主動使用,那么類加載器就不會報告錯誤
加載.class文件的方式
– 從本地系統中直接加載
– 通過網絡下載.class文件
– 從zip,jar等歸檔文件中加載.class文件
– 從專有數據庫中提取.class文件
– 將Java源文件動態編譯為.class文件
其中類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。在這五個階段中,加載、驗證、準備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之后開始,這是為了支持Java語言的運行時綁定(也成為動態綁定或晚期綁定)。另外注意這里的幾個階段是按順序開始,而不是按順序進行或完成,因為這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中調用或激活另一個階段。
加載時類加載過程的第一個階段,在加載階段,虛擬機需要完成以下三件事情:
1、通過一個類的全限定名來獲取其定義的二進制字節流。
2、將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
3、在Java堆中生成一個代表這個類的java.lang.Class對象,作為對方法區中這些數據的訪問入口。
相對于類加載的其他階段而言,加載階段(準確地說,是加載階段獲取類的二進制字節流的動作)是可控性最強的階段,因為開發人員既可以使用系統提供的類加載器來完成加載,也可以自定義自己的類加載器來完成加載。
加載階段完成后,虛擬機外部的 二進制字節流就按照虛擬機所需的格式存儲在方法區之中,而且在Java堆中也創建一個java.lang.Class類的對象,這樣便可以通過該對象訪問方法區中的這些數據。
•連接
– 驗證:確保被加載的類的正確性
驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。驗證階段大致會完成4個階段的檢驗動作:
文件格式驗證:驗證字節流是否符合Class文件格式的規范;例如:是否以0xCAFEBABE開頭、主次版本號是否在當前虛擬機的處理范圍之內、常量池中的常量是否有不被支持的類型。
元數據驗證:對字節碼描述的信息進行語義分析(注意:對比javac編譯階段的語義分析),以保證其描述的信息符合Java語言規范的要求;例如:這個類是否有父類,除了java.lang.Object之外。
字節碼驗證:通過數據流和控制流分析,確定程序語義是合法的、符合邏輯的。
符號引用驗證:確保解析動作能正確執行。
驗證階段是非常重要的,但不是必須的,它對程序運行期沒有影響,如果所引用的類經過反復驗證,那么可以考慮采用-Xverifynone參數來關閉大部分的類驗證措施,以縮短虛擬機類加載的時間。
– 準備:為類的靜態變量分配內存,并將其初始化為默認值
準備階段是正式為類變量分配內存并設置類變量初始值的階段,這些內存都將在方法區中分配。對于該階段有以下幾點需要注意:
1、這時候進行內存分配的僅包括類變量(static),而不包括實例變量,實例變量會在對象實例化時隨著對象一塊分配在Java堆中。
2、這里所設置的初始值通常情況下是數據類型默認的零值(如0、0L、null、false等),而不是被在Java代碼中被顯式地賦予的值。
假設一個類變量的定義為:public static int value = 3;
那么變量value在準備階段過后的初始值為0,而不是3,因為這時候尚未開始執行任何Java方法,而把value賦值為3的putstatic指令是在程序編譯后,存放于類構造器<clinit>()方法之中的,所以把value賦值為3的動作將在初始化階段才會執行。
· 這里還需要注意如下幾點:
· 對基本數據類型來說,對于類變量(static)和全局變量,如果不顯式地對其賦值而直接使用,則系統會為其賦予默認的零值,而對于局部變量來說,在使用前必須顯式地為其賦值,否則編譯時不通過。
· 對于同時被static和final修飾的常量,必須在聲明的時候就為其顯式地賦值,否則編譯時不通過;而只被final修飾的常量則既可以在聲明時顯式地為其賦值,也可以在類初始化時顯式地為其賦值,總之,在使用前必須為其顯式地賦值,系統不會為其賦予默認零值。
· 對于引用數據類型reference來說,如數組引用、對象引用等,如果沒有對其進行顯式地賦值而直接使用,系統都會為其賦予默認的零值,即null。
· 如果在數組初始化時沒有對數組中的各元素賦值,那么其中的元素將根據對應的數據類型而被賦予默認的零值。3、如果類字段的字段屬性表中存在ConstantValue屬性,即同時被final和static修飾,那么在準備階段變量value就會被初始化為ConstValue屬性所指定的值。
假設上面的類變量value被定義為: public static final int value = 3;
編譯時Javac將會為value生成ConstantValue屬性,在準備階段虛擬機就會根據ConstantValue的設置將value賦值為3。回憶上一篇博文中對象被動引用的第2個例子,便是這種情況。我們可以理解為static final常量在編譯期就將其結果放入了調用它的類的常量池中
– 解析:把類中的符號引用轉換為直接引用
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程,解析動作主要針對類或接口、字段、類方法、接口方法、方法類型、方法句柄和調用點限定符7類符號引用進行。符號引用就是一組符號來描述目標,可以是任何字面量。
直接引用就是直接指向目標的指針、相對偏移量或一個間接定位到目標的句柄。
•初始化
初始化,為類的靜態變量賦予正確的初始值,JVM負責對類進行初始化,主要對類變量進行初始化。在Java中對類變量進行初始值設定有兩種方式:
②使用靜態代碼塊為類變量指定初始值
JVM初始化步驟
1、假如這個類還沒有被加載和連接,則程序先加載并連接該類
2、假如該類的直接父類還沒有被初始化,則先初始化其直接父類
3、假如類中有初始化語句,則系統依次執行這些初始化語句
類初始化時機:只有當對類的主動使用的時候才會導致類的初始化,類的主動使用包括以下六種:
– 創建類的實例,也就是new的方式
– 訪問某個類或接口的靜態變量,或者對該靜態變量賦值
– 調用類的靜態方法
– 反射(如Class.forName(“com.shengsiyuan.Test”))
– 初始化某個類的子類,則其父類也會被初始化
– Java虛擬機啟動時被標明為啟動類的類(Java Test),直接使用java.exe命令來運行某個主類
結束生命周期
– 執行了System.exit()方法
– 程序正常執行結束
– 程序在執行過程中遇到了異常或錯誤而異常終止
– 由于操作系統出現錯誤而導致Java虛擬機進程終止
package com.neo.classloader;
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
System.out.println(loader);
System.out.println(loader.getParent());
System.out.println(loader.getParent().getParent());
}
}
運行后,輸出結果:
從上面的結果可以看出,并沒有獲取到ExtClassLoader的父Loader,原因是Bootstrap Loader(引導類加載器)是用C語言實現的,找不到一個確定的返回父Loader的方式,于是就返回null。
這幾種類加載器的層次關系如下圖所示:
注意:這里父類加載器并不是通過繼承關系來實現的,而是采用組合實現的。
站在Java虛擬機的角度來講,只存在兩種不同的類加載器:啟動類加載器:它使用C++實現(這里僅限于Hotspot,也就是JDK1.5之后默認的虛擬機,有很多其他的虛擬機是用Java語言實現的),是虛擬機自身的一部分;所有其他的類加載器:這些類加載器都由Java語言實現,獨立于虛擬機之外,并且全部繼承自抽象類java.lang.ClassLoader,這些類加載器需要由啟動類加載器加載到內存中之后才能去加載其他的類。
站在Java開發人員的角度來看,類加載器可以大致劃分為以下三類:
啟動類加載器:Bootstrap ClassLoader,負責加載存放在JDK\jre\lib(JDK代表JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,并且能被虛擬機識別的類庫(如rt.jar,所有的java.*開頭的類均被Bootstrap ClassLoader加載)。啟動類加載器是無法被Java程序直接引用的。
擴展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載DK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的所有類庫(如javax.*開頭的類),開發者可以直接使用擴展類加載器。
應用程序類加載器:Application ClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者可以直接使用該類加載器,如果應用程序中沒有自定義過自己的類加載器,一般情況下這個就是程序中默認的類加載器。
應用程序都是由這三種類加載器互相配合進行加載的,如果有必要,我們還可以加入自定義的類加載器。因為JVM自帶的ClassLoader只是懂得從本地文件系統加載標準的java class文件,因此如果編寫了自己的ClassLoader,便可以做到如下幾點:
1)在執行非置信代碼之前,自動驗證數字簽名。
2)動態地創建符合用戶特定需要的定制化構建類。
3)從特定的場所取得java class,例如數據庫中和網絡中。
•全盤負責,當一個類加載器負責加載某個Class時,該Class所依賴的和引用的其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入
•父類委托,先讓父類加載器試圖加載該類,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類
•緩存機制,緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區尋找該Class,只有緩存區不存在,系統才會讀取該類對應的二進制數據,并將其轉換成Class對象,存入緩存區。這就是為什么修改了Class后,必須重啟JVM,程序的修改才會生效
4、類的加載
類加載有三種方式:
1、命令行啟動應用時候由JVM初始化加載
2、通過Class.forName()方法動態加載
3、通過ClassLoader.loadClass()方法動態加載
例子:
package com.neo.classloader;
public class loaderTest {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader loader = HelloWorld.class.getClassLoader();
System.out.println(loader);
//使用ClassLoader.loadClass()來加載類,不會執行初始化塊
loader.loadClass("Test2");
//使用Class.forName()來加載類,默認會執行初始化塊
// Class.forName("Test2");
//使用Class.forName()來加載類,并指定ClassLoader,初始化時不執行靜態塊
// Class.forName("Test2", false, loader);
}
}
demo類
public class Test2 {
static {
System.out.println("靜態初始化塊執行了!");
}
}
分別切換加載方式,會有不同的輸出結果。
Class.forName()和ClassLoader.loadClass()區別
Class.forName():將類的.class文件加載到jvm中之外,還會對類進行解釋,執行類中的static塊;
ClassLoader.loadClass():只干一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance才會去執行static塊。
注:
Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。并且只有調用了newInstance()方法采用調用構造函數,創建類的對象 。
5、雙親委派模型
雙親委派模型的工作流程是:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把請求委托給父加載器去完成,依次向上,因此,所有的類加載請求最終都應該被傳遞到頂層的啟動類加載器中,只有當父加載器在它的搜索范圍中沒有找到所需的類時,即無法完成該加載,子加載器才會嘗試自己去加載該類。
雙親委派機制:
1、當AppClassLoader加載一個class時,它首先不會自己去嘗試加載這個類,而是把類加載請求委派給父類加載器ExtClassLoader去完成。
2、當ExtClassLoader加載一個class時,它首先也不會自己去嘗試加載這個類,而是把類加載請求委派給BootStrapClassLoader去完成。
3、如果BootStrapClassLoader加載失敗(例如在$JAVA_HOME/jre/lib里未查找到該class),會使用ExtClassLoader來嘗試加載;
4、若ExtClassLoader也加載失敗,則會使用AppClassLoader來加載,如果AppClassLoader也加載失敗,則會報出異常ClassNotFoundException。
ClassLoader源碼分析:
public Class<?> loadClass(String name)throws ClassNotFoundException {
return loadClass(name, false);
}
protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 首先判斷該類型是否已經被加載
Class c = findLoadedClass(name);
if (c == null) {
//如果沒有被加載,就委托給父類加載或者委派給啟動類加載器加載
try {
if (parent != null) {
//如果存在父類加載器,就委派給父類加載器加載
c = parent.loadClass(name, false);
} else {
//如果不存在父類加載器,就檢查是否是由啟動類加載器加載的類,通過調用本地方法native Class findBootstrapClass(String name)
c = findBootstrapClass0(name);
}
} catch (ClassNotFoundException e) {
// 如果父類加載器和啟動類加載器都不能完成加載任務,才調用自身的加載功能
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
雙親委派模型意義:
-保證Java程序安全穩定運行
6、自定義類加載器
通常情況下,我們都是直接使用系統類加載器。但是,有的時候,我們也需要自定義類加載器。比如應用是通過網絡來傳輸 Java 類的字節碼,為保證安全性,這些字節碼經過了加密處理,這時系統類加載器就無法對其進行加載,這樣則需要自定義類加載器來實現。自定義類加載器一般都是繼承自 ClassLoader 類,從上面對 loadClass 方法來分析來看,我們只需要重寫 findClass 方法即可。下面我們通過一個示例來演示自定義類加載器的流程:
package com.neo.classloader;
import java.io.*;
public class MyClassLoader extends ClassLoader {
private String root;
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] loadClassData(String className) {
String fileName = root + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
try {
InputStream ins = new FileInputStream(fileName);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int length = 0;
while ((length = ins.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public String getRoot() {
return root;
}
public void setRoot(String root) {
this.root = root;
}
public static void main(String[] args) {
MyClassLoader classLoader = new MyClassLoader();
classLoader.setRoot("E:\\temp");
Class<?> testClass = null;
try {
testClass = classLoader.loadClass("com.neo.classloader.Test2");
Object object = testClass.newInstance();
System.out.println(object.getClass().getClassLoader());
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
自定義類加載器的核心在于對字節碼文件的獲取,如果是加密的字節碼則需要在該類中對文件進行解密。由于這里只是演示,我并未對class文件進行加密,因此沒有解密的過程。這里有幾點需要注意:
1、這里傳遞的文件名需要是類的全限定性名稱,即com.paddx.test.classloading.Test格式的,因為 defineClass 方法是按這種格式進行處理的。
2、最好不要重寫loadClass方法,因為這樣容易破壞雙親委托模式。
3、這類Test 類本身可以被 AppClassLoader 類加載,因此我們不能把 com/paddx/test/classloading/Test.class 放在類路徑下。否則,由于雙親委托機制的存在,會直接導致該類由 AppClassLoader 加載,而不會通過我們自定義類加載器來加載。
參考:
http://blog.csdn.net/ns_code/article/details/17881581
https://segmentfault.com/a/1190000005608960
http://www.importnew.com/18548.html
http://zyjustin9.iteye.com/blog/2092131
http://www.codeceo.com/article/java-class-loader-learn.html
##如果覺得寫的還可以,請幫忙點擊推薦##
- 3月 15 週三 201722:41
jvm系列(三):java GC算法 垃圾收集器
文章出處GC算法 垃圾收集器
概述
垃圾收集 Garbage Collection 通常被稱為“GC”,它誕生于1960年 MIT 的 Lisp 語言,經過半個多世紀,目前已經十分成熟了。
jvm 中,程序計數器、虛擬機棧、本地方法棧都是隨線程而生隨線程而滅,棧幀隨著方法的進入和退出做入棧和出棧操作,實現了自動的內存清理,因此,我們的內存垃圾回收主要集中于 java 堆和方法區中,在程序運行期間,這部分內存的分配和使用都是動態的.
對象存活判斷
判斷對象是否存活一般有兩種方式:
引用計數:每個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數為0時可以回收。此方法簡單,無法解決對象相互循環引用的問題。
可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。不可達對象。
在Java語言中,GC Roots包括:
虛擬機棧中引用的對象。
方法區中類靜態屬性實體引用的對象。
方法區中常量引用的對象。
本地方法棧中JNI引用的對象。
垃圾收集算法
標記 -清除算法
“標記-清除”(Mark-Sweep)算法,如它的名字一樣,算法分為“標記”和“清除”兩個階段:首先標記出所有需要回收的對象,在標記完成后統一回收掉所有被標記的對象。之所以說它是最基礎的收集算法,是因為后續的收集算法都是基于這種思路并對其缺點進行改進而得到的。
它的主要缺點有兩個:一個是效率問題,標記和清除過程的效率都不高;另外一個是空間問題,標記清除之后會產生大量不連續的內存碎片,空間碎片太多可能會導致,當程序在以后的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
復制算法
“復制”(Copying)的收集算法,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。
這樣使得每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。只是這種算法的代價是將內存縮小為原來的一半,持續復制長生存期的對象則導致效率降低。
標記-壓縮算法
復制收集算法在對象存活率較高時就要執行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
根據老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但后續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內存
分代收集算法
GC分代的基本假設:絕大部分對象的生命周期都非常短暫,存活時間短。
“分代收集”(Generational Collection)算法,把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。
垃圾收集器
如果說收集算法是內存回收的方法論,垃圾收集器就是內存回收的具體實現
Serial收集器
串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。新生代、老年代使用串行回收;新生代復制算法、老年代標記-壓縮;垃圾收集的過程中會Stop The World(服務暫停)
參數控制:-XX:+UseSerialGC 串行收集器
ParNew收集器
ParNew收集器其實就是Serial收集器的多線程版本。新生代并行,老年代串行;新生代復制算法、老年代標記-壓縮
參數控制:-XX:+UseParNewGC ParNew收集器
-XX:ParallelGCThreads 限制線程數量
Parallel收集器
Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關注系統的吞吐量。可以通過參數來打開自適應調節策略,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大的吞吐量;也可以通過參數控制GC的時間不大于多少毫秒或者比例;新生代復制算法、老年代標記-壓縮
參數控制:-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行
Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。這個收集器是在JDK 1.6中才開始提供
參數控制: -XX:+UseParallelOldGC 使用Parallel收集器+ 老年代并行
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的Java應用都集中在互聯網站或B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。
從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基于“標記-清除”算法實現的,它的運作過程相對于前面幾種收集器來說要更復雜一些,整個過程分為4個步驟,包括:
初始標記(CMS initial mark)
并發標記(CMS concurrent mark)
重新標記(CMS remark)
并發清除(CMS concurrent sweep)
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,并發標記階段就是進行GC Roots Tracing的過程,而重新標記階段則是為了修正并發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比并發標記的時間短。
由于整個過程中耗時最長的并發標記和并發清除過程中,收集器線程都可以與用戶線程一起工作,所以總體上來說,CMS收集器的內存回收過程是與用戶線程一起并發地執行。老年代收集器(新生代使用ParNew)
優點:并發收集、低停頓
缺點:產生大量空間碎片、并發階段會降低吞吐量
參數控制:-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,進行一次碎片整理;整理過程是獨占的,會引起停頓時間變長
-XX:+CMSFullGCsBeforeCompaction 設置進行幾次Full GC后,進行一次碎片整理
-XX:ParallelCMSThreads 設定CMS的線程數量(一般情況約等于可用CPU數量)
G1收集器
G1是目前技術發展的最前沿成果之一,HotSpot開發團隊賦予它的使命是未來可以替換掉JDK1.5中發布的CMS收集器。與CMS收集器相比G1收集器有以下特點:
1. 空間整合,G1收集器采用標記整理算法,不會產生內存空間碎片。分配大對象時不會因為無法找到連續空間而提前觸發下一次GC。
2. 可預測停頓,這是G1的另一大優勢,降低停頓時間是G1和CMS的共同關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為N毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經是實時Java(RTSJ)的垃圾收集器的特征了。
上面提到的垃圾收集器,收集的范圍都是整個新生代或者老年代,而G1不再是這樣。使用G1收集器時,Java堆的內存布局與其他收集器有很大差別,它將整個Java堆劃分為多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續)Region的集合。
G1的新生代收集跟ParNew類似,當新生代占用達到一定比例的時候,開始出發收集。和CMS類似,G1收集器收集老年代對象會有短暫停頓。
收集步驟:
1、標記階段,首先初始標記(Initial-Mark),這個階段是停頓的(Stop the World Event),并且會觸發一次普通Mintor GC。對應GC log:GC pause (young) (inital-mark)
2、Root Region Scanning,程序運行過程中會回收survivor區(存活到老年代),這一過程必須在young GC之前完成。
3、Concurrent Marking,在整個堆中進行并發標記(和應用程序并發執行),此過程可能被young GC中斷。在并發標記階段,若發現區域對象中的所有對象都是垃圾,那個這個區域會被立即回收(圖中打X)。同時,并發標記過程中,會計算每個區域的對象活性(區域中存活對象的比例)。
4、Remark, 再標記,會有短暫停頓(STW)。再標記階段是用來收集 并發標記階段 產生新的垃圾(并發階段和應用程序一同運行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
5、Copy/Clean up,多線程清除失活對象,會有STW。G1將回收區域的存活對象拷貝到新區域,清除Remember Sets,并發清空回收區域并把它返回到空閑區域鏈表中。
6、復制/清除過程后。回收區域的活性對象已經被集中回收到深藍色和深綠色區域。
常用的收集器組合
| 新生代GC策略 | 年老代GC策略 | 說明 | |
| 組合1 | Serial | Serial Old | Serial和Serial Old都是單線程進行GC,特點就是GC時暫停所有應用線程。 |
| 組合2 | Serial | CMS+Serial Old | CMS(Concurrent Mark Sweep)是并發GC,實現GC線程和應用線程并發工作,不需要暫停所有應用線程。另外,當CMS進行GC失敗時,會自動使用Serial Old策略進行GC。 |
| 組合3 | ParNew | CMS | 使用-XX:+UseParNewGC選項來開啟。ParNew是Serial的并行版本,可以指定GC線程數,默認GC線程數為CPU的數量。可以使用-XX:ParallelGCThreads選項指定GC的線程數。 如果指定了選項-XX:+UseConcMarkSweepGC選項,則新生代默認使用ParNew GC策略。 |
| 組合4 | ParNew | Serial Old | 使用-XX:+UseParNewGC選項來開啟。新生代使用ParNew GC策略,年老代默認使用Serial Old GC策略。 |
| 組合5 | Parallel Scavenge | Serial Old | Parallel Scavenge策略主要是關注一個可控的吞吐量:應用程序運行時間 / (應用程序運行時間 + GC時間),可見這會使得CPU的利用率盡可能的高,適用于后臺持久運行的應用程序,而不適用于交互較多的應用程序。 |
| 組合6 | Parallel Scavenge | Parallel Old | Parallel Old是Serial Old的并行版本
|
| 組合7 | G1GC | G1GC | -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC #開啟 -XX:MaxGCPauseMillis =50 #暫停時間目標 -XX:GCPauseIntervalMillis =200 #暫停間隔目標 -XX:+G1YoungGenSize=512m #年輕代大小 -XX:SurvivorRatio=6 #幸存區比例 |
參考
http://my.oschina.net/hosee/blog/644618
深入理解Java虛擬機:JVM高級特性與最佳實踐 pdf
下載地址:http://download.csdn.net/detail/ityouknow/9557109
- 3月 15 週三 201722:41
spring boot 實戰:我們的第一款開源軟件
文章出處在信息爆炸時代,如何避免持續性信息過剩,使自己變得專注而不是被紛繁的信息所累?每天會看到各種各樣的新聞,各種新潮的技術層出不窮,如何篩選出自己所關心的?
各位看官會想,我們是來看開源軟件的,你給我扯什么信息干嘛,別著急,聽我慢慢道來。
背景
瀏覽器收藏夾應該是我們在收藏文章、網站的第一個利器,平時遇到喜歡的網站或者文章很方便的收藏到收藏夾中;很快我們的收藏夾就滿了,于是就像我這樣,創建文件夾來分組兩層、三層都有:
有的也會借助百度首頁導航這樣的一些功能來整理自己收藏的網站,以前我記得QQ還有一款產品叫做網絡收藏夾,用過一段時間,后來QQ也把這款產品給淘汰了;也嘗試了去用印象筆記、有道筆記這些產品,這些產品都偏向收藏一些具體的文章或者自己整理的日志信息方面。
當瀏覽器收藏夾收藏的網站或者文章在一百份以內的時候收藏和查找問題都不是特別大。當收藏大于1000份的時候,去查找自己收藏的內容絕對是個體力活,另外還有一些文章我僅僅只是暫時保存下來,準備隨后找時間看看就行,也需要收藏、整理、刪除的時候就很麻煩。
產品介紹
于是在這樣的背景下,我就想著需要做這么一款產品,可以方便隨時隨地的收藏我喜歡的文章或者網站,方便整理,我日后需要的時候非常方便的去檢索,另外如果可以的話,我是否可以分享我自己收藏的文章或者網站,同時也可以看看大牛們或者是同行都收藏了什么文章我是否感興趣,于是就開發了這么一款產品:云收藏
核心功能點:
放產品一些截圖:
主頁
注冊
首頁
收藏
技術點
這段時間我們團隊主要在學習spring boot,這個開源項目也就成了我們的練習新技術的一個非常好的產品,主要的技術都是和spring boot相關,可以參考我以前文章構建微服務:spring boot系列文章
網頁端
網頁端收藏夾主頁
收藏快捷圖標
這個是收藏的最關鍵一步,一段js代碼,拖入到瀏覽器的收藏夾,每次點擊收藏的時候負責讀取網站的title、描述、網址等信息,并且提交到收藏的頁面
前端
前端頁面由Angle - Bootstrap Admin theme這套主題改造而來;模版引擎使用了
thymeleaf,可以參考這篇文章:springboot(四):thymeleaf使用詳解持久層
數據庫主要使用了 Spring data jpa模版來實現,可以參考這篇文章:springbooot(五):spring data jpa的使用
session
session使用持久化技術來保存登錄狀態,登錄一次保持需要會話30天,主要是依賴redis來實現,參考:springboot(三):Spring boot中Redis的使用
其它
使用
grade做為項目的構建工具、使用了一點webjars、vuejs、Jsoup、Scheduled … 客戶端
客戶端技術使用react native來開發安卓和IOS的app,目前還在開發中,完成之后也會開源出來。
未來計劃做的內容
這個開源產品暫時只是開源了我們web端產品,安卓端、IOS端內容的開發還在進行中。
未來我們還會持續的來完善這些產品,做一些有意思的小功能,以下可能是我們近期準備要做的
大家有什么更好玩想法,也可以在建議給我們
產品主頁
產品地址
源碼地址
作者:純潔的微笑
出處:http://www.ityouknow.com/
版權所有,歡迎保留原文鏈接進行轉載:)
- 3月 15 週三 201722:41
jvm系列(六):jvm調優-從eclipse開始
文章出處
jvm調優-從eclipse開始
概述
什么是jvm調優呢?jvm調優就是根據gc日志分析jvm內存分配、回收的情況來調整各區域內存比例或者gc回收的策略;更深一層就是根據dump出來的內存結構和線程棧來分析代碼中不合理的地方給予改進。eclipse優化主要涉及的是前者,通過gc日志來分析。本文主要是通過分析eclipse gc日志為例來示例如何根據gc日志來分析jvm內存而進行調優,像根據關閉eclipse啟動項、關閉各種校驗等措施來優化eclipse本文不再闡述,網上有很多,本次測試的eclipse已經進行了配置上面的優化。
準備環境
eclipse版本:Release 4.5.0
eclipse 默認配置:eclipse.ini
-startup
plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.300.v20150602-1417
-product
org.eclipse.epp.package.jee.product
--launcher.defaultAction
openFile
--launcher.XXMaxPermSize
256M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
256m
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.7
-Xms256m
-Xmx1024m
在配置的末尾處添加如下配置文件:
-XX:+PrintGCDetails // 輸出GC的詳細日志
-XX:+PrintGCDateStamps // 輸出GC的時間戳(以日期的形式)
-Xloggc:gc.log // 輸出GC的詳細日志
eclipse啟動計時插件:
http://www.chendd.cn/information/viewInformation/experienceShare/148.a
GChisto.jar:gc日志分析工具jar包一個
Visual GC: java自帶的內存監控工具,通過visual gc可以實時的監控到各個內存區域的變化。
如何分析GC日志
摘錄GC日志一部分(綠色為年輕代gc回收;藍色為full gc回收):
2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs]
2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
通過上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen屬于Parallel收集器。其中PSYoungGen表示gc回收前后年輕代的內存變化;ParOldGen表示gc回收前后老年代的內存變化;PSPermGen表示gc回收前后永久區的內存變化。young gc 主要是針對年輕代進行內存回收比較頻繁,耗時短;full gc 會對整個堆內存進行回城,耗時長,因此一般盡量減少full gc的次數
通過兩張圖非常明顯看出gc日志構成:
young gc 日志
Full GC日志
啟動調優
啟動eclipse查看默認配置下啟動時間大概是22秒。
根據GChisto分析gc日志看出來,啟動過程中進行了一次full gc,19次minor gc;full gc和young gc的時間差不多都是0.65秒左右。
第一步優化:
為了避免內存頻繁的動態擴展,直接把-Xms配置和-Xmx一致,修改如下:
-Xms1024m
修改完畢,重新啟動:
啟動時間縮小到17秒,分析gc日志得出young gc22次,full gc沒有了! 但是young gc增加了兩次。
第二步優化:
因為本機的內存8G,給eclipse分配1g還是有點小了,簡單粗暴直接所有內存配置加倍。
配置如下:
--launcher.XXMaxPermSize
512M
--launcher.XXMaxPermSize
512m
-Xms2048m
-Xmx2048m
啟動時間縮小到15秒,但是 young gc已經縮短到只有7次,說明因為gc回收導致eclipse 啟動慢的問題已經初步解決
第三步優化:
通過Visual GC看到在eclipse啟動的時候classloader加載class的時間有一些,關閉字節碼可能會優化一部分啟動時間,加入如下參數:
-Xverify:none(關閉Java字節碼驗證,從而加快了類裝入的速度)
重新啟動測試,啟動時間已經優化到了9秒!
查看啟動日志,young gc 的次數僅僅只有了一次!
至此優化結束,附最終的eclipse.ini文件
-startup
plugins/org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.1.300.v20150602-1417
-product
org.eclipse.epp.package.jee.product
--launcher.defaultAction
openFile
--launcher.XXMaxPermSize
512M
-showsplash
org.eclipse.platform
--launcher.XXMaxPermSize
512m
--launcher.defaultAction
openFile
--launcher.appendVmargs
-vmargs
-Dosgi.requiredJavaVersion=1.7
-Xms2048m
-Xmx2048m
-Xverify:none
-XX:+PrintGCDetails
-XX:+PrintGCDateStamps
-Xloggc:gc.log
- 3月 15 週三 201722:41
混合應用技術選型
混合應用技術選型
越來越多的公司選擇使用 hybrid開發模式來做app,我司在一些個別項目上也采用了這樣的架構,目前來看整體還可以,但是也是有很多的坑要去填。
目前開發APP的三種形式:
1、客戶端(純原生)+服務端(php/java/...)
2、客戶端(原生+html5)+服務端(php/java/...)
3、客戶端(webap(SPA))+服務端(node環境+vue/react/...)
方案一應該是目前主流公司的標準開發模式,我們公司以前的產品也大都以這種方式來進行;
優點:體驗好,客戶端操作穩定;
缺點:發布版本升級太麻煩
方案二采用原生+h5的方式來配合,使用的原則是用戶使用的一級頁面或者主框架頁面采用原生語言來開發,比如:登錄、首頁、我的賬戶首頁等;所有的二級頁面均采用H5開發。
優點:開發速度快,升級方便
缺點:原生和H5交互比較復雜
方案三使用一些SPA框架,比如vue、react等。利用這些框架以組件的形式進行開發頁面切換成不同的組件有利于復用,服務端必須使用node環境包裝一層再和服務端交互,或者直接使用node做服務端
優點:開發速度快,頁面利用率高
缺點:目前來開沒有網絡等情況交互不友好,生產使用情況很少
我們根據調研結果采用了第二種方案,但是使用第二種方案又會面臨很多問題:
1、原生和H5用戶狀態如何來同步?
2、原生和H5如何相互交互使用
3、如何保證信息不泄露
解決方案:
1、用戶登錄后下發token,用戶在客戶端登錄后將token存儲,原生請求帶上token;每次點擊H5頁面請求的時候將token存入cookie,瀏覽器會自動帶上cookie以識別用戶狀態。
2、使用WebViewJavascriptBridge來解決H5調用原生接口的問題。
- 3月 15 週三 201722:41
jvm系列(二):JVM內存結構
文章出處JVM內存結構
所有的Java開發人員可能會遇到這樣的困惑?我該為堆內存設置多大空間呢?OutOfMemoryError的異常到底涉及到運行時數據的哪塊區域?該怎么解決呢?其實如果你經常解決服務器性能問題,那么這些問題就會變的非常常見,了解JVM內存也是為了服務器出現性能問題的時候可以快速的了解那塊的內存區域出現問題,以便于快速的解決生產故障。
先看一張圖,這張圖能很清晰的說明JVM內存結構布局。
Java的內存結構:
JVM內存結構主要有三大塊:堆內存、方法區和棧。堆內存是JVM中最大的一塊由年輕代和老年代組成,而年輕代內存又被分成三部分,Eden空間、From Survivor空間、To Survivor空間,默認情況下年輕代按照8:1:1的比例來分配;
方法區存儲類信息、常量、靜態變量等數據,是線程共享的區域,為與Java堆區分,方法區還有一個別名Non-Heap(非堆);棧又分為java虛擬機棧和本地方法棧主要用于方法的執行。
在通過一張圖來了解如何通過參數來控制各區域的內存大小
控制參數
-Xms設置堆的最小空間大小。
-XX:NewSize設置新生代最小空間大小。
-XX:MaxNewSize設置新生代最大空間大小。
-Xss設置每個線程的堆棧大小。
沒有直接設置老年代的參數,但是可以設置堆空間大小和新生代空間大小兩個參數來間接控制。
老年代空間大小=堆空間大小-年輕代大空間大小
從更高的一個維度再次來看JVM和系統調用之間的關系
方法區和對是所有線程共享的內存區域;而java棧、本地方法棧和程序員計數器是運行是線程私有的內存區域。
下面我們詳細介紹每個區域的作用
Java堆(Heap)
對于大多數應用來說,Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這里分配內存。
Java堆是垃圾收集器管理的主要區域,因此很多時候也被稱做“GC堆”。如果從內存回收的角度看,由于現在收集器基本都是采用的分代收集算法,所以Java堆中還可以細分為:新生代和老年代;再細致一點的有Eden空間、From Survivor空間、To Survivor空間等。
根據Java虛擬機規范的規定,Java堆可以處于物理上不連續的內存空間中,只要邏輯上是連續的即可,就像我們的磁盤空間一樣。在實現時,既可以實現成固定大小的,也可以是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(通過-Xmx和-Xms控制)。
如果在堆中沒有內存完成實例分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常。
方法區(Method Area)
方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。雖然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。
對于習慣在HotSpot虛擬機上開發和部署程序的開發者來說,很多人愿意把方法區稱為“永久代”(Permanent Generation),本質上兩者并不等價,僅僅是因為HotSpot虛擬機的設計團隊選擇把GC分代收集擴展至方法區,或者說使用永久代來實現方法區而已。
Java虛擬機規范對這個區域的限制非常寬松,除了和Java堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集。相對而言,垃圾收集行為在這個區域是比較少出現的,但并非數據進入了方法區就如永久代的名字一樣“永久”存在了。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,一般來說這個區域的回收“成績”比較難以令人滿意,尤其是類型的卸載,條件相當苛刻,但是這部分區域的回收確實是有必要的。
根據Java虛擬機規范的規定,當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。
程序計數器(Program Counter Register)
程序計數器(Program Counter Register)是一塊較小的內存空間,它的作用可以看做是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型里(僅是概念模型,各種虛擬機可能會通過一些更高效的方式去實現),字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都需要依賴這個計數器來完成。
由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內核)只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存。
如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Natvie方法,這個計數器值則為空(Undefined)。
此內存區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域。
JVM棧(JVM Stacks)
與程序計數器一樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用于存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中從入棧到出棧的過程。
局部變量表存放了編譯期可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference類型,它不等同于對象本身,根據不同的虛擬機實現,它可能是一個指向對象起始地址的引用指針,也可能指向一個代表對象的句柄或者其他與此對象相關的位置)和returnAddress類型(指向了一條字節碼指令的地址)。
其中64位長度的long和double類型的數據會占用2個局部變量空間(Slot),其余的數據類型只占用1個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法需要在幀中分配多大的局部變量空間是完全確定的,在方法運行期間不會改變局部變量表的大小。
在Java虛擬機規范中,對這個區域規定了兩種異常狀況:如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常;如果虛擬機棧可以動態擴展(當前大部分的Java虛擬機都可動態擴展,只不過Java虛擬機規范中也允許固定長度的虛擬機棧),當擴展時無法申請到足夠的內存時會拋出OutOfMemoryError異常。
本地方法棧(Native Method Stacks)
本地方法棧(Native Method Stacks)與虛擬機棧所發揮的作用是非常相似的,其區別不過是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是為虛擬機使用到的Native方法服務。虛擬機規范中對本地方法棧中的方法使用的語言、使用方式與數據結構并沒有強制規定,因此具體的虛擬機可以自由實現它。甚至有的虛擬機(譬如Sun HotSpot虛擬機)直接就把本地方法棧和虛擬機棧合二為一。與虛擬機棧一樣,本地方法棧區域也會拋出StackOverflowError和OutOfMemoryError異常。
參考:
http://ifeve.com/under-the-hood-runtime-data-areas-javas-memory-model/
《深入理解Java虛擬機:JVM高級特性與最佳實踐_周志明.高清掃描版.pdf》
下載地址:http://download.csdn.net/detail/ityouknow/9557109
- 3月 15 週三 201722:41
spring boot(五):spring data jpa的使用
在上篇文章springboot(二):web綜合開發中簡單介紹了一下spring data jpa的基礎性使用,這篇文章將更加全面的介紹spring data jpa 常見用法以及注意事項
使用spring data jpa 開發時,發現國內對spring boot jpa全面介紹的文章比較少案例也比較零碎,因此寫文章總結一下。本人也正在翻譯Spring Data JPA 參考指南,有興趣的同學歡迎聯系我,一起加入翻譯中!
spring data jpa介紹
首先了解JPA是什么?
JPA(Java Persistence API)是Sun官方提出的Java持久化規范。它為Java開發人員提供了一種對象/關聯映射工具來管理Java應用中的關系數據。他的出現主要是為了簡化現有的持久化開發工作和整合ORM技術,結束現在Hibernate,TopLink,JDO等ORM框架各自為營的局面。值得注意的是,JPA是在充分吸收了現有Hibernate,TopLink,JDO等ORM框架的基礎上發展而來的,具有易于使用,伸縮性強等優點。從目前的開發社區的反應上看,JPA受到了極大的支持和贊揚,其中就包括了Spring與EJB3.0的開發團隊。
注意:JPA是一套規范,不是一套產品,那么像Hibernate,TopLink,JDO他們是一套產品,如果說這些產品實現了這個JPA規范,那么我們就可以叫他們為JPA的實現產品。
spring data jpa
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 規范的基礎上封裝的一套JPA應用框架,可使開發者用極簡的代碼即可實現對數據的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易于擴展!學習并使用 Spring Data JPA 可以極大提高開發效率!
spring data jpa讓我們解脫了DAO層的操作,基本上所有CRUD都可以依賴于它來實現
基本查詢
基本查詢也分為兩種,一種是spring data默認已經實現,一種是根據查詢的方法來自動解析成SQL。
預先生成方法
spring data jpa 默認預先生成了一些基本的CURD的方法,例如:增、刪、改等等
1 繼承JpaRepository
public interface UserRepository extends JpaRepository<User, Long> {
}2 使用默認方法
@Test
public void testBaseQuery() throws Exception {
User user=new User();
userRepository.findAll();
userRepository.findOne(1l);
userRepository.save(user);
userRepository.delete(user);
userRepository.count();
userRepository.exists(1l);
// ...
}就不解釋了根據方法名就看出意思來
自定義簡單查詢
自定義的簡單查詢就是根據方法名來自動生成SQL,主要的語法是
findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy后面跟屬性名稱:User findByUserName(String userName);也使用一些加一些關鍵字
And、 OrUser findByUserNameOrEmail(String username, String email);修改、刪除、統計也是類似語法
Long deleteById(Long id);
Long countByUserName(String userName)基本上SQL體系中的關鍵詞都可以使用,例如:
LIKE、 IgnoreCase、 OrderBy。List<User> findByEmailLike(String email);
User findByUserNameIgnoreCase(String userName);
List<User> findByUserNameOrderByEmailDesc(String email);具體的關鍵字,使用方法和生產成SQL如下表所示
復雜查詢
在實際的開發中我們需要用到分頁、刪選、連表等查詢的時候就需要特殊的方法或者自定義SQL
分頁查詢
分頁查詢在實際使用中非常普遍了,spring data jpa已經幫我們實現了分頁的功能,在查詢的方法中,需要傳入參數
Pageable,當查詢中有多個參數的時候
Pageable建議做為最后一個參數傳入Page<User> findALL(Pageable pageable);
Page<User> findByUserName(String userName,Pageable pageable);Pageable 是spring封裝的分頁實現類,使用的時候需要傳入頁數、每頁條數和排序規則@Test
public void testPageQuery() throws Exception {
int page=1,size=10;
Sort sort = new Sort(Direction.DESC, "id");
Pageable pageable = new PageRequest(page, size, sort);
userRepository.findALL(pageable);
userRepository.findByUserName("testName", pageable);
}限制查詢
有時候我們只需要查詢前N個元素,或者支取前一個實體。
ser findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);自定義SQL查詢
其實Spring data 覺大部分的SQL都可以根據方法名定義的方式來實現,但是由于某些原因我們想使用自定義的SQL來查詢,spring data也是完美支持的;在SQL的查詢方法上面使用
@Query注解,如涉及到刪除和修改在需要加上@Modifying.也可以根據需要添加 @Transactional 對事物的支持,查詢超時的設置等@Modifying
@Query("update User u set u.userName = ?1 where c.id = ?2")
int modifyByIdAndUserId(String userName, Long id);
@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteByUserId(Long id);
@Transactional(timeout = 10)
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);多表查詢
多表查詢在spring data jpa中有兩種實現方式,第一種是利用hibernate的級聯查詢來實現,第二種是創建一個結果集的接口來接收連表查詢后的結果,這里主要第二種方式。
首先需要定義一個結果集的接口類。
public interface HotelSummary {
City getCity();
String getName();
Double getAverageRating();
default Integer getAverageRatingRounded() {
return getAverageRating() == null ? null : (int) Math.round(getAverageRating());
}
}查詢的方法返回類型設置為新創建的接口
@Query("select h.city as city, h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r where h.city = ?1 group by h")
Page<HotelSummary> findByCity(City city, Pageable pageable);
@Query("select h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r group by h")
Page<HotelSummary> findByCity(Pageable pageable);使用
Page<HotelSummary> hotels = this.hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name"));
for(HotelSummary summay:hotels){
System.out.println("Name" +summay.getName());
}在運行中Spring會給接口(HotelSummary)自動生產一個代理類來接收返回的結果,代碼匯總使用getXX的形式來獲取
多數據源的支持
同源數據庫的多源支持
日常項目中因為使用的分布式開發模式,不同的服務有不同的數據源,常常需要在一個項目中使用多個數據源,因此需要配置sping data jpa對多數據源的使用,一般分一下為三步:
這里有一篇文章寫的很清楚:Spring Boot多數據源配置與使用
異構數據庫多源支持
比如我們的項目中,即需要對mysql的支持,也需要對mongodb的查詢等。
實體類聲明
@Entity 關系型數據庫支持類型、聲明@Document 為mongodb支持類型,不同的數據源使用不同的實體就可以了interface PersonRepository extends Repository<Person, Long> {
…
}
@Entity
public class Person {
…
}
interface UserRepository extends Repository<User, Long> {
…
}
@Document
public class User {
…
}但是,如果User用戶既使用mysql也使用mongodb呢,也可以做混合使用
interface JpaPersonRepository extends Repository<Person, Long> {
…
}
interface MongoDBPersonRepository extends Repository<Person, Long> {
…
}
@Entity
@Document
public class Person {
…
}也可以通過對不同的包路徑進行聲明,比如A包路徑下使用mysql,B包路徑下使用mongoDB
@EnableJpaRepositories(basePackages = "com.neo.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.neo.repositories.mongo")
interface Configuration { }其它
使用枚舉
使用枚舉的時候,我們希望數據庫中存儲的是枚舉對應的String類型,而不是枚舉的索引值,需要在屬性上面添加
@Enumerated(EnumType.STRING) 注解@Enumerated(EnumType.STRING)
@Column(nullable = true)
private UserType type;不需要和數據庫映射的屬性
正常情況下我們在實體類上加入注解
@Entity,就會讓實體類和表相關連如果其中某個屬性我們不需要和數據庫來關聯只是在展示的時候做計算,只需要加上@Transient屬性既可。@Transient
private String userName;源碼案例
這里有一個開源項目幾乎使用了這里介紹的所有標簽和布局,大家可以參考:
cloudfavorites
參考
Spring Data JPA - Reference Documentation
Spring Data JPA——參考文檔 中文版
作者:純潔的微笑
出處:http://www.ityouknow.com/
版權所有,歡迎保留原文鏈接進行轉載:)
























