理解Java垃圾回收
當(dāng)程序創(chuàng)建對象、數(shù)組等引用類型的實(shí)體時(shí),系統(tǒng)會(huì)在堆內(nèi)存中為這一對象分配一塊內(nèi)存,對象就保存在這塊內(nèi)存中,當(dāng)這塊內(nèi)存不再被任何引用變量引用時(shí),這塊內(nèi)存就變成垃圾,等待垃圾回收機(jī)制進(jìn)行回收。垃圾回收機(jī)制具有三個(gè)特征:
垃圾回收機(jī)制只負(fù)責(zé)回收堆內(nèi)存中的對象,不會(huì)回收任何物理資源(例如數(shù)據(jù)庫連接,打開的文件資源等),也不會(huì)回收以某種創(chuàng)建對象的方式以外的方式為該對像分配的內(nèi)存,(例如對象調(diào)用本地方法中malloc的方式申請的內(nèi)存)
程序無法精確控制垃圾回收的運(yùn)行,只可以建議垃圾回收進(jìn)行,建議的方式有兩種System.gc() 和Runtime.getRuntime().gc()
在垃圾回收任何對象之前,總會(huì)先調(diào)用它的finalize()方法,但是同垃圾回收的時(shí)機(jī)一致,調(diào)用finalize()方法的時(shí)機(jī)也不確定。
針對以上三個(gè)特征,有三個(gè)問題:
1、必須手動(dòng)的進(jìn)行清理工作,釋放除創(chuàng)建對象的方式以外的方式分配的內(nèi)存和其它的物理資源。并且要注意消除過期的對象引用,否則可能引起OOM。
手動(dòng)清理通常用到try...finally...這樣的代碼結(jié)構(gòu)。
示例如下:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class ManualClear {
public static void main(String[] args) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream("./src/ManualClear.java");
} catch (FileNotFoundException e) {
System.out.println(e.getMessage());
e.printStackTrace();
return;
}
try {
byte[] bbuf = new byte[1024];
int hasRead = 0;
try {
while ((hasRead = fileInputStream.read(bbuf)) > 0) {
System.out.println(new String(bbuf, 0, hasRead));
}
} catch (IOException e) {
e.printStackTrace();
}
} finally {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
對于過期對象的引用,引起的OOM通常有三種常見的情況,這三種情況通常都不易發(fā)現(xiàn),短時(shí)間內(nèi)運(yùn)行也不會(huì)有什么問題,但是時(shí)間久了后,泄漏的對象增加后終會(huì)引起程序崩潰。
類自己管理內(nèi)存時(shí),要警惕內(nèi)存泄漏
示例如下:
import java.util.Arrays;
import java.util.EmptyStackException;
class Stack{
private Object[] elements;
private int size;
private static final int DEFAULT_INITAL_CAPACITY = 16;
public Stack() {
elements = new Object[DEFAULT_INITAL_CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++] = e;
}
public Object pop() {
if (size == 0) {
throw new EmptyStackException();
}
return elements[--size];
}
private void ensureCapacity() {
if (elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
public class StackDemo {
public static void main(String[] args) {
Stack stack = new Stack();
for (int i = 0; i < 10000; i++) {
stack.push(new Object());
}
for(int i = 0; i < 10000; i++) {
stack.pop();
}
}
}
之所以會(huì)內(nèi)存泄漏,是因?yàn)槟切┏鰲5膶ο蠹词钩绦蚱渌鼘ο蟛辉僖茫荢tack類中的elements[]數(shù)組依然保存著這些對象的引用,導(dǎo)致這些對象不會(huì)被垃圾回收所回收,所以,當(dāng)需要類自己管理內(nèi)存事,要警惕內(nèi)部維護(hù)的這些過期引用是否被及時(shí)解除了引用,本例中只需在出棧后,顯示的將
elements[size] = null;即可。
緩存是要警惕內(nèi)存泄漏
出現(xiàn)這樣情況通常是一旦將對象放入緩存,很可能長時(shí)間不使用很容易遺忘,通??梢杂肳akeHashMap代表緩存,在緩存中的項(xiàng)過期后,他們可以被自動(dòng)刪除?;蛘呖梢杂梢粋€(gè)后臺(tái)線程定期執(zhí)行來清除緩沖中的過期項(xiàng)。
監(jiān)聽器或回調(diào)的注冊,最好可以顯示的取消注冊。
2、不要手動(dòng)調(diào)用finalize(),它是給垃圾回收器調(diào)用的
3、避免使用finalize()方法,除非用來作為判斷終結(jié)條件以發(fā)現(xiàn)對象中沒有被適當(dāng)清理的部分;用來作為安全網(wǎng)在手動(dòng)清理忘記調(diào)用的情況下清理系統(tǒng)資源,延后清理總別永不清理要強(qiáng),并且如果同時(shí)記錄下忘記清理資源的信息的話,也方便后面發(fā)現(xiàn)錯(cuò)誤,并及時(shí)修改忘記清理的代碼;釋放對象中本地方法獲得的不是很關(guān)鍵的系統(tǒng)資源。
finalize()方法由于其執(zhí)行時(shí)間以及是否確定被執(zhí)行都不能準(zhǔn)確確保,所以最好不用來釋放關(guān)鍵資源,但是可用于上面所說的三種情況。其中第一種情況,示例如下:
class Book {
boolean checkout = false;
public Book(boolean checkout) {
this.checkout = checkout;
}
public void checkin(){
checkout = false;
}
@Override
protected void finalize() throws Throwable {
if (checkout) {
System.out.println("Error: check out");
}
}
}
public class FinalizeCheckObjectUse {
public static void main(String[] args) {
new Book(true);
System.gc();
}
}
執(zhí)行結(jié)果:
Error: check out
例子中的Book對象,在釋放前必須處于checkIn的狀態(tài),否則不能釋放,finalize中的實(shí)現(xiàn)可以幫助及時(shí)發(fā)現(xiàn)不合法的對象,或者更直接的,在finalize中直接使用某個(gè)引用變量引用,使其重新進(jìn)入reachable的狀態(tài),然后再次對其進(jìn)行處理。
另一點(diǎn)需要注意的時(shí),子類如果覆蓋了父類的finalize方法,但是忘了手工調(diào)用super.finalize或者子類的finalize過程出現(xiàn)異常導(dǎo)致沒有執(zhí)行到super.finalize時(shí),那么父類的終結(jié)方法將永遠(yuǎn)不會(huì)調(diào)到。
如下:
class Parent{
@Override
protected void finalize() throws Throwable {
System.out.println(getClass().getName() + " finalize start");
}
}
class Son extends Parent{
@Override
protected void finalize() throws Throwable {
System.out.println(getClass().getName() + " finalize start");
}
}
public class SuperFinalizeLost {
public static void main(String[] args) {
new Son();
System.gc();
}
}
運(yùn)行結(jié)果:
Son finalize start
或者
class Parent{
@Override
protected void finalize() throws Throwable {
System.out.println(getClass().getName() + " finalize start");
}
}
class Son extends Parent{
@Override
protected void finalize() throws Throwable {
System.out.println(getClass().getName() + " finalize start");
int i = 5 / 0;
super.finalize();
}
}
public class SuperFinalizeLost {
public static void main(String[] args) {
new Son();
System.gc();
}
}
執(zhí)行結(jié)果:
Son finalize start
對于第二種情況,可以使用try...finally...結(jié)構(gòu)解決,但是對于第一種情況,最好使用一種叫終結(jié)方法守護(hù)者的方式。示例如下
class Parent2{
private final Object finalizeGuardian = new Object() {
protected void finalize() throws Throwable {
System.out.println("在此執(zhí)行父類終結(jié)方法中的邏輯");
};
};
}
class Son2 extends Parent2{
@Override
protected void finalize() throws Throwable {
System.out.println(getClass().getName() + " finalize start");
int i = 5 / 0;
super.finalize();
}
}
public class FinalizeGuardian {
public static void main(String[] args) {
new Son2();
System.gc();
}
}
執(zhí)行結(jié)果:
在此執(zhí)行父類終結(jié)方法中的邏輯
Son2 finalize start
這樣可以保證父類的終結(jié)方法中所需做的操作執(zhí)行到。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助。
相關(guān)文章
Java實(shí)現(xiàn)富文本轉(zhuǎn)markdown
這篇文章主要為大家詳細(xì)介紹了如何通過Java實(shí)現(xiàn)富文本轉(zhuǎn)markdown功能,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,有需要的小伙伴可以參考下2023-12-12
Java中的ThreadLocal與ThreadLocalMap詳解
這篇文章主要介紹了Java中的ThreadLocal與ThreadLocalMap詳解,ThreadLocal 是一個(gè)線程局部變量,其實(shí)的功用非常簡單,就是為每一個(gè)使用該變量的線程都提供一個(gè)變量值的副本,是Java中一種較為特殊的線程綁定機(jī)制,需要的朋友可以參考下2023-09-09
elasticsearch中term與match的區(qū)別講解
今天小編就為大家分享一篇關(guān)于elasticsearch中term與match的區(qū)別講解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-02-02
SpringBoot通過請求對象獲取輸入流無數(shù)據(jù)
這篇文章主要介紹了使用SpringBoot通過請求對象獲取輸入流無數(shù)據(jù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
java文件操作報(bào)錯(cuò):java.io.FileNotFoundException(拒絕訪問)問題
在進(jìn)行編程時(shí),經(jīng)常會(huì)遇到因疏忽小細(xì)節(jié)而導(dǎo)致的錯(cuò)誤,如忘記在路徑后添加文件名,本文通過一個(gè)具體的修改前后對比示例,解釋了錯(cuò)誤原因,并給出了解決方案,這類經(jīng)驗(yàn)分享對編程學(xué)習(xí)者具有參考價(jià)值2024-10-10
java實(shí)現(xiàn)ReadWriteLock讀寫鎖的示例
ReadWriteLock是Java并發(fā)包中的接口,定義了讀鎖和寫鎖,讀鎖允許多線程同時(shí)訪問共享資源,而寫鎖則要求獨(dú)占,這種機(jī)制適用于讀多寫少的場景,可以提高并發(fā)效率同時(shí)保證數(shù)據(jù)一致性,本文就來詳細(xì)的介紹一下如何實(shí)現(xiàn),感興趣的可以了解一下2024-09-09

