解讀Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)

Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)

虛擬機(jī)棧

虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀。
我們經(jīng)常說(shuō)的堆內(nèi)存和棧內(nèi)存,所指的棧就是指虛擬機(jī)棧。
局部變量表存放了編譯期可知的各種基本數(shù)據(jù)類型、對(duì)象引用和returnAddress類型。

打印棧信息

jstack pid
Java棧信息

線程請(qǐng)求的棧深度大于虛擬機(jī)所允許的最大深度,將拋出StackOverFlowError

/**
 * VM Args: -Xss160k
 */
public class JavaVMStackSOF {
    private int stackLength = 1;

    public void stackLeak() {
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        }  catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}
java棧溢出

Java堆

Java堆是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊,是被所有線程共享的一塊內(nèi)存區(qū)域。
此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例。
幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存。
是垃圾收集器管理的主要區(qū)域,也被稱作“GC堆”。

打印堆信息

jmap -dump:format=b,file=test.bin pid
jhat test.bin
Java堆信息

java堆的參數(shù)設(shè)置

-Xms:設(shè)置堆的最小值
-Xmx:設(shè)置堆的最大值
-XX:+HeapDumpOnOutOfMemoryError:在出現(xiàn)內(nèi)存溢出異常時(shí)Dump出當(dāng)前的內(nèi)存堆轉(zhuǎn)儲(chǔ)快照

Java堆溢出

import java.util.ArrayList;
import java.util.List;

/**
 * VM ARGS: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {
    static class OOMObject {
    }

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

這一次我們換一種堆分析工具,用Eclipse Memory Analyzer來(lái)分析快照文件。


用Eclipse Memory Analyzer分析快照

案例:fastjson內(nèi)存泄露測(cè)試

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.util.ParameterizedTypeImpl;

import java.lang.reflect.Type;

/**
 * VM ARGS: -Xms100m -Xmx100m -XX:+HeapDumpOnOutOfMemoryError
 */
public class Main {
    public static void main(final String[] args) {
        UserInfo userInfo=new UserInfo();
        userInfo.setName("zyr");
        userInfo.setPassword("123");
        WrapReturn wrapReturn = new WrapReturn();
        wrapReturn.setResult(userInfo);
        byte[] bytes = JSON.toJSONBytes(new WrapReturn(userInfo));
        while (true){
            Object o = JSON.parseObject(bytes, new ParameterizedTypeImpl(new Type[]{UserInfo.class}, null, WrapReturn.class));
        }
    }
}

用jvisualvm觀察內(nèi)存使用情況:


內(nèi)存使用情況

OutOfMemoryError

用堆工具查看dump文件

方法區(qū)

方法區(qū)用于存儲(chǔ)已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
也有人會(huì)把方法區(qū)稱為永久代,本質(zhì)上兩者并不等價(jià),僅僅是因?yàn)镠otSpot虛擬機(jī)的設(shè)計(jì)團(tuán)隊(duì)選擇把GC分代收集擴(kuò)展至方法區(qū)。

運(yùn)行時(shí)常量池

Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯器生成的各種字面量和符號(hào)引用。
除了保存Class文件中描述的符號(hào)引用外,還會(huì)把翻譯出來(lái)的直接引用也存儲(chǔ)在運(yùn)行時(shí)常量池中。
運(yùn)行時(shí)常量池的另一個(gè)重要特征是具備動(dòng)態(tài)性,運(yùn)行期間也可能將新的常量放入池中,用得比較多的是String類的intern()方法。

        String a = "test";
        String b = "test";
        String c = new String("test");
        String d = new String("test");
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(c == d);

問(wèn)題:

  1. 為什么a == b?
    因?yàn)樗鼈冎赶虺A砍乩锿粔K引用。
  2. 為什么a != c?
    因?yàn)閍為常量池中的引用,c為堆中的實(shí)例引用。
  3. 為什么c != d?
    因?yàn)閏和d為堆中的兩個(gè)不同實(shí)例的引用。
  4. 怎樣修改代碼,讓a == b == c == d?
    ①用.equal方法來(lái)做比較,這是常用的方法,生產(chǎn)中都應(yīng)該用此方法做比較。
    ②使用intern(),讓所有的比較都基于常量池中的引用,a.intern() == b.intern() == c.intern() == d.intern()。這里只是為了加強(qiáng)大家對(duì)常量池的理解,生產(chǎn)環(huán)境不要這樣使用。

String類的intern()方法定義:

public native String intern();

注意看,這是一個(gè)native方法,它的返回值是String,指返回常量池中的字符串。

運(yùn)行時(shí)常量池溢出

import java.util.ArrayList;
import java.util.List;

/**
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
 */
public class RuntimeConstantPool10M {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}

java與php的比較

《深入理解Java虛擬機(jī)》中,作者常說(shuō):Java與C++之間有一堵由內(nèi)存動(dòng)態(tài)分配和垃圾收集技術(shù)所圍成的“高墻”,墻外面的人想進(jìn)去,墻里面的人卻想出來(lái)。
作為前php開(kāi)發(fā)程序員,最后我來(lái)談?wù)刯ava與php的差別。
打個(gè)不太恰當(dāng)?shù)谋扔?,php就像一臺(tái)游戲機(jī),而java像一臺(tái)計(jì)算機(jī)。


php

java

如果只是網(wǎng)站開(kāi)發(fā),php的nginx + php-fpm + mysql模型已經(jīng)非常成熟,體現(xiàn)了unix的軟件思想,與其他命令組合來(lái)解決問(wèn)題。一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)進(jìn)程,用完即扔,完全不需要考慮內(nèi)存方面的問(wèn)題(就算有內(nèi)存泄露,重啟進(jìn)程就好了),簡(jiǎn)單粗暴。在傳統(tǒng)的網(wǎng)頁(yè)開(kāi)發(fā)興起時(shí)風(fēng)靡一時(shí)。就如同游戲機(jī)一樣,針對(duì)性特別強(qiáng),簡(jiǎn)單高效。

而使用java開(kāi)發(fā)網(wǎng)站項(xiàng)目,不僅需要了解業(yè)務(wù),對(duì)底層的運(yùn)行原理都要有所涉及,并且需要針對(duì)性能調(diào)優(yōu)。程序涉及到的方方面面都要了解,雖然更加復(fù)雜,也提供了更強(qiáng)大的功能。比如最近興起的服務(wù)化,基于java可以輕松地實(shí)現(xiàn),用php做就有點(diǎn)捉襟見(jiàn)肘了。就如同計(jì)算機(jī)一樣,什么都能做,不僅能實(shí)現(xiàn)游戲的功能,還能上網(wǎng)聊天,這是游戲機(jī)做不到的。

參考資料:
《深入理解Java虛擬機(jī)》第2版
深入理解Java虛擬機(jī)筆記一(Java內(nèi)存區(qū)域與內(nèi)存溢出異常)
《Java虛擬機(jī)原理圖解》3、JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)
Java的native方法
parseObject是否存在內(nèi)存泄漏情況
深入解析String#intern

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容