Java進階篇

背景

最近在準備面試,結合之前的工作經驗和近期在網上收集的一些面試資料,準備將Android開發(fā)崗位的知識點做一個系統(tǒng)的梳理,整理成一個系列:Android應用開發(fā)崗 面試匯總。本系列將分為以下幾個大模塊:
Java基礎篇、Java進階篇、常見設計模式
Android基礎篇Android進階篇、性能優(yōu)化
網絡相關數據結構與算法
常用開源庫、Kotlin、Jetpack

注1:以上文章將陸續(xù)更新,直到我找到滿意的工作為止,有跳轉鏈接的表示已發(fā)表的文章。
注2:該系列屬于個人的總結和網上東拼西湊的結果,每個知識點的內容并不一定完整,有不正確的地方歡迎批評指正。
注3:部分摘抄較多的段落或有注明出處。如有侵權,請聯系本人進行刪除。

一、多線程和線程同步

線程概念: 指進程中的一個按順序執(zhí)行的流程,一個進程中可以運行多個線程。線程總是屬于某個進程,進程中的多個線程共享進程的內存

1、啟動一個線程的方式

  • 使用Thread類來定義工作(重寫Thread的run方法)
        Thread thread = new Thread() {
            @Override
            public void run() {  //重寫Thread的run方法
                System.out.println("Thread started!");
            }
        };
        thread.start();
    }
  • 使用Runnable來定義工作
    原理:runnable傳到Thread構造方法里,當Thread的run執(zhí)行時,該方法內部執(zhí)行runnable的run
    作用:便于runnable的重用
    static void runnable() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread with Runnable started!");
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
    }
  • 工廠方法來定義工作,復用Thread的創(chuàng)建和復用runnable
static void threadFactory() {
        final AtomicInteger count = new AtomicInteger(0);  //原子類型
        ThreadFactory factory = new ThreadFactory() {
            @Override
            public Thread newThread(Runnable runnable) {
                return new Thread(runnable, "Thread-" + count.incrementAndGet());//++count
            }
        };
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " started!");
            }
        };
        Thread thread = factory.newThread(runnable);
        thread.start();
        Thread thread2 = factory.newThread(runnable); //復用runnable
        thread2.start();
        
    }
  • 通過線程池來定義
static void executor() {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread with executor started!");
            }
        };
        Executor executor = Executors.newCachedThreadPool();
        executor.execute(runnable);
        executor.execute(runnable);//復用
    }
  • Callable 來定義(很少用)。有返回值的Runnable
static void callable() {
        Callable<String> callable = new Callable<String>() {
            @Override
            public String call() throws Exception {
                Thread.sleep(5000);//等待5秒模,擬耗時操作
                return "Done!";
            }
        };
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> future = executorService.submit(callable);
        while (true) {  //該循環(huán)的邏輯可以理解成加載網絡轉菊花的情況
            //xx1() 處理其他事情
            //xx2()
            if (future.isDone()) { //判斷任務是否執(zhí)行完
                try {
                    //卡主線程,阻塞式的方法,需要等5秒后(sleep的時長)取到值。用來獲取任務執(zhí)行完的時機
                    String result = future.get();
                    System.out.println("result:" + result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }

                break;
            }
        }
    }

2、線程池的使用

幾種不同類型的線程池

  • Executors.newCachedThreadPool();
    說明:核心線程數默認0,線程數上限Integer.MAX_VALUE,等待回收時間60秒
  • Executors.newSingleThreadExecutor();
    說明:默認1,上限1,0秒
    使用場景:應用場景少,用來做功能的取消
  • Executors.newFixedThreadPool(10); //nThreads=10
    說明:nThreads為固定線程數, 默認和上限均為nThreads,0秒
    使用場景:用來執(zhí)行瞬時爆發(fā)性的任務,如批量處理多個bitmap
  • Executors.newScheduledThreadPool(10);
    說明: 默認corePoolSize,上限Integer.MAX_VALUE,10毫秒
    使用場景:做定時任務,功能類似于Timer。用來做定時處理的線程池,如每隔60秒檢查一次網絡

用到的類

  • Executor: 是一個接口,里面只有一個execute方法

  • Executors: 是一個工具類,用來創(chuàng)建不同類型的線程池:ThreadPoolExecutor,返回ExecutorService對象,ExecutorService為Executor的子接口

  • ExecutorService: 為Executor的子接口。該類中有shutdown()和shutdownNow(),用來結束executor,即關閉線程池

  • shutdown() 保守的結束,指如果線程池中有正在執(zhí)行的任務或正在排隊的任務,則等他們執(zhí)行完后再結束。不允許有新的任務加進來或者排隊
  • shutdownNow() 積極性的結束,指馬上結束線程池中的所有任務,用到的是Thread的interrupt()方法
  • ThreadPoolExecutor 線程池對象,包含各種不同的線程池類型,該對象的實例用ExecutorService接收。如:ExecutorService executor = Executors.newCachedThreadPool();

ThreadPoolExecutor構造方法的參數說明:
corePoolSize: 線程數的默認值(也稱核心線程數),即當創(chuàng)建該線程池時,就自動創(chuàng)建默認數個線程,當線程被回收時,保留默認數個線程
maximumPoolSize: 線程數的最大值,當線程增加到最大數個后,線程就不能再增加
keepAliveTime: 線程等待被回收的時間,當線程執(zhí)行完處于閑置狀態(tài)時,線程不會被立即回收,需要等待設定的時候后再進行回收,便于復用(避免頻繁創(chuàng)建線程損耗性能)
unit: 時間單位
workQueue: 線程池所使用的緩沖隊列,達到核心線程數之后,新的線程放入該隊列中
threadFactory: 線程池用于創(chuàng)建線程
handler: 線程池對拒絕任務的處理策略

面試題:自定義線程池需要注意什么,核心線程數是多少

分析下線程池處理的程序是CPU密集型,還是IO密集型
CPU密集型:核心線程數 = CPU核數 + 1
IO密集型:核心線程數 = CPU核數 * 2
CPU核數 = Runtime.getRuntime().availableProcessors()
如何確定是CPU密集型還是IO密集型?

  • 計算密集型任務的特點是要進行大量的計算,消耗CPU資源,比如計算圓周率、對視頻進行高清解碼等等,全靠CPU的運算能力
  • 涉及到網絡、磁盤IO的任務都是IO密集型任務,這類任務的特點是CPU消耗很少,任務的大部分時間都在等待IO操作完成(因為IO的速度遠遠低于CPU和內存的速度)

具體程序是CPU密集型,還是IO密集型請參考鏈接

3、線程間通信

線程之間有兩種通信的方式:消息傳遞和共享內存

  • 共享內存:線程之間共享程序的公共狀態(tài),通過讀/寫修改公共狀態(tài)進行隱式通信。如子線程中獲取和修改主線程的變量,可直接獲取和修改
  • 消息傳遞:線程之間沒有公共狀態(tài),必須通過發(fā)送消息來進行顯式通信。如wait、notify等方法

Java內存模型:

Java內存模型中規(guī)定所有變量都存儲在主內存(虛擬機內存的一部分)中,主要對應Java的堆內存。這里提到的變量實際上是指共享變量,存在線程間競爭的變量,如:實例變量、靜態(tài)變量和構成數組對象的元素,而局部變量和方法參數因為是線程私有的,所以不存在線程間共享和競爭關系,所以也就不在前面提到的變量范圍內。

線程的內存

每個線程有著自己獨有的工作內存,工作內存中保存了被該線程使用到的變量,這些變量來自主內存變量的副本拷貝。線程對變量的所有讀寫操作都必須在工作內存中進行,不能直接讀寫主內存中的變量。而不同線程間的工作內存也是獨立的,一個線程無法訪問其他線程的工作內存中的變量。即子線程內的局部變量不能被其他線程共享。

線程間共享內存:

原理:
線程工作時,把需要的變量從主內存中拷貝到自己的工作內存,線程運行結束之后再將自己工作內存中的變量寫回到主內存中,而多個線程間對變量的交互只能通過主內存來間接實現。具體的線程、工作內存、主內存的交互關系圖如下:

thread_diagram.png

思考:通過上面的圖和前面的介紹,我們就很容易明白我們平常所說的多線程編程時遇到數據狀態(tài)不一致的問題是怎么產生的。例如:線程1和線程2都需要操作主內存中的共享變量A,當線程1已經在工作內存中修改了共享變量A副本的值但是還沒有寫回主內存,這時線程2拷貝了主內存中共享變量A到自己的工作內存中,緊接著線程1將自己工作內存中修改過的共享變量A的副本寫回到了主內存,很明顯線程2加載的共享變量A是之前的舊狀態(tài)的數據,這樣就產生了數據狀態(tài)不一致的問題(即線程同步問題)。
參考鏈接

4、線程同步

如何解決以上線程同步問題呢?Java中有如下幾種方式:

保證變量的原子性

即一個操作或者多個操作,要么全部執(zhí)行并且執(zhí)行的過程不會被任何因素打斷,要么就都不執(zhí)行。
比如 a=0;(a非long和double類型) 這個操作是不可分割的,那么我們說這個操作時原子操作。再比如:a++; 這個操作實際是temp = a + 1;a = temp,是可分割的,所以他不是一個原子操作。
非原子操作都會存在線程安全問題,需要我們使用同步技術(sychronized)來讓它變成一個原子操作。一個操作是原子操作,那么我們稱它具有原子性。java的concurrent包下提供了一些原子類,我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。另,大致可以認為基礎數據類型的訪問和讀寫是具備原子性的。

使用volatile關鍵字

保證資源的同步性,即被volatile 修飾的變量,如int aaa = 0,會以最高的積極性去維護同步。當在其他線程中aaa被修改后,會立刻同步到主內存中。當其他線程讀取aaa時,會先去主內存中讀取,同步到子線程中,以防被其他線程修改。即保證了每次讀寫變量都從主內存中讀。

使用synchronized關鍵字

該關鍵字作用在代碼塊、方法(一般方法、靜態(tài)方法)上。由于可以使用不同的類型來作為鎖,因此分成了類鎖和對象鎖。給代碼塊、方法加一個同步的monitor,被監(jiān)視的地方具有同步性。被同一個monitor修飾的方法、代碼塊具有互斥性。

  • 類鎖:使用字節(jié)碼文件(即.class)作為鎖。如靜態(tài)同步函數(使用本類的.class),同步代碼塊中使用.class。
  • 對象鎖:使用對象作為鎖。如同步函數(使用本類實例,即 this),同步代碼塊中是用引用的對象。
    public Object obj = new Object();
     // 靜態(tài)同步函數,使用本類字節(jié)碼做類鎖(即Demo.class)
    public static synchronized void method1() {
    }
    public void method2() {
        // 同步代碼塊,使用字節(jié)碼做類鎖
        synchronized (Demo.class) { 
        }
    }
    // 同步函數,使用本類對象實例即this做對象鎖
    public synchronized void method3() { 
    }
    // 同步代碼塊,使用本類對象實例即this做對象鎖
    public void method4() {
        synchronized (this) { 
        }
    }
    public void method5() {
        //同步代碼塊,使用共享數據obj實例做對象鎖。
        synchronized (obj) { 
        }
    }
}

synchronized括號里的參數,可以看成是一個monitor。

ReentrantLock

對代碼塊進行加鎖。ReentrantLock允許同一個線程多次調用lock接口獲取鎖,每調用一次計數便加一。因此在釋放鎖的時候必須調用相應多次數unlock才能釋放鎖:

         mLock.lock();
         System.out.println("ThreadOne: start");
         try {
             for (int m = 0; m < Integer.MAX_VALUE; m++) {
                 num++;
             }
             System.out.println("ThreadOne: over");
         } finally {  //需要在finally中進行解鎖,避免被鎖的代碼塊出現異常而無法釋放鎖的情況
             mLock.unlock();
         }

同時,可以使用ReentrantReadWriteLock分別獲取讀鎖和寫鎖,當只進行讀操作時,只用讀鎖即可。保證了多個線程同時調用被讀鎖鎖住的資源,不會出現同步性問題。

ThreadLocal的作用

ThreadLocal是JDK包提供的,它提供線程本地變量,如果創(chuàng)建了ThreadLocal變量,那么訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內存中的變量,從而規(guī)避了線程安全問題
作用:
ThreadLocal是除了加鎖這種同步方式之外的一種保證一種規(guī)避多線程訪問出現線程不安全的方法,當我們在創(chuàng)建一個變量后,如果每個線程對其進行訪問的時候訪問的都是線程自己的變量這樣就不會存在線程不安全問題。即實現了線程隔離,線程直接是不能共享內存的,即該對象不管在哪個線程中讀取數據,讀到的都是該線程中自己的數據,不跟其他線程同步。

5、線程間通信

結束線程的方式:

  • Thread.stop() 已被棄用,因為調用該方法后,線程會被立即終止,無法確定程序運行到哪里了。
  • Thread.interrupt() 當前流行的使用方式,配合isInterrupt()使用。調用該方法后,會把線程標記為被停止執(zhí)行狀態(tài),在子線程中,程序員可通過isInterrupt()方法來決定在哪里終止操作(即可由程序員控制終止的地方)。比如在一個耗時方法執(zhí)行前,使用該判斷,如果為true,則不繼續(xù)往下執(zhí)行。

wait()、notifyAll()

  • 被synchronized修飾的代碼塊A持有了鎖,但是該代碼塊需要等待某個變量aaa滿足條件后才能繼續(xù)往下執(zhí)行,而修改aaa的代碼在另一個被同一個monitor監(jiān)控的線程B里。則測試可調用wait()方法來暫時釋放鎖,讓B執(zhí)行,當B執(zhí)行后,需要調用notifyAll()方法來喚醒其他等待中的線程,之后A就可以繼續(xù)執(zhí)行。
  • wait()、notify()、notifyAll(),為Object的方法,在方法中調用時,是synchronized的monitor去做等待和喚醒操作
  • notify喚醒正在等待中的一個線程(喚醒哪一個是沒有順序的,隨機),notifyAll喚醒等待中的所有線程。
  • wait()、notifyAll() 必須成對使用,且都是需要被synchronized的同一個monitor監(jiān)控。
    image.png

join()

功能和wait()、notify()方法一樣,當某個線程threadA執(zhí)行時,需要等待另一個線程threadB先執(zhí)行完,則可以在線程A中調用threadB.join(),達到等待的效果。

yield()

該方法作用是把自己的線程時間線讓出一下下,讓給跟自己同優(yōu)先級的其他線程。作用等價于wait(),更短時間的wait,自動wait然后恢復。

樂觀鎖與悲觀鎖

數據庫相關的業(yè)務常用(高并發(fā)控制),安卓中不常用。

  • 樂觀鎖:讀數據時不加鎖,寫數據時先判斷是否有更新,再加鎖。總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。
  • 悲觀鎖:讀數據時就上鎖??偸羌僭O最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完后再把資源轉讓給其它線程)

死鎖

多重鎖容易導致死鎖

synchronized (obj1) { 
            work();
            synchronized (obj2) {
                work();
            }
        }

sleep和wait區(qū)別

  • sleep:是將當前線程阻塞,如:sleep(1000),阻塞1秒。sleep是Thread的靜態(tài)方法,在哪個線程中調用,哪個線程就休眠。
  • wait:當前被synchronized修飾的代碼塊A持有了鎖,該代碼中調用了wait()后,將monitor讓出(即將A所在的線程掛起),給其他擁有同一個monitor的代碼塊B執(zhí)行,直到代碼塊A被喚醒后才可繼續(xù)執(zhí)行(可用monitor.notifyAll()喚醒)。wait是Object的方法。

2、泛型


參考

3、Java 的 IO、NIO 和 Okio

io是輸入輸出流,它的作用就是對外部進行數據交互使用的,內部和外部分別表示的是內存以及內存以外的,外部包括手機文件,電腦文件和網絡, 服務器等都稱為外部 ,外部統(tǒng)稱為文件和網絡
詳情略
參考鏈接

4、集合

概述:

Java集合里使用接口來定義功能,是一套完善的繼承體系。Iterator是所有集合的總接口,其他所有接口都繼承于它,該接口定義了集合的遍歷操作,Collection接口繼承于Iterator,是集合的次級接口(Map獨立存在),定義了集合的一些通用操作。

image.png

參考鏈接

結構:

image.png

List:有序、可重復;索引查詢速度快;插入、刪除伴隨數據移動,速度慢;
Set:無序,不可重復;
Map:鍵值對,鍵唯一,值多個;

面試題:

  • ArrayList與LinkedList的區(qū)別和適用場景?

Arraylist:
優(yōu)點:ArrayList是實現了基于動態(tài)數組的數據結構,因地址連續(xù),一旦數據存儲好了,查詢操作效率會比較高(在內存里是連著放的)。
缺點:因為地址連續(xù),ArrayList要移動數據,所以插入和刪除操作效率比較低。
LinkedList:
優(yōu)點:LinkedList基于鏈表的數據結構,地址是任意的,其在開辟內存空間的時候不需要等一個連續(xù)的地址,對新增和刪除操作add和remove,LinedList比較占優(yōu)勢。LikedList 適用于要頭尾操作或插入指定位置的場景。
缺點:因為LinkedList要移動指針,所以查詢操作性能比較低。
適用場景分析:
當需要對數據進行對應訪問的情況下選用ArrayList,當要對數據進行多次增加刪除修改時采用LinkedList。

  • ArrayList和LinkedList怎么動態(tài)擴容的?

ArrayList: ArrayList 的初始大小是0,然后,當add第一個元素的時候大小則變成10。并且,在后續(xù)擴容的時候會變成當前容量的1.5倍大小。
LinkedList: LinkedList 是一個雙向鏈表,沒有初始化大小,也沒有擴容的機制,就是一直在前面或者后面新增就好。

  • HashMap原理
  • HashMap何時擴容
  • Hashmap如何解決散列碰撞(必問)?

Java中HashMap是利用“拉鏈法”處理HashCode的碰撞問題。在調用HashMap的put方法或get方法時,都會首先調用hashcode方法,去查找相關的key,當有沖突時,再調用equals方法。hashMap基于hasing原理,我們通過put和get方法存取對象。當我們將鍵值對傳遞給put方法時,他調用鍵對象的hashCode()方法來計算hashCode,然后找到bucket(哈希桶)位置來存儲對象。當獲取對象時,通過鍵對象的equals()方法找到正確的鍵值對,然后返回值對象。HashMap使用鏈表來解決碰撞問題,當碰撞發(fā)生了,對象將會存儲在鏈表的下一個節(jié)點中。hashMap在每個鏈表節(jié)點存儲鍵值對對象。當兩個不同的鍵卻有相同的hashCode時,他們會存儲在同一個bucket位置的鏈表中。鍵對象的equals()來找到鍵值對。

常見面試題

5、ClassLoader

概念:

Classloader負責將Class加載到JVM中,并且確定由那個ClassLoader來加載(父優(yōu)先的等級加載機制)。還有一個任務就是將Class字節(jié)碼重新解釋為JVM統(tǒng)一要求的格式

類加載器的雙親委派模型

當一個類加載器收到一個類加載的請求,它首先會將該請求委派給父類加載器去加載,每一個層次的類加載器都是如此,因此所有的類加載請求最終都應該被傳入到頂層的啟動類加載器(Bootstrap ClassLoader)中,只有當父類加載器反饋無法完成這個列的加載請求時(它的搜索范圍內不存在這個類),子類加載器才嘗試加載。其層次結構示意圖如下:

image.png

作用:

  • 可以避免重復加載,父類已經加載了,子類就不需要再次加
  • 更加安全,很好的解決了各個類加載器的基礎類的統(tǒng)一問題,如果不使用該種方式,那么用戶可以隨意定義類加載器來加載核心api,會帶來相關隱患。
    參考鏈接

6、JVM

JVM基本構成

image.png

從上圖可知,JVM主要包括四個部分:

  • 1.類加載器(ClassLoader):在JVM啟動時或者在類運行將需要的class加載到JVM中。(下圖表示了從java源文件到JVM的整個過程,可配合理解。


    image.png
  • 2.執(zhí)行引擎:負責執(zhí)行class文件中包含的字節(jié)碼指令
  • 3.內存區(qū)(也叫運行時數據區(qū)):是在JVM運行的時候操作所分配的內存區(qū)。運行時內存區(qū)主要可以劃分為5個區(qū)域,如圖:


    image.png

方法區(qū)(MethodArea):用于存儲類結構信息的地方,包括常量池、靜態(tài)常量、構造函數等。雖然JVM規(guī)范把方法區(qū)描述為堆的一個輯部分, 但它卻有個別名non-heap(非堆),所以大家不要搞混淆了。方法區(qū)還包含一個運行時常量池。

java堆(Heap):存儲java實例或者對象的地方。這塊是GC的主要區(qū)域。從存儲的內容我們可以很容易知道,方法和堆是被所有java線程共享的。
java棧(Stack):java棧總是和線程關聯在一起,每當創(chuàng)一個線程時,JVM就會為這個線程創(chuàng)建一個對應的java棧在這個java棧中,其中又會包含多個棧幀,每運行一個方法就建一個棧幀,用于存儲局部變量表、操作棧、方法返回等。每一個方法從調用直至執(zhí)行完成的過程,就對應一棧幀在java棧中入棧到出棧的過程。所以java棧是現成有的。
程序計數器(PCRegister):用于保存當前線程執(zhí)行的內存地址。由于JVM程序是多線程執(zhí)行的(線程輪流切換),所以為了保證程切換回來后,還能恢復到原先狀態(tài),就需要一個獨立計數器,記錄之前中斷的地方,可見程序計數器也是線程私有的。
本地方法棧(Native MethodStack):和java棧的作用差不多,只不過是為JVM使用到native方法服務的。

  • 4.本地方法接口:主要是調用C或C++實現的本地方法及回調結果。

GC的原理和回收策略

引用管理

Java中使用可達性算法對對象進行是否可回收的標記算法。通過一系列稱為GCRoots的對象作為起始點,從這些節(jié)點從上向下搜索,所走過的路徑稱為引用鏈,當一個對象沒有任何引用鏈與GCRoots連接時就說明此對象不可用,也就是對象不可達。

回收算法

  • 1.標記-清除(Mark-sweep)
    標記-清除算法采用從根集合進行掃描,對存活的對象進行標記,標記完畢后,再掃描整個空間中未被標記的對象,進行回收。標記-清除算法不需要進行對象的移動,并且僅對不存活的對象進行處理,在存活對象比較多的情況下極為高效,但由于標記-清除算法直接回收不存活的對象,因此會造成內存碎片。
  • 2.標記-整理(Mark-Compact)
    標記-整理算法采用標記-清除算法一樣的方式進行對象的標記,但在清除時不同,在回收不存活的對象占用的空間后,會將所有的存活對象往左端空閑空間移動,并更新對應的指針。標記-整理算法是在標記-清除算法的基礎上,又進行了對象的移動,因此成本更高(耗時),但是卻解決了內存碎片的問題。該垃圾回收算法適用于對象存活率高的場景(老年代)。
  • 3.復制(Copying)
    ?復制算法將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。這種算法適用于對象存活率低的場景,比如新生代。這樣使得每次都是對整個半區(qū)進行內存回收,內存分配時也就不用考慮內存碎片等復雜情況。
  • 4.分代收集算法
    不同的對象的生命周期(存活情況)是不一樣的,而不同生命周期的對象位于堆中不同的區(qū)域,因此對堆內存不同區(qū)域采用不同的策略進行回收可以提高 JVM 的執(zhí)行效率。當代商用虛擬機使用的都是分代收集算法:新生代對象存活率低,就采用復制算法;老年代存活率高,就用標記清除算法或者標記整理算法。Java堆內存一般可以分為新生代、老年代和永久代三個模塊。
    參考鏈接
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容