深入理解JVM(一) --内存區域與內存溢出
作者:强哥   类别:Java开发    日期:2017-12-08 17:19:33    阅读:2630 次   消耗积分:0 分

JVM会怎么分配内存???大多数人的回答是内存分为堆和栈,但实际上内存的分配比堆栈更严格

内存被划分为5个大区:程序计数器、本地方法栈、虚拟机栈、堆、方法区

程序计数器(线程独享) 当前线程所执行的字节码的行号指令器,通过改变计数器的值选取虚拟机下一条需要执行的字节码指令,分支、循环、跳转、异常、线程恢复等基础功能

虚拟机栈(线程独享) JAVA方法执行的内存模型,每个方法在执行的同时都会存建一个栈帧用于存储方法出口、局部变量表、操作数栈、动态连接等信息,局部表量表(大多数人认为的栈)用于存放可知各种数据类型(Boolean、byte、char、short、int、float、long、double)、对象引用类型和returnAddress(指向了一个字节码指令地址)

本地方法栈 与虚拟机栈的作用非常相似,它们之间的区别是虚拟机栈为虚拟机执行JAVA方法服务,本地方法栈为Native方法服务

方法区(线程共享) 被虚拟机加载的常量、静态量、方法描述、类信息等。JAVA虚拟机规范把方法区描述为堆的一个逻辑部分,但它还有一个别名叫做Non-Heep 运行时常量池:方法区的一部分,用于存放编译时的字面量、符号引用

堆(线程共享) 对象

在分配对象时,对象包括了三部分内容:对象头(存储自身运行数据、类型指针)、实例数据(对象真正有意义的数据)、对齐填充(并不是所有的JVM都有,以0填充)。

对象头中用于存储自身的运行数据部分:如哈希码、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等,官方称为Mark Word  Mark Word 32位空间中的25位用于存储HashCode, 4位存储对象分代年龄,2位用于锁标志位,1位固定0

对象头中用于存储类型指针部分:即对象指向的类元数据指针,虚拟机通过这个指针来确定是这个对象是哪个类的实例。如果对象是一个JAVA数组,那在对象头中还必须有一块用于记录数组长度的数据,虚拟机可以通过普通JAVA对象的元数据确定JAVA对象大小,但是从数组的元数据中无法确定数组大小

实例数据:对象真正有效信息,也是程序代码中所定义的各种类型的字段内容。无论从父类继承还是子类定义的,都需要纪录。这部分的存储顺序受虚拟机分配策略影响,部分虚拟机分配为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers)可以看出。相同宽度总是被分配到一起。

那么对象是如何被访问的?AVA程序需要通过栈上的reference数据来操作堆上的具体对象。由于reference类型在JAVA虚拟机中只规定了一个指向对象的引用,并没有规定如何访问,所以对像的访问方式取决于虚拟机的实现而定,访问的方式大体分为使用句柄访问使用直接指针访问

使用句柄访问:JAVA堆中将会划分出一块内存作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体信息

 

图片1

使用直接指针访问: JAVA堆对象的存局中就必须考虑如何放置访问数据的相关信息,而reference中存储的直接就是对象地址

图片2

了解了JAVA的内存分配,现在我们来看看JAVA在什么时候会出现OOM

JAVA堆内存的OOM:实际应用中最常见的内存溢出,JAVA堆用于存储对象实例,不断创建新对象,而GC无法清除对象(GC Roots到对象之间有可达路径),那么当对象到大最大堆容量时就会产生堆内存溢出

首先我们用如下方式修改JVM内存设置:-Xms堆最小值,-Xmx堆量大值,-Xss虚拟机栈最小值

代码:

//在Eclipse中修改JVM内存
//-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

public class HeapOOM {
    public static void main(String[] args) {
        List<HeapOOM> list = new ArrayList<HeapOOM>();
        while (true) {
            list.add(new HeapOOM());
        }
    }
}

结果:java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid2004.hprof ... Heap dump file created [32572164 bytes in 0.448 secs]

虚拟机栈溢出和本地方法栈溢出:
如果线程请求的栈深度大于虚拟机允许的最大深度,则抛出StackOverflowError 
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError 
在单线程环境下,无论由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时都将抛出StackOverflowError  
在多线程环境下则可抛出OutOfMemoryError

操作系统分配给每个进程的内存是有限制的,如32位windows限制为2GB。虚拟机提供了参数来控制JAVA堆和方法区这两部分的内存的最大值,那么虚拟机栈和本地方法栈所瓜分的内存则为2GB - Xmx(最大堆容量) - MaxPermSize(最大方法区容量)。 每个线程所能分配到的栈容量越大,线程数就越少,创建线程时越容易吧剩下内存耗尽  在虚拟机默认参数的大多数情况下 (由于每个方法压入栈的帧大小并不是一样的)栈深度能达到1000-2000的空间大小,对于正常方法调用(包括递归),这个深度完全够用,但是如果创建线程过多则会造成OutOfMemoryError,在不能减少内存数的或者更换64位系统的情况下,就只能通过减少最大堆和最少栈容量来换取更多的线程

代码:

//使用-Xss参数减少栈内存容量,以抛出StackOverflowError
//定义大量本地变量,增大此方法栈帧中本地变量表的长度,以抛出StackOverflowError
//VM -Xss128k

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

结果:
stack length :2401 Exception in thread "main"
java.lang.StackOverflowError
at demo.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at demo.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)

多線程環境下的內存溢出

// VM -Xss2m

public class JavaVMStackSOF {
    private void dontStop(){
    while(true){    } //循環目的讓線程保持不被回收的運行狀態
    }

    public void stackLeakByThread(){
        while(true){
            Thread thread = new Thread(new Runnable(){
                public void run(){
                    dontStop();
                }
            });
        thread.start();
        }
    }

    public static void main(String[] args) throws Throwable{
        JavaVMStackSOF oom = new JavaVMStackSOF();
        oom.stackLeakByThread();
    }
}

結果:系統假死

方法区运行时常量池溢出

前面提到方法区中有一块区域称为运行时常量池,字符串为常量,String中的intern()方法的作用是检测常量池中是否有一个等于此String的常量,如果有则返回这个字符串的对象,如果没有则将此String标注的字符串添加到常量池中,并且返回此String引用

通过-XX:PermSize和-XX:MaxPermSize限制方法区大小,从而间接限制常量池容量

// VM -XX:PermSize=10M -XX:MaxPermSize=10M

public class RuntimeConstantPool {

    public static void main(String[] args) {
    //将常量String放入list中避免full GC回收常量池
        List<String> list = new ArrayList<String>();
        int i = 0;
        while(true){
            list.add(String.valueOf(i++).intern());
        }
    }
}

结果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at demo.RuntimeConstantPool.main(RuntimeConstantPool.java:16)

 


这样创新的模式,值得你的选择!

蜗牛学院,只为成就更好的你!

还在等什么,赶快关注蜗牛学院官方微信,加入到蜗牛学院的大家庭中来吧!

20181009_153045_341.jpg

   
版权所有,转载本站文章请注明出处:蜗牛笔记, http://www.woniunote.com/article/39
上一篇: 立秋|重庆校区迎来了一批新朋友,他们为同一个梦想而来
下一篇: servlet和jsp数据交互七:JSON字符串
提示:登录后添加有效评论可享受积分哦!
最新文章
    最多阅读
      特别推荐
      回到顶部