解读Java虚拟机运行时数据区

Java虚拟机运行时数据区

虚拟机栈

虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧。
我们经常说的堆内存和栈内存,所指的栈就是指虚拟机栈。
局部变量表存放了编译期可知的各种基本数据类型、对象引用和returnAddress类型。

打印栈信息

jstack pid
Java栈信息

线程请求的栈深度大于虚拟机所允许的最大深度,将抛出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虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域。
此内存区域的唯一目的就是存放对象实例。
几乎所有的对象实例都在这里分配内存。
是垃圾收集器管理的主要区域,也被称作“GC堆”。

打印堆信息

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

java堆的参数设置

-Xms:设置堆的最小值
-Xmx:设置堆的最大值
-XX:+HeapDumpOnOutOfMemoryError:在出现内存溢出异常时Dump出当前的内存堆转储快照

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来分析快照文件。


用Eclipse Memory Analyzer分析快照

案例:fastjson内存泄露测试

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观察内存使用情况:


内存使用情况

OutOfMemoryError

用堆工具查看dump文件

方法区

方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
也有人会把方法区称为永久代,本质上两者并不等价,仅仅是因为HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区。

运行时常量池

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种字面量和符号引用。
除了保存Class文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。
运行时常量池的另一个重要特征是具备动态性,运行期间也可能将新的常量放入池中,用得比较多的是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);

问题:

  1. 为什么a == b?
    因为它们指向常量池里同一块引用。
  2. 为什么a != c?
    因为a为常量池中的引用,c为堆中的实例引用。
  3. 为什么c != d?
    因为c和d为堆中的两个不同实例的引用。
  4. 怎样修改代码,让a == b == c == d?
    ①用.equal方法来做比较,这是常用的方法,生产中都应该用此方法做比较。
    ②使用intern(),让所有的比较都基于常量池中的引用,a.intern() == b.intern() == c.intern() == d.intern()。这里只是为了加强大家对常量池的理解,生产环境不要这样使用。

String类的intern()方法定义:

public native String intern();

注意看,这是一个native方法,它的返回值是String,指返回常量池中的字符串。

运行时常量池溢出

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虚拟机》中,作者常说:Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。
作为前php开发程序员,最后我来谈谈java与php的差别。
打个不太恰当的比喻,php就像一台游戏机,而java像一台计算机。


php

java

如果只是网站开发,php的nginx + php-fpm + mysql模型已经非常成熟,体现了unix的软件思想,与其他命令组合来解决问题。一个请求对应一个进程,用完即扔,完全不需要考虑内存方面的问题(就算有内存泄露,重启进程就好了),简单粗暴。在传统的网页开发兴起时风靡一时。就如同游戏机一样,针对性特别强,简单高效。

而使用java开发网站项目,不仅需要了解业务,对底层的运行原理都要有所涉及,并且需要针对性能调优。程序涉及到的方方面面都要了解,虽然更加复杂,也提供了更强大的功能。比如最近兴起的服务化,基于java可以轻松地实现,用php做就有点捉襟见肘了。就如同计算机一样,什么都能做,不仅能实现游戏的功能,还能上网聊天,这是游戏机做不到的。

参考资料:
《深入理解Java虚拟机》第2版
深入理解Java虚拟机笔记一(Java内存区域与内存溢出异常)
《Java虚拟机原理图解》3、JVM运行时数据区
Java的native方法
parseObject是否存在内存泄漏情况
深入解析String#intern

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容