當前位置:首頁>職場>jvm面試題匯總(JVM三種核心內容有)
發布時間:2024-01-24閱讀(16)
這章節主要從實戰角度方面,去解讀JVM。
問:JVM三種核心內容有哪一些?
答:類加載機制 JVM調優實戰 代碼優化
jvm知識圖譜:

Java源代碼經過編譯器編譯成字節碼之后,最終都需要加載到虛擬機之后才能運行。虛擬機把描述類的數據從Class 文件加載到內存,并對數據進行校驗、轉換解析和初始化,最終形成可以被虛擬機直接使用的Java 類型,這就是虛擬機的類加載機制。
2.1 類加載時機一個類型從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期將會經歷加載(Loading)、驗證(Verification)、準備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸載(Unloading)七個階段,其中驗證、準備、解析三個部分統稱為連接(Linking)。這七個階段的發生順序下圖所示。

上圖中,加載、驗證、準備、初始化和卸載這五個階段的順序是確定的,類型的加載過程必須按照這種順序按部就班地開始,而解析階段則不一定:它在某些情況下可以在初始化階段之后再開始,這是為了支持Java語言的運行時綁定特性(也稱為動態綁定或晚期綁定)。
關于在什么情況下需要開始類加載過程的第一個階段“加載”,《Java虛擬機規范》中并沒有進行強制約束,這點可以交給虛擬機的具體實現來自由把握。
但是對于初始化階段,《Java虛擬機規范》則是嚴格規定了有且只有六種情況必須立即對類進行“初始化”(而加載、驗證、準備自然需要在此之前開始):
對于這六種會觸發類型進行初始化的場景,《Java虛擬機規范》中使用了一個非常強烈的限定語——“有且只有”,這六種場景中的行為稱為對一個類型進行主動引用。除此之外,所有引用類型的方式都不會觸發初始化,稱為被動引用。
比如如下幾種場景就是被動引用:
加載
在加載階段,Java虛擬機需要完成以下三件事情:
通過一個類的全限定名來獲取定義此類的二進制字節流。
將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
在內存中生成一個代表這個類的java.lang. Class對象,作為方法區這個類的各種數據的訪問入口。
驗證
驗證是連接階段的第一步,這一階段的目的是確保Class文件的字節流中包含的信息符合《Java虛擬機規范》的全部約束要求,保證這些信息被當作代碼運行后不會危害虛擬機自身的安全。
驗證階段大致上會完成下面4 個階段的檢驗動作:
準備
準備階段是正式為類變量分配內存并設置類變量初始值的階段。
解析
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。
初始化
類初始化階段是類加載過程中的最后一步,前面的類加載過程中,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全是由虛擬機主導和控制的。
到了初始化階段,才真正開始執行類中定義的Java 程序代碼。
2.3 類加載器類加載器雖然只用于實現類的加載動作,但它在Java程序中起到的作用卻遠超類加載階段。
對于任意一個類,都必須由加載它的類加載器和這個類本身一起共同確立其在Java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。
這句話可以表達得更通俗一些:比較兩個類是否“相等”,只有在這兩個類是由同一個類加載器加載的前提下才有意義,否則,即使這兩個類來源于同一個Class文件,被同一個Java虛擬機加載,只要加載它們的類加載器不同,那這兩個類就必定不相等。
雙親委派模型
從Java 虛擬機的角度來講,只存在兩種不同的類加載器:一種是啟動類加載器(Bootstrap ClassLoader),這個類加載器使用C 來實現,是虛擬機自身的一部分;另一種就是所有其他的類加載器,這些類加載器都由Java 來實現,獨立于虛擬機外部,并且全都繼承自抽象類 java.lang.ClassLoader 。
從Java 開發者的角度來看,類加載器可以劃分為:
我們的應用程序都是由這3 種類加載器互相配合進行加載的,在必要時還可以自己定義類加載器。它們的關系如下圖所示:

雙親委派模型要求除了頂層的啟動類加載器外,其余的類加載器都應有自己的父類加載器。
雙親委派模型的工作過程是:
這樣做的好處就是Java 類隨著它的類加載器一起具備了一種帶有優先級的層次關系。例如java.lang. Object,它放在rt.jar 中,無論哪一個類加載器要加載這個類,最終都是委派給處于模型頂端的啟動類加載器來加載,因此Object 類在程序的各種類加載器環境中都是同一個類。
相反,如果沒有使用雙親委派模型,由各個類加載器自行去加載的話,如果用戶自己編寫了一個稱為java.lang. Object 的類,并放在程序的ClassPath 中,那系統中將會出現多個不同的Object 類,Java 類型體系中最基本的行為也就無法保證了。
雙親委派模型對于保證Java程序的穩定運作極為重要,但它的實現卻異常簡單,用以實現雙親委派的代碼只有短短十余行,全部集中在java.lang. ClassLoader的loadClass()方法之中:
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 { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // 如果父類拋出 ClassNotFoundException 說明父類加載器無法完成加載 } if (c == null) { // 如果父類加載器無法加載,則調用自己的 findClass 方法來進行類加載 c = findClass(name); } } if (resolve) { resolveClass(c); } return c;}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.
在jvm中有很多的參數可以進行設置,這樣可以讓jvm在各種環境中都能夠高效的運行。絕大部分的參數保持默認即可。
三種參數類型
-X參數
jvm的-X參數是非標準參數,在不同版本的jvm中,參數可能會有所不同,可以通過java -X查看非標準參數。
-XX參數
-XX參數也是非標準參數,主要用于jvm的調優和debug操作。
-XX參數的使用有2種方式,一種是boolean類型,一種是非boolean類型:
-Xms和-Xmx參數
-Xms與-Xmx分別是設置jvm的堆內存的初始大小和最大大小。-Xmx2048m:等價于-XX:MaxHeapSize,設置JVM最大堆內存為2048M。-Xms512m:等價于-XX:InitialHeapSize,設置JVM初始堆內存為512M。適當的調整jvm的內存大小,可以充分利用服務器資源,讓程序跑得更快。示例:
[root@node01 test]# java -Xms512m -Xmx2048m TestJVMitcast1.2.
jstat
jstat命令可以查看堆內存各部分的使用量,以及加載類的數量。命令的格式如下:jstat [-命令選項] [vmid] [間隔時間/毫秒] [查詢次數]
查看class加載統計
F:	>jstat -class 12076Loaded Bytes Unloaded Bytes Time 5962 10814.2 0 0.0 3.751.2.3.
說明:Loaded:加載class的數量Bytes:所占用空間大小Unloaded:未加載數量Bytes:未加載占用空間Time:時間
查看編譯統計
F:	>jstat -compiler 12076Compiled Failed Invalid Time FailedType FailedMethod 3115 0 0 3.43 01.2.3.
說明:Compiled:編譯數量。Failed:失敗數量Invalid:不可用數量Time:時間FailedType:失敗類型FailedMethod:失敗的方法
垃圾回收統計
F:	>jstat -gc 12076S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT3584.0 6656.0 3412.1 0.0 180224.0 89915.4 61440.0 5332.1 27904.0 26267.3 3840.0 3420.8 6 0.036 1 0.026 0.062#也可以指定打印的間隔和次數,每1秒中打印一次,共打印5次F:	>jstat -gc 12076 1000 5S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT3584.0 6656.0 3412.1 0.0 180224.0 89915.4 61440.0 5332.1 27904.0 26267.3 3840.0 3420.8 6 0.036 1 0.026 0.0623584.0 6656.0 3412.1 0.0 180224.0 89915.4 61440.0 5332.1 27904.0 26267.3 3840.0 3420.8 6 0.036 1 0.026 0.0623584.0 6656.0 3412.1 0.0 180224.0 89915.4 61440.0 5332.1 27904.0 26267.3 3840.0 3420.8 6 0.036 1 0.026 0.0623584.0 6656.0 3412.1 0.0 180224.0 89915.4 61440.0 5332.1 27904.0 26267.3 3840.0 3420.8 6 0.036 1 0.026 0.0623584.0 6656.0 3412.1 0.0 180224.0 89915.4 61440.0 5332.1 27904.0 26267.3 3840.0 3420.8 6 0.036 1 0.026 0.0621.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.
說明:S0C:第一個Survivor區的大小(KB)S1C:第二個Survivor區的大小(KB)S0U:第一個Survivor區的使用大小(KB)S1U:第二個Survivor區的使用大小(KB)EC:Eden區的大小(KB)EU:Eden區的使用大小(KB)OC:Old區大小(KB)OU:Old使用大小(KB)MC:方法區大小(KB)MU:方法區使用大小(KB)CCSC:壓縮類空間大小(KB)CCSU:壓縮類空間使用大小(KB)YGC:年輕代垃圾回收次數YGCT:年輕代垃圾回收消耗時間FGC:老年代垃圾回收次數FGCT:老年代垃圾回收消耗時間GCT:垃圾回收消耗總時間
3.2 Jmap的使用以及內存溢出分析前面通過jstat可以對jvm堆的內存進行統計分析,而jmap可以獲取到更加詳細的內容,如:內存使用情況的匯總、對內存溢出的定位與分析。
查看內存使用情況
[root@node01 ~]# jmap -Heap 6219Attaching to process ID 6219, please wait... Debugger attached successfully.Server compiler detected.JVM version is 25.141-b15using thread-local object allocation.Parallel GC with 2 thread(s)Heap Configuration: #堆內存配置信息MinHeapFreeRatio = 0MaxHeapFreeRatio = 100MaxHeapSize = 488636416 (466.0MB)NewSize = 10485760 (10.0MB)MaxNewSize = 162529280 (155.0MB)OldSize = 20971520 (20.0MB)NewRatio = 2SurvivorRatio = 8MetaspaceSize = 21807104 (20.796875MB)CompressedClassSpaceSize = 1073741824 (1024.0MB)MaxMetaspaceSize = 17592186044415 MBG1HeapRegionSize = 0 (0.0MB)Heap Usage: # 堆內存的使用情況PS Young Generation #年輕代Eden Space:capacity = 123731968 (118.0MB)used = 1384736 (1.320587158203125MB)free = 122347232 (116.67941284179688MB)1.1191416594941737% usedFrom Space:capacity = 9437184 (9.0MB)used = 0 (0.0MB)free = 9437184 (9.0MB)0.0% usedTo Space:capacity = 9437184 (9.0MB)used = 0 (0.0MB)free = 9437184 (9.0MB)0.0% usedPS Old Generation #年老代capacity = 28311552 (27.0MB)used = 13698672 (13.064071655273438MB)free = 14612880 (13.935928344726562MB)48.38545057508681% used13648 interned Strings occupying 1866368 bytes.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.
查看內存中對象數量及大小
#查看所有對象,包括活躍以及非活躍的jmap -histo <pid> | more#查看活躍對象 jmap -histo:live <pid> | more[root@node01 ~]# jmap -histo:live 6219 | morenum #instances #bytes class name----------------------------------------------1: 37437 7914608 [C2: 34916 837984 java.lang.String3: 884 654848 [B4: 17188 550016 java.util.HashMap$Node5: 3674 424968 java.lang.Class6: 6322 395512 [Ljava.lang.Object;7: 3738 328944 java.lang.reflect.Method8: 1028 208048 [Ljava.util.HashMap$Node;9: 2247 144264 [I10: 4305 137760 java.util.concurrent.ConcurrentHashMap$Node11: 1270 109080 [Ljava.lang.String;12: 64 84128 [Ljava.util.concurrent.ConcurrentHashMap$Node;13: 1714 82272 java.util.HashMap14: 3285 70072 [Ljava.lang.Class;15: 2888 69312 java.util.ArrayList16: 3983 63728 java.lang.Object17: 1271 61008 org.apache.tomcat.util.digester.CallMethodRule18: 1518 60720 java.util.LinkedHashMap$Entry19: 1671 53472 com.sun.org.apache.xerces.internal.xni.QName20: 88 50880 [Ljava.util.WeakHashMap$Entry;21: 618 49440 java.lang.reflect.Constructor22: 1545 49440 java.util.Hashtable$Entry23: 1027 41080 java.util.TreeMap$Entry24: 846 40608 org.apache.tomcat.util.modeler.AttributeInfo25: 142 38032 [S26: 946 37840 java.lang.ref.SoftReference27: 226 36816 [[C。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。#對象說明B byteC charD doubleF floatI intJ longZ boolean[ 數組,如[I表示int[][L 類名 其他對象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.
將內存使用情況dump到文件中
#用法:jmap -dump:format=b,file=dumpFileName <pid>#示例jmap -dump:format=b,file=/tmp/dump.dat 62191.2.3.4.

可以看到已經在/tmp下生成了dump.dat的文件。
通過jhat對dump文件進行分析
在上一小節中,我們將jvm的內存dump到文件中,這個文件是一個二進制的文件,不方便查看,這時我們可以借助于jhat工具進行查看。
#用法:jhat -port <port> <file>#示例:[root@node01 tmp]# jhat -port 9999 /tmp/dump.dat Reading from /tmp/dump.dat...Dump file created Mon Sep 10 01:04:21 CST 2018Snapshot read, resolving...Resolving 204094 objects...Chasing references, expect 40 dots........................................Eliminating duplicate references........................................Snapshot resolved.Started HTTP server on port 9999Server is ready.1.2.3.4.5.6.7.8.9.10.11.12.13.
打開瀏覽器進行訪問:http://192.168.40.133:9999/

在最后由OQL查詢功能


使用MAT對內存溢出的定位與分析
內存溢出在實際的生產環境中經常會遇到,比如,不斷地將數據寫入到一個集合中,出現了死循環,讀取超大的文件等等,都可能會造成內存溢出。
如果出現了內存溢出,首先我們需要定位到發生內存溢出的環節,并且進行分析,是正常還是非正常情況,如果是正常的需求,就應該考慮加大內存的設置,如果是非正常需求,那么就要對代碼進行修改,修復這個bug。首先,我們得先學會如何定位問題,然后再進行分析。如何定位問題呢,我們需要借助于jmap與MAT工具進行定位分析。
接下來,我們模擬內存溢出的場景。
模擬內存溢出
編寫代碼,向List集合中添加100萬個字符串,每個字符串由1000個UUID組成。如果程序能夠正常執行,最后打印ok。
public class TestJvmOutOfMemory {public static void main(String[] args) { List<Object> list = new ArrayList<>(); for (int i = 0; i < 10000000; i ) { String str = ""; for (int j = 0; j < 1000; j ) { str = UUID.randomUUID().toString(); } list.add(str); } System.out.println("ok"); }}1.2.3.4.5.6.7.8.9.10.11.12.13.
為了演示效果,我們將設置執行的參數
#參數如下:-Xms8m -Xmx8m -XX: HeapDumpOnOutOfMemoryError1.2.
運行測試
java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid5348.hprof ...Heap dump file created [8137186 bytes in 0.032 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Arrays.java:3332)atjava.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)at java.lang.StringBuilder.append(StringBuilder.java:136)at cn.itcast.jvm.TestJvmOutOfMemory.main(TestJvmOutOfMemory.java:14)Process finished with exit code 11.2.3.4.5.6.7.8.9.10.11.
可以看到,當發生內存溢出時,會dump文件到java_pid5348.hprof。

導入到MA T工具中進行分析

可以看到,有91.03%的內存由Object[]數組占有,所以比較可疑。分析:這個可疑是正確的,因為已經有超過90%的內存都被它占有,這是非常有可能出現內存溢出的。

可以看到集合中存儲了大量的uuid字符串
3.4 Jsatck的使用有些時候我們需要查看下jvm中的線程執行情況,比如,發現服務器的CPU的負載突然增高了、出現了死鎖、死循環等,我們該如何分析呢?
由于程序是正常運行的,沒有任何的輸出,從日志方面也看不出什么問題,所以就需要看下jvm的內部線程的執行情況,然后再進行分析查找出原因。
這個時候,就需要借助于jstack命令了,jstack的作用是將正在運行的jvm的線程情況進行快照,并且打印出來:
#用法:jstack <pid>[root@node01 bin]# jstack 2203Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.141-b15 mixed mode):"Attach Listener" #24 daemon prio=9 os_prio=0 tid=0x00007fabb4001000 nid=0x906 waiting oncondition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"http-bio-8080-exec-5" #23 daemon prio=5 os_prio=0 tid=0x00007fabb057c000 nid=0x8e1waiting on condition [0x00007fabd05b8000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x00000000f8508360> (ajava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) atjava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)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:1074)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)atorg.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:748)"http-bio-8080-exec-4" #22 daemon prio=5 os_prio=0 tid=0x00007fab9c113800 nid=0x8e0waiting on condition [0x00007fabd06b9000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x00000000f8508360> (ajava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)atjava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)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:1074)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)atorg.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:748)"http-bio-8080-exec-3" #21 daemon prio=5 os_prio=0 tid=0x0000000001aeb800 nid=0x8dfwaiting on condition [0x00007fabd09ba000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x00000000f8508360> (ajava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)atjava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)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:1074)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)atorg.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:748)"http-bio-8080-exec-2" #20 daemon prio=5 os_prio=0 tid=0x0000000001aea000 nid=0x8dewaiting on condition [0x00007fabd0abb000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x00000000f8508360> (ajava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)atjava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)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:1074)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)atorg.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:748)"http-bio-8080-exec-1" #19 daemon prio=5 os_prio=0 tid=0x0000000001ae8800 nid=0x8ddwaiting on condition [0x00007fabd0bbc000]java.lang.Thread.State: WAITING (parking)at sun.misc.Unsafe.park(Native Method)- parking to wait for <0x00000000f8508360> (ajava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)atjava.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)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:1074)atjava.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)atjava.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)atorg.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)at java.lang.Thread.run(Thread.java:748)"ajp-bio-8009-AsyncTimeout" #17 daemon prio=5 os_prio=0 tid=0x00007fabe8128000 nid=0x8d0waiting on condition [0x00007fabd0ece000]java.lang.Thread.State: TIMED_WAITING (sleeping) at java.lang.Thread.sleep(Native Method)at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:152)at java.lang.Thread.run(Thread.java:748)"ajp-bio-8009-Acceptor-0" #16 daemon prio=5 os_prio=0 tid=0x00007fabe82d4000 nid=0x8cfrunnable [0x00007fabd0fcf000]java.lang.Thread.State: RUNNABLEat java.net.PlainSocketImpl.socketAccept(Native Method)at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)at java.net.ServerSocket.implAccept(ServerSocket.java:545)at java.net.ServerSocket.accept(ServerSocket.java:513)atorg.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)at java.lang.Thread.run(Thread.java:748)"http-bio-8080-AsyncTimeout" #15 daemon prio=5 os_prio=0 tid=0x00007fabe82d1800 nid=0x8cewaiting on condition [0x00007fabd10d0000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at org.apache.tomcat.util.net.JIoEndpoint$AsyncTimeout.run(JIoEndpoint.java:152)at java.lang.Thread.run(Thread.java:748)"http-bio-8080-Acceptor-0" #14 daemon prio=5 os_prio=0 tid=0x00007fabe82d0000 nid=0x8cdrunnable [0x00007fabd11d1000]java.lang.Thread.State: RUNNABLEat java.net.PlainSocketImpl.socketAccept(Native Method)at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)at java.net.ServerSocket.implAccept(ServerSocket.java:545)at java.net.ServerSocket.accept(ServerSocket.java:513)atorg.apache.tomcat.util.net.DefaultServerSocketFactory.acceptSocket(DefaultServerSocketFactory.java:60)at org.apache.tomcat.util.net.JIoEndpoint$Acceptor.run(JIoEndpoint.java:220)at java.lang.Thread.run(Thread.java:748)"ContainerBackgroundProcessor[StandardEngine[Catalina]]" #13 daemon prio=5 os_prio=0tid=0x00007fabe82ce000 nid=0x8cc waiting on condition [0x00007fabd12d2000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)atorg.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1513)at java.lang.Thread.run(Thread.java:748)"GC Daemon" #10 daemon prio=2 os_prio=0 tid=0x00007fabe83b4000 nid=0x8b3 in Object.wait()[0x00007fabd1c2f000]java.lang.Thread.State: TIMED_WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00000000e315c2d0> (a sun.misc.GC$LatencyLock)at sun.misc.GC$Daemon.run(GC.java:117)- locked <0x00000000e315c2d0> (a sun.misc.GC$LatencyLock) "Service Thread" #7 daemon prio=9 os_prio=0 tid=0x00007fabe80c3800 nid=0x8a5 runnable[0x0000000000000000]java.lang.Thread.State: RUNNABLE"C1 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fabe80b6800 nid=0x8a4 waitingon condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fabe80b3800 nid=0x8a3 waitingon condition [0x0000000000000000]java.lang.Thread.State: RUNNABLE"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fabe80b2000 nid=0x8a2 runnable[0x0000000000000000]java.lang.Thread.State: RUNNABLE"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fabe807f000 nid=0x8a1 in Object.wait()[0x00007fabd2a67000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00000000e3162918> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)- locked <0x00000000e3162918> (a java.lang.ref.ReferenceQueue$Lock)at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fabe807a800 nid=0x8a0 inObject.wait() [0x00007fabd2b68000]java.lang.Thread.State: WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x00000000e3162958> (a java.lang.ref.Reference$Lock)at java.lang.Object.wait(Object.java:502)at java.lang.ref.Reference.tryHandlePending(Reference.java:191)- locked <0x00000000e3162958> (a java.lang.ref.Reference$Lock)at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)"main" #1 prio=5 os_prio=0 tid=0x00007fabe8009000 nid=0x89c runnable [0x00007fabed210000]java.lang.Thread.State: RUNNABLEat java.net.PlainSocketImpl.socketAccept(Native Method)at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)at java.net.ServerSocket.implAccept(ServerSocket.java:545)at java.net.ServerSocket.accept(ServerSocket.java:513)at org.apache.catalina.core.StandardServer.await(StandardServer.java:453)at org.apache.catalina.startup.Catalina.await(Catalina.java:777)at org.apache.catalina.startup.Catalina.start(Catalina.java:723)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)atsun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:321)at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:455) "VM Thread" os_prio=0 tid=0x00007fabe8073000 nid=0x89f runnable"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fabe801e000 nid=0x89d runnable"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fabe8020000 nid=0x89e runnable"VM Periodic Task Thread" os_prio=0 tid=0x00007fabe80d6800 nid=0x8a6 waiting on conditionJNI global references: 431.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.
VisualVM,能夠監控線程,內存情況,查看方法的CPU時間和內存中的對 象,已被GC的對象,反向查看分配的堆棧(如100個String對象分別由哪幾個對象分配出來的)。
VisualVM使用簡單,幾乎0配置,功能還是比較豐富的,幾乎囊括了其它JDK自帶命令的所有功能。
啟動
在jdk的安裝目錄的bin目錄下,找到jvisualvm.exe,雙擊打開即可。


查看 CPU、內存、類、線程運行信息

參看線程信息

也可以點擊右上角Dump按鈕,將線程的信息導出,其實就是執行的jstack命令。

監控遠程JVM
VisualJVM不僅是可以監控本地jvm進程,還可以監控遠程的jvm進程,需要借助于JMX技術實現。
什么是JMX
JMX(Java Management Extensions,即Java管理擴展)是一個為應用程序、設備、系統等植入管理功能的框架。JMX可以跨越一系列異構操作系統平臺、系統體系結構和網絡傳輸協議,靈活地開發無縫集成的系統、網絡和服務管理應用。
監控Tomcat
想要監控遠程的tomcat,就需要在遠程的tomcat進行對JMX配置,方法如下:
#在tomcat的bin目錄下,修改catalina.sh,添加如下的參數 JAVA_OPTS="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false" #這幾個參數的意思是: #-Dcom.sun.management.jmxremote :允許使用JMX遠程管理 #-Dcom.sun.management.jmxremote.port=9999 :JMX遠程連接端口 #-Dcom.sun.management.jmxremote.authenticate=false :不進行身份認證,任何用戶都可以連接 #-Dcom.sun.management.jmxremote.ssl=false :不使用ssl1.2.3.4.5.6.7.8.9.
使用VisualJVM遠程連接Tomcat
添加主機

在一個主機下可能會有很多的jvm需要監控,所以接下來要在該主機上添加需要監控的jvm:


連接成功。使用方法和前面就一樣了,就可以和監控本地jvm進程一樣,監控遠程的tomcat進程。
3.6 可視化GC日志分析工具
GC日志輸出參數
前面通過-XX: PrintGCDetails可以對GC日志進行打印,我們就可以在控制臺查看,這樣雖然可以查看GC的信息,但是并不直觀,可以借助于第三方的GC日志分析工具進行查看。
在日志打印輸出涉及到的參數如下:
-XX: PrintGC 輸出GC日志 -XX: PrintGCDetails 輸出GC的詳細日志 -XX: PrintGCTimeStamps 輸出GC的時間戳(以基準時間的形式) -XX: PrintGCDateStamps 輸出GC的時間戳(以日期的形式,如 2013-05-04T21:53:59.234 0800) -XX: PrintHeapAtGC 在進行GC的前后打印出堆的信息 -Xloggc:../logs/gc.log 日志文件的輸出路徑 1.2.3.4.5.6.7.8.9.10.11.
測試:
-XX: UseG1GC -XX:MaxGCPauseMillis=100 -Xmx256m -XX: PrintGCDetails -XX: PrintGCTimeStamps -XX: PrintGCDateStamps -XX: PrintHeapAtGC-Xloggc:F://test//gc.log 1.2.3.
運行后就可以在E盤下生成gc.log文件。
使用GC Easy
它是一款在線的可視化工具,易用、功能強大,網站: http://gceasy.io/
3.7 調優實戰
先部署一個web項目(自行準備)
壓測
下面我們通過jmeter進行壓力測試,先測得在初始狀態下的并發量等信息,然后我們在對jvm做調優處理,再與初始狀態測得的數據進行比較,看調好了還是調壞了。
首先需要對jmeter本身的參數調整,jmeter默認的的內存大小只有1g,如果并發數到達300以上時,將無法正常執行,會拋出內存溢出等異常,所以需要對內存大小做出調整。修改jmeter.bat文件:set HEAP=-Xms1g -Xmx4g -XX:MaxMetaspaceSize=512m在該文件中可以看到,jmeter默認使用的垃圾收集器是G1.Defaults to -XX: UseG1GC -XX:MaxGCPauseMillis=100 -XX:G1ReservePercent=201.2.3.4.5.6.
添加gc相關參數
#內存設置較小是為了更頻繁的gc,方便觀察效果,實際要比此設置的更大 JAVA_OPTS="-XX: UseParallelGC -XX: UseParallelOldGC -Xms64m -Xmx128m - XX: PrintGCDetails -XX: PrintGCTimeStamps -XX: PrintGCDateStamps -XX: PrintHeapAtGC - Xloggc:../logs/gc.log -Dcom.sun.management.jmxremote - Dcom.sun.management.jmxremote.port=9999 - Dcom.sun.management.jmxremote.authenticate=false - Dcom.sun.management.jmxremote.ssl=false"1.
重啟tomcat
創建測試用例進行壓測
調優方向主要從以下幾個方面:
對于JVM的調優,給出大家幾條建議:
PerfMa
PerfMa提供了JVM參數分析、線程分析、堆內存分析功能,界面美觀,功能強大,我們在做jvm調優時,可以作為一個輔助工具。官網: https://www.perfma.com/
04 Tomcat8優化4.1 禁用AJP連接在服務狀態頁面中可以看到,默認狀態下會啟用AJP服務,并且占用8009端口。

什么是AJP呢?
AJP(Apache JServer Protocol) AJPv13協議是面向包的。WEB服務器和Servlet容器通過TCP連接來交互;為了節省SOCKET創建的昂貴代價,WEB服務器會嘗試維護一個永久TCP連接到servlet容器,并且在多個請求和響應周期過程會重用連接。
我們一般是使用Nginx tomcat的架構,所以用不著AJP協議,所以把AJP連接器禁用。
修改conf下的server.xml文件,將AJP服務禁用掉即可。
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />1.
4.2 執行器(線程池)
在tomcat中每一個用戶請求都是一個線程,所以可以使用線程池提高性能。
修改server.xml文件:
<!--將注釋打開--> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="500" minSpareThreads="50" prestartminSpareThreads="true" maxQueueSize="100"/> <!-- 參數說明: maxThreads:最大并發數,默認設置 200,一般建議在 500 ~ 1000,根據硬件設施和業務來判斷 minSpareThreads:Tomcat 初始化時創建的線程數,默認設置 25 prestartminSpareThreads: 在 Tomcat 初始化的時候就初始化 minSpareThreads 的參數值,如果不等于 true,minSpareThreads 的值就沒啥效果了 maxQueueSize,最大的等待隊列數,超過則拒絕請求 --> <!--在Connector中設置executor屬性指向上面的執行器--> <Connector executor="tomcatThreadPool" port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />1.2.3.4.5.
保存退出,重啟tomcat,查看效果。
4.3 三種運行模式
tomcat的運行模式有3種:
1. bio 默認的模式,性能非常低下,沒有經過任何優化處理和支持.
2. nio nio(new I/O),是Java SE 1.4及后續版本提供的一種新的I/O操作方式(即java.nio包及其子包)。Java nio是一個基于緩沖區、并能提供非阻塞I/O操作的Java API,因此nio也被看成是non-blocking I/O的縮寫。它擁有比傳統I/O操作(bio)更好的并發運行性能。
3. apr 安裝起來最困難,但是從操作系統級別來解決異步的IO問題,大幅度的提高性能.
推薦使用nio,不過,在tomcat8中有最新的nio2,速度更快,建議使用nio2.
設置nio2
<Connector executor="tomcatThreadPool" port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol" connectionTimeout="20000" redirectPort="8443" />1.
(1)盡可能使用局部變量
調用方法時傳遞的參數以及在調用中創建的臨時變量都保存在棧中速度較快,其他變量,如靜態變量、實例變量等,都在堆中創建,速度較慢。另外,棧中創建的變量,隨著方法的運行結束,這些內容就沒了,不需要額外的垃圾回收。
(2)盡量減少對變量的重復計算
明確一個概念,對方法的調用,即使方法中只有一句語句,也是有消耗的。所以例如下面的操作:
for (int i = 0; i < list.size(); i ) {...}1.
建議替換為:
int length = list.size(); for (int i = 0, i < length; i ) {...}1.
這樣,在list.size()很大的時候,就減少了很多的消耗。
(3)盡量采用懶加載的策略,即在需要的時候才創建
String str = "aaa"; if (i == 1){ list.add(str); }//建議替換成 if (i == 1){ String str = "aaa"; list.add(str); }1.2.3.4.5.6.7.8.
(4)異常不應該用來控制流程
異常對性能不利。拋出異常首先要創建一個新的對象,Throwable接口的構造函數調用名為fifillInStackTrace()的本地同步方 法,fifillInStackTrace()方法檢查堆棧,收集調用跟蹤信息。只要有異常被拋出,Java虛擬機就必須調整調用堆棧,因為在處理過程中創建 了一個新的對象。異常只能用于錯誤處理,不應該用來控制程序流程。
(5)不要將數組聲明為public static final
因為這毫無意義,這樣只是定義了引用為static final,數組的內容還是可以隨意改變的,將數組聲明為public更是一個安全漏洞,這意味著這個數組可以被外部類所改變。
(6)不要創建一些不使用的對象,不要導入一些不使用的類
這毫無意義,如果代碼中出現"The value of the local variable i is not used"、"The import java.util is never used",那么請刪除這些無用的內容
(7)程序運行過程中避免使用反射
反射是Java提供給用戶一個很強大的功能,功能強大往往意味著效率不高。不建議在程序運行過程中使用尤其是頻繁使用反射機制,特別是 Method的invoke方法。
如果確實有必要,一種建議性的做法是將那些需要通過反射加載的類在項目啟動的時候通過反射實例化出一個對象并放入內存。
(8)使用數據庫連接池和線程池
這兩個池都是用于重用對象的,前者可以避免頻繁地打開和關閉連接,后者可以避免頻繁地創建和銷毀線程。
(9)容器初始化時盡可能指定長度
容器初始化時盡可能指定長度,如:new ArrayList<>(10); new HashMap<>(32); 避免容器長度不足時,擴容帶來的性能損耗。
(10)ArrayList隨機遍歷快,LinkedList添加刪除快
(11)使用Entry遍歷map
(12)不要手動調用System().gc;
(13)String盡量少用正則表達式
正則表達式雖然功能強大,但是其效率較低,除非是有需要,否則盡可能少用。
replace() 不支持正則 replaceAll() 支持正則
如果僅僅是字符的替換建議使用replace()。
(14)日志的輸出要注意級別
(15)對資源的close()建議分開操作
最后
另外還整理成了40多套PDF文檔:全套的Java面試寶典手冊,“性能調優 微服務架構 并發編程 開源框架 分布式”等七大面試專欄,包含Tomcat、JVM、MySQL、SpringCloud、SpringBoot、Dubbo、并發、Spring、SpringMVC、MyBatis、Zookeeper、Ngnix、Kafka、MQ、Redis、MongoDB、memcached等等。如果你對這個感興趣,小編可以免費分享。
資料獲取方式:關注小編 轉發文章 私信【面試題】獲取上述資料~
重要的事情說三遍,轉發 轉發 轉發,一定要記得轉發哦!!!
歡迎分享轉載→http://www.avcorse.com/read-224410.html
Copyright ? 2024 有趣生活 All Rights Reserve吉ICP備19000289號-5 TXT地圖HTML地圖XML地圖