內(nèi)存模型以及如何判定對象已死問題
體驗與驗證
2.4.5.1 使用visualvm
visualgc插件下載鏈接 :https://visualvm.github.io/pluginscenters.html
選擇對應(yīng)JDK版本鏈接—>Tools—>Visual GC
若上述鏈接找不到合適的,大家也可以自己在網(wǎng)上下載對應(yīng)的版本
2.4.5.2 堆內(nèi)存溢出
- 代碼
@RestController
public class HeapController {
List<Person> list=new ArrayList<Person>();
@GetMapping("/heap")
public String heap(){
while(true){
list.add(new Person());
}
}
}
記得設(shè)置參數(shù)比如-Xmx20M -Xms20M
- 運行結(jié)果
訪問
:http://localhost:8080/heap
Exception in thread "http-nio-8080-exec-2" java.lang.OutOfMemoryError: GC overhead limit exceeded
2.4.5.3 方法區(qū)內(nèi)存溢出
比如向方法區(qū)中添加Class的信息
- asm依賴和Class代碼
<dependency>
<groupId>asm</groupId>
<artifactId>asm</artifactId>
<version>3.3.1</version>
</dependency>
public class MyMetaspace extends ClassLoader {
public static List<Class<?>> createClasses() {
List<Class<?>> classes = new ArrayList<Class<?>>();
for (int i = 0; i < 10000000; ++i) {
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, "Class" + i, null,
"java/lang/Object", null);
MethodVisitor mw = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
"()V", null, null);
mw.visitVarInsn(Opcodes.ALOAD, 0);
mw.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object",
"<init>", "()V");
mw.visitInsn(Opcodes.RETURN);
mw.visitMaxs(1, 1);
mw.visitEnd();
Metaspace test = new Metaspace();
byte[] code = cw.toByteArray();
Class<?> exampleClass = test.defineClass("Class" + i, code, 0, code.length);
classes.add(exampleClass);
}
return classes;
}
}
- 代碼
@RestController
public class NonHeapController {
List<Class<?>> list=new ArrayList<Class<?>>();
@GetMapping("/nonheap")
public String nonheap(){
while(true){
list.addAll(MyMetaspace.createClasses());
}
}
}
設(shè)置Metaspace的大小,比如-XX:MetaspaceSize=50M -XX:MaxMetaspaceSize=50M
- 運行結(jié)果
訪問->http://localhost:8080/nonheap
java.lang.OutOfMemoryError: Metaspace
at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.8.0_191]
at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[na:1.8.0_191]
2.4.5.4 虛擬機棧
- 代碼演示StackOverFlow
public class StackDemo {
public static long count=0;
public static void method(long i){
System.out.println(count++);
method(i);
}
public static void main(String[] args) {
method(1);
}
}
- 運行結(jié)果
- 說明
Stack Space用來做方法的遞歸調(diào)用時壓入Stack Frame(棧幀)。所以當(dāng)遞歸調(diào)用太深的時候,就有可能耗盡Stack Space,爆出StackOverflow的錯誤。
-Xss128k:設(shè)置每個線程的堆棧大小。JDK 5以后每個線程堆棧大小為1M,以前每個線程堆棧大小為256K。根據(jù)應(yīng)用的線程所需內(nèi)存大小進行調(diào)整。在相同物理內(nèi)存下,減小這個值能生成更多的線程。但是操作系統(tǒng)對一個進程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗值在3000~5000左右。
線程棧的大小是個雙刃劍,如果設(shè)置過小,可能會出現(xiàn)棧溢出,特別是在該線程內(nèi)有遞歸、大的循環(huán)時出現(xiàn)溢出的可能性更大,如果該值設(shè)置過大,就有影響到創(chuàng)建棧的數(shù)量,如果是多線程的應(yīng)用,就會出現(xiàn)內(nèi)存溢出的錯誤。
思考:什么時候才會進行垃圾回收 ?
關(guān)注的內(nèi)存的回收 收的多 抉擇 :收的多 還是時間短
回收 短到什么程度 短到感知不到 1次網(wǎng)絡(luò)延遲時間
CPU使用率很高的情況下 適當(dāng)降低垃圾回收的頻率
對象的生命周期
創(chuàng)建階段
(1)為對象分配存儲空間
(2)開始構(gòu)造對象
(3)從超類到子類對static成員進行初始化
(4)超類成員變量按順序初始化,遞歸調(diào)用超類的構(gòu)造方法
(5)子類成員變量按順序初始化,子類構(gòu)造方法調(diào)用,并且一旦對象被創(chuàng)建,并被分派給某些變量賦值,這個對象的狀態(tài)就切換到了應(yīng)用階段
應(yīng)用階段
(1)系統(tǒng)至少維護著對象的一個強引用(Strong Reference)
(2)所有對該對象的引用全部是強引用(除非我們顯式地使用了:軟引用(Soft Reference)、弱引用(Weak Reference)或虛引用(Phantom Reference))
引用的定義:
1.我們的數(shù)據(jù)類型必須是引用類型
2.我們這個類型的數(shù)據(jù)所存儲的數(shù)據(jù)必須是另外一塊內(nèi)存的起始地址
引用:
1.強引用
JVM內(nèi)存管理器從根引用集合(Root Set)出發(fā)遍尋堆中所有到達對象的路徑。當(dāng)?shù)竭_某對象的任意路徑都不含有引用對象時,對這個對象的引用就被稱為強引用
2.軟引用
軟引用是用來描述一些還有用但是非必須的對象。對于軟引用關(guān)聯(lián)的對象,在系統(tǒng)將于發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進回收范圍中進行二次回收。
(當(dāng)你去處理占用內(nèi)存較大的對象 并且生命周期比較長的,不是頻繁使用的)
問題:軟引用可能會降低應(yīng)用的運行效率與性能。比如:軟引用指向的對象如果初始化很耗時,或者這個對象在進行使用的時候被第三方施加了我們未知的操作。
3.弱引用
弱引用(Weak Reference)對象與軟引用對象的最大不同就在于:GC在進行回收時,需要通過算法檢查是否回收軟引用對象,而對于Weak引用對象, GC總是進行回收。因此Weak引用對象會更容易、更快被GC回收
4.虛引用
也叫幽靈引用和幻影引用,為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被回收時收到一個系統(tǒng)通知。也就是說,如果一個對象被設(shè)置上了一個虛引用,實際上跟沒有設(shè)置引用沒有任何的區(qū)別
軟引用代碼Demo:
public class SoftReferenceDemo {
public static void main(String[] args) {
//。。。一堆業(yè)務(wù)代碼
Worker a = new Worker();
//。。業(yè)務(wù)代碼使用到了我們的Worker實例
// 使用完了a,將它設(shè)置為soft 引用類型,并且釋放強引用;
SoftReference sr = new SoftReference(a);
a = null;
//這個時候他是有可能執(zhí)行一次GC的
System.gc();
// 下次使用時
if (sr != null) {
a = (Worker) sr.get();
System.out.println(a );
} else {
// GC由于內(nèi)存資源不足,可能系統(tǒng)已回收了a的軟引用,
// 因此需要重新裝載。
a = new Worker();
sr = new SoftReference(a);
}
}
}
弱引用代碼Demo:
public class WeakReferenceDemo {
public static void main(String[] args) throws InterruptedException {
//100M的緩存數(shù)據(jù)
byte[] cacheData = new byte[100 * 1024 * 1024];
//將緩存數(shù)據(jù)用軟引用持有
WeakReference<byte[]> cacheRef = new WeakReference<>(cacheData);
System.out.println("第一次GC前" + cacheData);
System.out.println("第一次GC前" + cacheRef.get());
//進行一次GC后查看對象的回收情況
System.gc();
//因為我們不確定我們的System什么時候GC
Thread.sleep(1000);
System.out.println("第一次GC后" + cacheData);
System.out.println("第一次GC后" + cacheRef.get());
//將緩存數(shù)據(jù)的強引用去除
cacheData = null;
System.gc(); //默認(rèn)通知一次Full GC
//等待GC
Thread.sleep(500);
System.out.println("第二次GC后" + cacheData);
System.out.println("第二次GC后" + cacheRef.get());
// // 弱引用Map
// WeakHashMap<String, String> whm = new WeakHashMap<String,String>();
}
}
虛引用代碼Demo:
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
Object value = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
Thread thread = new Thread(() -> {
try {
int cnt = 0;
WeakReference<byte[]> k;
while ((k = (WeakReference) referenceQueue.remove()) != null) {
System.out.println((cnt++) + "回收了:" + k);
}
} catch (InterruptedException e) {
//結(jié)束循環(huán)
}
});
thread.setDaemon(true);
thread.start();
Map<Object, Object> map = new HashMap<>();
for (int i = 0; i < 10000; i++) {
byte[] bytes = new byte[1024 * 1024];
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes, referenceQueue);
map.put(weakReference, value);
}
System.out.println("map.size->" + map.size());
}
}
finalize方法代碼Demo:
public class Finalize {
private static Finalize save_hook = null;//類變量
public void isAlive() {
System.out.println("我還活著");
}
@Override
public void finalize() {
System.out.println("finalize方法被執(zhí)行");
Finalize.save_hook = this;
}
public static void main(String[] args) throws InterruptedException {
save_hook = new Finalize();//對象
//對象第一次拯救自己
save_hook = null;
System.gc();
//暫停0.5秒等待他
Thread.sleep(500);
if (save_hook != null) {
save_hook.isAlive();
} else {
System.out.println("好了,現(xiàn)在我死了");
}
//對象第二次拯救自己
save_hook = null;
System.gc();
//暫停0.5秒等待他
Thread.sleep(500);
if (save_hook != null) {
save_hook.isAlive();
} else {
System.out.println("我終于死亡了");
}
}
}
不可見階段
不可見階段的對象在虛擬機的對象根引用集合中再也找不到直接或者間接的強引用,最常見的就是線程或者函數(shù)中的臨時變量。程序不在持有對象的強引用。 (但是某些類的靜態(tài)變量或者JNI是有可能持有的 )
不可達階段
指對象不再被任何強引用持有,GC發(fā)現(xiàn)該對象已經(jīng)不可達。
如何確定一個對象是垃圾?
要想進行垃圾回收,得先知道什么樣的對象是垃圾。
2.5.1.1 引用計數(shù)法
對于某個對象而言,只要應(yīng)用程序中持有該對象的引用,就說明該對象不是垃圾,如果一個對象沒有任何指針對其引用,它就是垃圾。
弊端
:如果AB相互持有引用(循環(huán)引用),導(dǎo)致永遠(yuǎn)不能被回收。
2.5.1.2 可達性分析
通過GC Root的對象,開始向下尋找,看某個對象是否可達 根對象(錯誤的)
能作為GC Root:類加載器、Thread、虛擬機棧的本地變量表、static成員、常量引用、本地方法棧的變量等。GC Roots本質(zhì)上一組活躍的引用
虛擬機棧(棧幀中的本地變量表)中引用的對象。
方法區(qū)中類靜態(tài)屬性引用的對象。
方法區(qū)中常量引用的對象。
本地方法棧中JNI(即一般說的Native方法)引用的對象。
收集階段(Collected)
GC發(fā)現(xiàn)對象處于不可達階段并且GC已經(jīng)對該對象的內(nèi)存空間重新分配做好準(zhǔn)備,對象進程收集階段。如果,該對象的finalize()函數(shù)被重寫,則執(zhí)行該函數(shù)。
1.會影響到JVM的對象以及分配回收速度
2.可能造成對象再次復(fù)活(詐尸)
終結(jié)階段(Finalized)
對象的finalize()函數(shù)執(zhí)行完成后,對象仍處于不可達狀態(tài),該對象進程終結(jié)階段。
對象內(nèi)存空間重新分配階段(Deallocaled)文章來源:http://www.zghlxwxcb.cn/news/detail-622948.html
GC對該對象占用的內(nèi)存空間進行回收或者再分配,該對象徹底消失。文章來源地址http://www.zghlxwxcb.cn/news/detail-622948.html
到了這里,關(guān)于三、JVM-如何判斷對象已死問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!