初探JVM内存区域

1 JVM架构

线程私有内存:程序计数器,Java虚拟机栈,本地方法栈
线程共享内存:Java堆,方法区
引用
程序计数器(Program Counter Register):当前线程所执行的字节码的行号指示器。如果线程执行的是一个java方法,这个计算器记录的是正在持行的虚拟机字节码指令的地址;如果执行的是Native方法,这个计数器则为空。此区域是java虚拟器规范中唯一没有定义任何OutOfMemoryError情况的区域

引用
Java虚拟机栈:它是描述java方法执行的内存模型:每个方法被执行的时候都会创建一个stack frame用于储存局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈道出栈的过程。
如果线程所请求的栈深度大于虚拟机所永许的深度,将抛出StackOverflowError,如果虚拟机栈可以动态扩展,当扩展时无法申请足够的内存时会抛出OutOfMemoryError。

引用
本地方法栈:与java虚拟机栈作用类似,不过他是位执行Native方法服务。本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError

引用
Java堆: 它是虚拟机管理的内存中最大的一块。它是被所有线程共享的一块区域,是虚拟机创建的。它是存放对象实例,几乎所有对象实例都在堆上分配。它是垃圾回收器管理的主要区域。可以进一步分为Eden空间,from survivor控件爱你,to survivor空间等,可以通过-Xmx 和 -Xms 控制堆的大小,当缺少内存时出现OutOfMemoryError

引用
方法区:它和java堆一样,是各个线程共享的内存区域,它用于存储以被虚拟器加载的类信息/常量/静态变量/即时编译器编译后的代码等数据。如果它无法满足内存分配,将抛出OutOfMemoryError异常。

运行时常量池:它是方法区的一部分,Class文件中除啦有类的版本,字段,方法,接口等描述性信息外,还有一个是常量池(Constant Pool Table),用于存放编译器生成的各种字面量和符号引用,这部分内容会在类加载后存放到方法去的运行时常量池中。当缺少内存时出现OutOfMemoryError



2 基于sun jvm 实战OutOfMemoryError异常情况和分析过程

堆内存错误分析
import java.util.ArrayList;
import java.util.List;

/**
 *VM Arguments: -verbose:gc -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运行参数 -verbose:gc -Xms20m -Xmx20m


参数含义
-verbose:gc 显示gc过程
-Xms 堆的最小动态内存
-Xmx 堆的最大动态内存
-XX:+HeapDumpOnOutOfMemoryError dump堆快照


运行结果:

引用
可以使用eclipse memory analyzer这个Jvm堆分析内存泄漏或者溢出的原因.
eclipse memory analyzer update url: http://download.eclipse.org/mat/1.1/update-site/

如果内存泄漏可以看卡泄漏对象到GC Roots的引用链,从而分析出泄漏的代码位置。我们可从下图看出OOMObject占用大量堆空间。

如果是内存溢出,那就是说内存中的对象都应该存在着,那就检查虚拟机参数-Xmx与-Xms是否可以变大。


eclipse memory analyzer dominator tree 视图


Shallow heap 对象自身占用的heap空间,不包括它引用的对象
Retained heap 前对象+当前对象可直接或间接引用到的对象总体所占用的空间



其他与jvm内存空间相关参数

-Xss 设置虚拟机栈和本地方法栈的大小
-XX:PermSize 方法区最小空间
-XX:MaxPermSize 方法区最大空间




栈溢出
public class JavaVMStackOOM
{

    private int stackLength = 1;

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

    /**
     * vm args -Xss128k
     */
    public static void main(String[] args)
    {
        JavaVMStackOOM stackOOM = new JavaVMStackOOM();
        stackOOM.stackLeak();
    }

}



运行时常量池溢出
public class ConstantsPoolOOM
{

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

}



方法区异常
public class MethodAreaOOM
{

    /**
     * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
     */
    public static void main(String[] args)
    {
        while (true)
        {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor()
            {

                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
                    throws Throwable
                {
                    return proxy.invokeSuper(obj, args);
                }
            });
            enhancer.create();
        }
    }

    static class OOMObject
    {

    }
}


本机直接内存溢出
public class DirectMemoryOOM
{

    private static final int _1MB = 1024 * 1024;

    /**
     * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
     * 
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     */
    public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException
    {
        Field unsafeFiled = Unsafe.class.getDeclaredFields()[0];
        unsafeFiled.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeFiled.get(null);
        while (true)
        {
            unsafe.allocateMemory(_1MB);
        }
    }

}

猜你喜欢

转载自caerun.iteye.com/blog/1162634
今日推荐