
虛擬機(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

線程請(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虛擬機(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堆的參數(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)分析快照文件。

案例: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)存使用情況:



方法區(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)題:
- 為什么a == b?
因?yàn)樗鼈冎赶虺A砍乩锿粔K引用。 - 為什么a != c?
因?yàn)閍為常量池中的引用,c為堆中的實(shí)例引用。 - 為什么c != d?
因?yàn)閏和d為堆中的兩個(gè)不同實(shí)例的引用。 - 怎樣修改代碼,讓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ī)。


如果只是網(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