可達型分析
可達性分析的理論
基本思路:
通過一系列GC Roots的根對象作為起始節點集, 根據引用關系向下搜索,
搜索過程中走過的路徑稱為引用鏈,所有在這個引用鏈上的對象都是可達對象,
而其他的沒有與GC Roots根對象關聯的單獨存在的引用鏈上的對象則為不可達對象
圖示:
image
在內存中的場景:
上圖的 object1 中有字段object2 , object3 的引用,
當我們把object1的引用ref賦值為null,
那么object1就變為了 上圖右側的不可達對象的圖例了..需要被gc回收掉了,如下圖引用
image
代碼實現舉例子:
@Data
public class Test {
public Test instance;
public int i;
public Test(int i) {
this.i = i;
}
}
public static void main(String[] args) {
Test test0 = new Test(0);
Test test1 = new Test(1);
Test test2 = new Test(2);
test0.instance=test1;
test1=test2;
System.out.println(test0.getInstance().getI());
}
執行結果 1
為什么不是2呢,test1已經被test2賦值了呀!
這是因為引用變量只能是指向某個對象的,而不能是指向引用的。
所以test0.instance 指向的是test1在堆中的對象,
所以在后面改變了test1的指向時, 并沒有影響之前的指向.
public static void main(String[] args) {
Test test0 = new Test(0);
Test test1 = new Test(1);
Test test2 = new Test(2);
test0.instance=test1;
test1.instance=test2;
test0=null;
}
ps: test0=null則 test0的原映射對象是不在引用鏈上了,會被gc回收.
且test0.instance的由上例子可知是引用的對象test1, 但這里不會回收test1,
因為test1也自己定義了一個根對象, 所以test1還是在test1引用鏈上,但不在test0的引用鏈上了,
如果將test1=null也這樣設置,那么test1也將會被gc回收;
問題:那么在平時的代碼中我們要不要在使用后對象就將對象置空呢?
這個問題要看 JVM 垃圾回收-判斷對象是否可以回收 中的描述了
可達性根枚舉對象
GC Roots的對象
1. 虛擬機棧中的引用的對象 如:Object o = new Object(); o即為虛擬棧中的引用對象
2. 方法區中類靜態屬性引用的對象 如:public static String static_str="111"; static_str 即為引用對象
3. 方法區中常量引用的對象
4. 本地方法棧中 JNI(native方法)引用的對象
5. 被同步鎖synchronize持有的對象
ps:這里提到了很多引用,我們下面將詳細描述引用的分類,引用之所以進行設定不同的分類,
是因為在內存中數據的要求是多樣的,
比如 我們希望在內存充足的情況下,保留這些類,但內存不足的時候,就進行回收(緩存機制)
引用是如何分類
強引用
使用方法
正常的寫的java代碼都是強引用99.999%
Object o = new Object();
這種就是強引用了,是不是在代碼中隨處可見,最親切。
只要某個對象有強引用與之關聯,這個對象永遠不會被回收
即使內存不足,JVM寧愿拋出OOM,也不會去回收
回收的方法:
o = null;
示例:
我們需要新寫一個類,然后重寫finalize方法
public class Student {
@Override
protected void finalize() throws Throwable {
System.out.println("Student 被回收了");
}
}
public static void main(String[] args) {
Student student = new Student();
student = null;
System.gc();
}
運行結果:
Student 被回收了
注釋:finalize方法是當gc時,系統來調用該方法
釋放該對象在堆中占用的內存. 該方法在Object中
軟引用
使用方法
用java.lang.ref.SoftReference 進行包裝
SoftReference<Student>studentSoftReference=new SoftReference<Student>(new Student());
回收方法
gc自動處理
當內存不足,會觸發JVM的GC,如果GC后,內存還是不足,就會把軟引用的包裹的對象給干掉
也就是只有在內存不足,JVM才會回收該對象
示例:
修改idea的堆內存大小 -Xmx20m 最大堆內存為20m , 設置內存追蹤 -XX:+PrintGCDetails
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(softReference.get());
System.gc();
System.out.println(softReference.get());
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println(softReference.get());
創建一個軟引用對象,里面包裹了byte[],byte[]占用了10M,然后又創建了10Mbyte[]
結果:
[GC (Allocation Failure) 5632K->1393K(19968K), 0.0049815 secs]
[B@763d9750
[GC (System.gc()) 16187K->12252K(19968K), 0.0014227 secs]
[Full GC (System.gc()) 12252K->11862K(19968K), 0.0101228 secs]
[B@763d9750
[GC (Allocation Failure) 12032K->11926K(19968K), 0.0004047 secs]
[GC (Allocation Failure) 11926K->11926K(19968K), 0.0002842 secs]
[Full GC (Allocation Failure) 11926K->11784K(19968K), 0.0049854 secs]
[GC (Allocation Failure) 11784K->11784K(19968K), 0.0002981 secs]
[Full GC (Allocation Failure) 11784K->1495K(16896K), 0.0066588 secs]
null
分析: 當gc回收時, 軟
注釋:一般是緩存使用
弱引用
使用方法
java.lang.ref.WeakReference包裝
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1024*1024*10]);
System.out.println(weakReference.get());
回收方式
System.gc();
不管內存是否足夠,只要發生GC,都會被回收
示例
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
結果:
[GC (Allocation Failure) 5632K->1410K(19968K), 0.0016663 secs]
[B@763d9750
[GC (System.gc()) 5726K->2034K(19968K), 0.0015112 secs]
[Full GC (System.gc()) 2034K->1914K(19968K), 0.0113147 secs]
null
注釋:弱引用在很多地方都有用到,比如ThreadLocal、WeakHashMap。
虛引用
使用方法:
當發生GC,虛引用就會被回收,并且會把回收的通知放到ReferenceQueue中
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
System.out.println(reference.get());
作用時,當gc回收的時候會產生一條記錄到ReferenceQueue中
回收方式:
System.gc() 自動回收
示例:
@Data
@Builder
public class Student {
@Override
protected void finalize() throws Throwable {
System.out.println("Student 被回收了");
}
}
public static void main(String[] args) {
ReferenceQueue queue = new ReferenceQueue();
List<byte[]> bytes = new ArrayList<>();
PhantomReference<Student> reference = new PhantomReference<Student>(new Student(),queue);
new Thread(() -> {
for (int i = 0; i < 100;i++ ) {
bytes.add(new byte[1024 * 1024]);
}
}).start();
new Thread(() -> {
while (true) {
Reference poll = queue.poll();
if (poll != null) {
System.out.println("虛引用被回收了:" + poll);
}
}
}).start();
Scanner scanner = new Scanner(System.in);
scanner.hasNext();
}
結果:
[GC (Allocation Failure) 5632K->1372K(19968K), 0.0011861 secs]
[GC (Allocation Failure) 7004K->2185K(19968K), 0.0017764 secs]
[GC (Allocation Failure) 7205K->6394K(19968K), 0.0028557 secs]
[GC (Allocation Failure) 11653K->11554K(19968K), 0.0027832 secs]
[Full GC (Ergonomics) 11554K->11374K(19968K), 0.0134628 secs]
[Full GC (Ergonomics) 16624K->16456K(19968K), 0.0055213 secs]
[Full GC (Ergonomics) 18620K->18488K(19968K), 0.0098597 secs]
[Full GC (Allocation Failure) 18488K->18402K(19968K), 0.0115410 secs]
Student 被回收了
[Full GC (Ergonomics) 18949K->17924K(19968K), 0.0106182 secs]
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at com.logistic.JVMTest.lambda$main$0(JVMTest.java:16)
at com.logistic.JVMTest$$Lambda$1/747464370.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
虛引用被回收了:java.lang.ref.PhantomReference@5eb93cd7
注釋:
第一個線程往集合里面塞數據,隨著數據越來越多,肯定會發生GC
第二個線程死循環,從queue里面拿數據,如果拿出來的數據不是null,就打印出來