JVM的位置
JVM的體系架構圖
類加載器ClassLoader
類的加載、連結和初始化(了解)
加載:查找并加載類的二進制資料
連接配接:
驗證:保證被加載的類的正确性;
準備:給類靜态變量配置設定記憶體空間,指派一個預設的初始值;-
解析:把類中的符号引用轉換為直接引用
在把java編譯為class檔案的時候,虛拟機并不知道所引用的位址;助記符:符号引用!
轉為真正的直接引用,找到對應的直接位址!
初始化:給類的靜态變量指派正确的值;
public class Test{
public static int a = 1;
}
// 1、加載 編譯檔案為 .class 檔案,通過類加載,加載到JVM
// 2、連接配接
驗證(1) 保證Class類檔案沒有問題
準備(2) 給int類型配置設定記憶體空間,a = 0;
解析(3) 符号引用轉換為直接引用
// 3、初始化
經過這個階段的解析,把1 指派給 變量 a;
類的加載 static
package com.coding.classloader;
// JVM 參數:
// -XX:+TraceClassLoading // 用于追蹤類的加載資訊并列印出來
// 分析項目啟動為什麼這麼慢,快速定位自己的類有沒有被加載!
// rt.jar jdk 出廠自帶的,最進階别的類加載器要加載的!
public class Demo02 {
public static void main(String[] args) {
System.out.println(MyChild1.str2);
// 運作的結果
/**
* MyParent1 static
* MyChild1 static
* hello,str2
*/
}
}
class MyParent1{
public static String str = "hello,world";
static {
System.out.println("MyParent1 static");
}
}
class MyChild1 extends MyParent1{
public static String str2 = "hello,str2";
static {
System.out.println("MyChild1 static");
}
}
常量 final
final 常量在編譯階段的時候是放在常量池;
這個代碼中将常量放到了 Demo03 的常量池中。之後 Demo03與MyParent02 就沒有關系了
package com.coding.classloader;
// 常量
public class Demo03 {
public static void main(String[] args) {
System.out.println(MyParent02.str);
}
}
class MyParent02{
public static final String str = "hello world";
static {
System.out.println("MyParent02 static"); // 這句話會輸出嗎?
}
/**
* final 常量在編譯階段的時候 常量池;
* 這個代碼中将常量放到了 Demo03 的常量池中。之後 Demo03與MyParent02 就沒有關系了
*/
}
UUID對象不在常量池内,final不會直接取值
package com.coding.classloader;
import java.util.UUID;
/**
* 當一個常量的值并非編譯期間可以确定的,那這個值就不會被方法調用類的常量池中!
* 程式運作期間的時候,回主動使用常用所在的類
*/
public class Demo04 {
public static void main(String[] args) {
System.out.println(MyParent04.str);
}
}
class MyParent04{
public static final String str = UUID.randomUUID().toString();
static {
System.out.println("MyParent04 static"); // 這句話會輸出嗎?
}
}
ClassLoader 分類
1、java虛拟機自帶的加載器
- BootStrap 根加載器 (加載系統的包,JDK 核心庫中的類 rt.jar)
- Ext 擴充類加載器 (加載一些擴充jar包中的類)
- Sys/App 系統(應用類)加載器 (我們自己編寫的類
2、使用者自己定義的加載器
ClassLoader,隻需要繼承這個抽象類即可,自定義自己的類加載器
雙親委派機制
雙親委派機制 可以保護java的核心類不會被自己定義的類所替代
一層一層的讓父類去加載,如果頂層的加載器不能加載,然後再向下類推
// Demo 01
// AppClassLoader 03
// ExtClassLoader 02
// BootStrap (最頂層) 01 java.lang.String rt.jar
```java
package com.coding.classloader;
// Demo01
public class Demo01 {
public static void main(String[] args) {
Object o = new Object(); // jdk 自帶的
Demo01 demo01 = new Demo01(); // 執行個體化一個自己定義的對象
// null 在這裡并不代表沒有,隻是Java觸及不到!
System.out.println(o.getClass().getClassLoader()); // null
System.out.println(demo01.getClass().getClassLoader()); // AppClassLoader
System.out.println(demo01.getClass().getClassLoader().getParent()); // ExtClassLoader
System.out.println(demo01.getClass().getClassLoader().getParent().getParent()); // null
// 思考:為什麼我們剛才自己定義的 java.lang.String 沒有生效?
// jvm 中有機制可以保護自己的安全;
// 雙親委派機制 : 一層一層的讓父類去加載,如果頂層的加載器不能加載,然後再向下類推
// Demo01
// AppClassLoader 03
// ExtClassLoader 02
// BootStrap (最頂層) 01 java.lang.String rt.jar
// 雙親委派機制 可以保護java的核心類不會被自己定義的類所替代
}
}
Native方法
native : 隻要是帶了這個關鍵字的,說明 java的作用範圍達不到,隻能去調用底層 C 語言的庫!
public class Test {
public static void main(String[] args) {
// java 真的可以開啟線程嗎?
// private native void start0();
new Thread().start();
}
}
程式計數器
每個線程都有一個程式計數器,是線程私有的。
程式計數器就是一塊十分小的記憶體空間;幾乎可以不計
作用: 看做目前位元組碼執行的行号訓示器;
分支、循環、跳轉、異常處理!都需要依賴于程式計數器來完成!
bipush
将 int、float、String、常量值推送值棧頂;
istore
将一個數值從操作數棧存儲到局部變量表;
iadd
加
imul
乘
方法區淵源
Method Area 方法區 是 Java虛拟機規範中定義的運作是資料區域之一,和堆(heap)一樣可以線上程之間共享!
JDK1.7之前
永久代:用于存儲一些虛拟機加載類資訊,常量,字元串、靜态變量等等。。。。這些東西都會放到永久代中;
永久代大小空間是有限的:如果滿了
OutOfMemoryError:PermGen
JDK1.8之後
徹底将永久代移除 HotSpot jvm ,Java Heap 中或者 Metaspcace(Native Heap)元空間;
元空間就是方法區在 HotSpot jvm 的實作;
方法區重要就是來存:類資訊,常量,字元串、靜态變量、符号引用、方法代碼。。。。。。
元空間和永久代,都是對JVM規範中方法區的實作。
元空間和永久代最大的差別:元空間并不在Java虛拟機中,使用的是本地記憶體!
-XX:MetasapceSize10m
棧Stack
棧和隊列都是基本的資料結構;
隊列:FIFO(First Input First OutPut)
棧的優勢:存取速度比堆快!僅次于寄存器,棧的資料是不可以共享的;
public class Demo01 {
public static void main(String[] args) {
a();
}
// main a a a a a a a a a a a 滿
// Exception in thread "main" java.lang.StackOverflowError
private static void a() {
a();
}
}
是以說,棧裡面是一定不會存在垃圾回收的問題的,隻要線程一旦結束,該棧就Over了。生命周期和線程一緻.
Stack原理
java棧的組成元素–`棧幀
棧(存什麼)+ 堆 + 方法區的互動圖:
棧主要是 HotSpot (指針)
堆(heap)
Java7之前:
Heap 堆,一個JVM執行個體中隻存在一個堆,堆的記憶體大小是可以調節的。
可以存的内容:類、方法、常量、儲存了類型引用的真實資訊;
分為三個部分:
- 新生區:Young (Eden-s0-s1)
- 養老區:Old Tenure
- 永久區:Perm
堆記憶體在邏輯上分為三個部分:新生、養老、永久(JDK1.8以後,叫元空間)
實體上隻有 新生、養老;元空間在本地記憶體中,不在JVM中!
GC 垃圾回收主要是在 新生區和養老區,又分為 普通的GC 和 Full GC,如果堆滿了,就會爆出 OutOfMemory;
新生區
新生區 就是一個類誕生、成長、消亡的地方!
新生區細分: Eden、s(from to),所有的類Eden被 new 出來的,慢慢的當 Eden 滿了,程式還需要建立對象的時候,就會觸發一次輕量級GC;清理完一次垃圾之後,會将活下來的對象,會放入幸存者區(),… 清理了 20次之後,出現了一些極其頑強的對象,有些對象突破了15次的垃圾回收!這時候就會将這個對象送入養老區!運作了幾個月之後,養老區滿了,就會觸發一次 Full GC;假設項目1年後,整個空間徹徹底底的滿了,突然有一天系統 OOM,排除OOM問題,或者重新開機;
Sun HotSpot 虛拟機中,記憶體管理(分代管理機制:不同的區域使用不同的算法!)
99% 的對象在 Eden 都是臨時對象;
養老區
15次都幸存下來的對象進入養老區,養老區滿了之後,觸發 Full GC
預設是15次,可以修改!
永久區(Perm)
放一些 JDK 自身攜帶的 Class、Interface的中繼資料;
幾乎不會被垃圾回收的;
OutOfMemoryError:PermGen
在項目啟動的時候永久代不夠用了?加載大量的第三方包!
JDK1.6之前: 有永久代、常量池在方法區;
JDK1.7:有永久代、但是開始嘗試去永久代,常量池在堆中;
JDK1.8 之後:永久代沒有了,取而代之的是元空間;常量池在元空間中;
堆記憶體調優
環境:HotSpot、JDK1.8;
測試一
package com.coding.oom;
/**
* 預設情況:
* maxMemory : 1808.0MB (虛拟機試圖使用的最大的記憶體量 一般是實體記憶體的 1/4)
* totalMemory : 123.0MB (虛拟機試圖預設的記憶體總量 一般是實體記憶體的 1/64)
*/
// 我們可以自定堆記憶體的總量
// -XX:+PrintGCDetails; // 輸出詳細的垃圾回收資訊
// -Xmx: 最大配置設定記憶體; 1/4
// -Xms: 初始配置設定的記憶體大小; 1/64
// -Xmx1024m -Xms1024m -XX:+PrintGCDetails
public class Demo01 {
public static void main(String[] args) {
// 擷取堆記憶體的初始大小和最大大小
long maxMemory = Runtime.getRuntime().maxMemory();
long totalMemory = Runtime.getRuntime().totalMemory();
System.out.println("maxMemory="+maxMemory+"(位元組)、"+(maxMemory/1024/(double)1024)+"MB");
System.out.println("totalMemory="+totalMemory+"(位元組)、"+(totalMemory/1024/(double)1024)+"MB");
}
}
測試二
package com.coding.oom;
import java.util.Random;
/*
* -Xmx8m -Xms8m -XX:+PrintGCDetails
*
* 分析GC日志:
*
* [Times: user=0.00 sys=0.00, real=0.00 secs]
* 1、GC 類型 GC:普通的GC,Full GC :重GC
* 2、1536K 執行 GC之前的大小
* 3、504K 執行 GC之後的大小
* 4、(2048K) young 的total大小
* 5、0.0012643 secs 清理的時間
* 6、user 總計GC所占用CPU的時間 sys OS調用等待的時間 real 應用暫停的時間
*
* GC :串行執行 STW(Stop The World) 并行執行 G1
*/
public class Demo02 {
public static void main(String[] args) {
System.gc(); // 手動喚醒GC(),等待cpu的調用
String str = "ilovecoding";
while (true){
str += str
+ new Random().nextInt(999999999)
+ new Random().nextInt(999999999);
}
// 出現問題:java.lang.OutOfMemoryError: Java heap space
}
}
Dump記憶體快照
在java程式運作的時候,想測試運作的情況!
使用一些工具來檢視;
1、Jconsole
2、idea debug
3、Eclipse(MAT插件)
4、IDEA(Jprofiler插件)
Jprofiler 插件
一款性能瓶頸分析插件
安裝 Jprofiler
1、IDEA安裝 JProfiler 插件
2、window上安裝 JProfiler (無腦下一步即可:注意路徑中不能有中文和空格)
3、激活
注冊碼僅供大家參考
[email protected]#23874-hrwpdp1sh1wrn#0620
[email protected]#36573-fdkscp15axjj6#25257
[email protected]#5481-ucjn4a16rvd98#6038
[email protected]#99016-hli5ay1ylizjj#27215
[email protected]#40775-3wle0g1uin5c1#0674
4、在IDEA 中綁定 JProfiler
package com.coding.oom;
import java.util.ArrayList;
import java.util.List;
// -Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError
public class Demo03 {
byte[] bytes = new byte[1*1024*1024]; // 1M
public static void main(String[] args) throws InterruptedException {
// 泛型:限制!
List<Demo03> list = new ArrayList<Demo03>();
int count = 0;
try {
// Error
while (true){
list.add(new Demo03());
count = count + 1;
}
} catch (Throwable e) { // Throwable 或者 Error
System.out.println("count="+count);
e.printStackTrace();
}
}
}
分析dump出來的快照,檢視異常對象;分析定位到具體的類和代碼問題!