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

解读Java虚拟机运行时数据区_第1张图片
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虚拟机运行时数据区_第2张图片
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 list = new ArrayList();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

这一次我们换一种堆分析工具,用Eclipse Memory Analyzer来分析快照文件。


解读Java虚拟机运行时数据区_第3张图片
用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观察内存使用情况:


解读Java虚拟机运行时数据区_第4张图片
内存使用情况

OutOfMemoryError

解读Java虚拟机运行时数据区_第5张图片
用堆工具查看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 list = new ArrayList();
        int i = 0;
        while (true) {
            list.add(String.valueOf(i++).intern());
        }
    }
}

java与php的比较

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


解读Java虚拟机运行时数据区_第6张图片
php

解读Java虚拟机运行时数据区_第7张图片
java

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

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

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

你可能感兴趣的:(解读Java虚拟机运行时数据区)