首页 > 代码库 > Java内存分析1 - 从两个程序说起

Java内存分析1 - 从两个程序说起

这次看一些关于JVM内存分析的内容。

首先来看两个程序,这里是程序一:JVMStackTest,看下代码:

package com.zhyea.robin.jvm;

public class JVMStackTest {

    private static int count = 0;

    private void recur() {
        ++count;
        recur();
        System.out.println("recur()方法执行结束");
    }

    public static void main(String[] args) {
        try {
            new JVMStackTest().recur();
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println("程序递归了 " + count + "次");
        }
    }
}

这个类很简单了。JVMStackTest的主体是一个recur()方法。recur()方法是一个递归的方法。所谓递归的方法就是在方法体中又调用了方法自身。在recur()方法中引用了一个类成员变量count来记录递归的次数,并且做了一段输出来提示方法执行结束。JVMStackTest的main方法内容也很少,就是创建了一个JVMStackTest的实例并调用recur()方法,同时使用try{}catch(){}来捕获可能发生的异常或错误,最后在程序执行结束前输出recur()方法递归的次数。

这个程序是有些问题的。问题出在recur()方法这里:recur()是一个递归的方法,但是却没有跳出的语句,这意味着recur()方法将无限的递归下去。我们需要认识到一点:资源是有限的,如果某个服务无限次的占用资源却始终不释放资源就必然会导致问题。每次调用recur()方法都会占用一些资源,资源占用得多了就会导致可用资源不足产生异常或错误。运行下这个程序,看看会报什么样的错误:

技术分享

在递归了1862次以后报了StackOverflowError,即栈溢出错误。请大家先记住这个错误信息,稍后我们会解释为什么会报这个错。

再来看看程序二,JVMHeapTest:

package com.zhyea.robin.jvm;

import java.util.LinkedList;
import java.util.List;

public class JVMHeapTest {

    private static List<byte[]> list = new LinkedList<>();

    private void loop() {
        while (true) {
            list.add(new byte[1024 * 1024]);
        }
    }

    public static void main(String[] args) {
        try {
            new JVMHeapTest().loop();
        } catch (Throwable t) {
            t.printStackTrace();
        } finally {
            System.out.println("list 的长度是:" + list.size());
        }
    }
}

这个JVMHeapTest也是很简单了,就是定义了一个List列表型的类成员变量list,通过泛型指定list中存储的单元必须是数组,而后通过loop()方法向list中不停地添加数组实例。每个数组的大小为1024,也就是1KB大小。main方法也和JVMStackTest差不多,就是创建一个类实例来调用loop方法,通过try/catch捕获可能会发生的异常,最后在程序执行结束的时候输出list的长度。

看看loop()方法,很明显地可以看出这个类也是存在问题的:这个类中的loop()方法有一个死循环,尤其是在这个死循环中还在不停向一个类成员列表添加元素。当添加的元素大小总和超过了某个上限后就会报错。具体是什么错误呢,运行一下看看:

技术分享

正如预料,发生了错误。发生错误时列表的长度是7,报的错是OutOfMemoryError,根据错误信息“Java heap space”可以看出OutOfMemory错误是发生在heap上,也就是堆上。

通过这两个程序我们演示了两种常见的内存错误:StackOverflow和OutOfMemory,即栈溢出和内存溢出。这两个错误的发生有一个共同点:无限制的占用系统某一部分资源(程序一的无限递归,程序二死循环),以至于超过了某个上限。我们需要弄明白的就是这里说的资源是什么资源,而上限又具体是多少。

关于资源,我们前面反复提到过几次,主要就是内存。不过相较于我们通常提到的内存,这里说的内存还分得更细一些。我们通常说笔记本有4G内存、8G内存,就是笔记本的机器内存,这个内存被称作是直接内存或物理内存。而我们启动一个Java程序就会从直接内存中获取一块让JVM独立使用的空间,这块空间就可以被称为是JVM内存。而JVM内存又可以被粗略地分成两大类:栈内存和堆内存。如下图:

技术分享

注意我们这里将内存分成栈内存和堆内存是一种很粗略的分法,至于准确的分法我们稍后会单独说。

看一下上图,左侧的细长条标识栈内存,右侧的椭圆标识内存。通常栈内存要比堆内存小得多。不知道有没有注意到栈内存的标示图中还有更细小的小长方形。这个小长方形标识的是栈帧。栈帧是栈内存的最小单位。

 

 

 

 

##########################

Java内存分析1 - 从两个程序说起