初探JVM内存

作者: wilsoncai 分类: JVM 发布时间: 2018-01-06 00:47

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异常情况和分析过程

堆内存错误分析
Java代码 收藏代码

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 方法区最大空间

栈溢出
Java代码 收藏代码

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();  
    }  
  
}  

运行时常量池溢出
Java代码 收藏代码

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());  
        }  
    }  
  
}  

方法区异常
Java代码 收藏代码

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  
    {  
  
    }  
}  

本机直接内存溢出
Java代码 收藏代码

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);  
        }  
    }  
  
}  

发表评论

电子邮件地址不会被公开。 必填项已用*标注