1. 程式人生 > >理解JVM(2) 棧記憶體,方法區,堆記憶體

理解JVM(2) 棧記憶體,方法區,堆記憶體

堆,方法區,棧的關係

分配最大堆記憶體-Xmx32m

class SimpleHeap(val id: Int){
    fun show() = println("My id is $id")
}

fun main(args: Array<String>) {
    val s1 = SimpleHeap(1)
    val s2 = SimpleHeap(2)

    s1.show()
    s2.show()
}

方法區內儲存類的基本資訊,包括方法的實現。方法區裡面的資訊很少清除

Java堆內儲存著s1,s2的例項

Java棧內儲存著s1和s2的方法show()的區域性變數

棧的溢位測試

棧幀包括:區域性變量表(原生型別或引用型別的物件引用),運算元棧(類似於暫存器結構,用於計算),幀資料區(常量池指標和異常處理表)

分配最大棧記憶體-Xss228K

var count = 0
class SimpleHeap{
    fun show(){
        count++
        val KB = ByteArray(1024*10)
        KB.set(count, count.toByte());
        show()
    }
}

上面程式碼,我以為是儲存了10K的區域性變數,後來發現數組還是放在堆記憶體裡面的,棧中只儲存一個引用。所以還是能遞迴3000次吧

var count = 0
class SimpleHeap{
    fun show(){
        count++
        val a = 1L
        val b = 2L
        val c = 3L
        val d = 4L
        val e = 5L
        val f = 6L
        val g = 7L
        val h = 8L
        val i = 9L
        val j = 10L
        show()
    }
}

一共呼叫670次,每次呼叫會使用350個位元組,然後區域性變數會儲存80位元組的long型區域性變數

堆記憶體回收分析

class SimpleHeap{
    fun gc1(){
        val MB = ByteArray(1024*1024*6)
        System.gc() //不會馬上回收記憶體
    }

    fun gc2(){
        var MB: ByteArray? = ByteArray(1024*1024*6)
        MB = null
        System.gc()
    }

    fun gc3(){
        {
            var MB = ByteArray(1024*1024*6)
        }
        System.gc()
    }

    fun gc4(){
        {
            var MB = ByteArray(1024*1024*6)
        }
        val c = 10
        System.gc()
    }

    fun gc5(){
        gc1()
        System.gc()
    }
}

gc1()

這裡寫圖片描述
可以看到沒有回收記憶體。

gc2()

這裡寫圖片描述
可以發現,又多分配了6MB,然後馬上回收,這次一次性回收了12MB,因為gc1()的6MB也給回收了。

gc3(),gc4(),gc5()

這裡寫圖片描述
gc3()gc4()不能為什麼,根本沒有分配記憶體,說不定給Kotlin編譯器給優化了。
gc5()在gc1()退出作用域後,直接回收掉了6MB

棧上分配記憶體

對於那些執行緒私有的物件(指不會被其他執行緒訪問到的物件),可以打散分配在棧上,而不是分配在堆上。在函式呼叫後自行下載,而不用垃圾收集器。

實現的技術是進行逃逸分析-XX:+DoEscapeAnalysis

/*
-server -Xmx10m -Xms10m 
-XX:+PrintGC -XX:+DoEscapeAnalysis 
-XX:-UseTLAB -XX:+EliminateAllocations
*/
class OnStackTest{
    class User(val id:Int = 0, val name:String = ""){
    }
    companion object {
        fun alloc(){
            val u = User(5,"owen")
        }
    }
}

fun main(args: Array<String>) {
    val b = System.currentTimeMillis()
    for (i in 0..1000000000){
        OnStackTest.alloc()
    }
    val c = System.currentTimeMillis()
    println(c - b)

}

要呼叫100000000次,按理說會頻繁呼叫GC,但棧上分配技術顯示, 咳咳,按理說是看不到GC日誌

[GC (Allocation Failure)  2047K->544K(9728K), 0.0032233 secs]
[GC (Allocation Failure)  2592K->472K(11776K), 0.0080457 secs]
60

方法區

儲存系統的類資訊,比如類的欄位,方法,常量池。

Java1.6,1.7可以理解為永久代(Perm),設定引數為-XX:PermSize=5m,-XX:MaxPermSize=5m

Java1.8中變成了元資料區,使用-XX:MaxMetaspaceSize指定,這是一塊堆外的直接記憶體,如果不指定大小,虛擬機器會耗盡所有系統的可用記憶體

這裡寫圖片描述