JVM笔记1:Java内存模型及内存溢出

 2019-12-10 11:18  阅读(682)
文章分类:Java Core

2019121000192\_1.png

灰色:所有线程间共享

白色:线程间隔离

程序计数器:当前线程所执行的字节码的行号指示器,字节码解释器通过改变该计数器的值来选取下一条需要执行的字节码指令。

1,一块很小的内存空间

2,每条线程都需要一个独立的计数器(java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的计数器)

3,该内存区域不存在OutOfMemoryError

Java虚拟机栈:为虚拟机执行java方法服务。每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在java虚拟机栈中从出栈到入栈的过程。

(通常习惯于将java内存区划分为堆内存和栈内存,这种划分方式很粗糙,其中的栈内存对应的就是虚拟机栈中的局部变量表部分。局部变量表在编译期间完成分配,当进入一个方法时,需要在帧上分配多大的局部变量空间是完全确定的,方法运行期间不会改变局部变量表的大小)

1,线程私有,生命周期和线程相同

2,如果线程申请的栈深度大于虚拟机所允许深度,抛出StackOverflowError异常

3,如果虚拟机栈可以动态扩展,当扩展无法申请到足够的内存时会抛出OutOfMemoryError异常

设置方式:-Xss,每个线程栈空间大小

java基本数据类型直接保存在虚拟机栈中,如果虚拟机栈没有足够的空间存入基本数据类型,则会报栈内存溢出

//-Xss100k
    public class Test {
        private int num = 1;

        public void recursion(){
            num++;
            recursion();
        }

        public static void main(String[] args) throws Throwable{
            Test test = new Test();
            try{
                test.recursion();
            }catch(Throwable e){
                System.out.println(test.num);
                throw e;
            }
        }
    }

2403
    Exception in thread "main" java.lang.StackOverflowError

本地方法栈:和虚拟机栈的作用类似,不过是为虚拟机执行native方法服务,虚拟机规范对这块没有强制规定,具体虚拟机可以自由实现(sun Hotspot将本地方法栈和虚拟机栈合二为一),本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。

设置方式:–Xoss,Hotspot虚拟机本地方法栈和虚拟机栈合二为一,故这个参数不生效

Java堆:虚拟机规范要求所有的对象实例及数组都需要在堆上分配(垃圾收集器管理的主要区域),随着技术的发展,现在也不是很绝对

1,所有线程共享

2,虚拟机启动时创建

3,可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可

4,如果在堆中没有完成实例分配,并且堆也无法再扩展时(通过-Xms和-Xmx控制),会抛出OutOfMemoryError异常

设置方式:-Xms,-Xmx

堆最小值为10m,并且不进行自动扩展,当没有足够的内存分配给新创建的对象时,便会堆内存溢出

//-Xms10m -Xmx10m
    public class Test {
        public static void main(String[] args) {
            List<Object> list = new ArrayList<Object>();
            while(true){
                list.add(new Object());
            }
        }
    }

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

方法区:用于存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(一般被称为:永久代,不准确,Hotspot虚拟机使用永久代实现该区域,别的虚拟机并不存在永久代的概念)。

1,所有线程共享

2,可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可

3,该区域的垃圾收集比较少见,主要针对常量池的回收和类型的卸载。回收条件苛刻但十分必要。

4,方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常

设置方式:-XX:PermSize,-XX:MaxPermSize

运行时常量池:方法区的一部分。Class文件除了有类的字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

1,并非Class文件中的常量池内容才能进入方法区运行常量池,运行期间也能将新的常量放入池中

2,作为方法区的一部分,受到方法区内存的限制,常量池无法再申请到内存时,会抛出OutOfMemoryError异常

运行时常量池为方法区的一部分,故可以通过方法区大小的设置间接的控制运行时常量池的大小

//-XX:PermSize=2m -XX:MaxPermSize=2m
    public class Test {
        public static void main(String[] args){
            int num = 1;
            List<String> list = new ArrayList<String>();
            while(true){
                //String类单独维护了一个初始为空的字符串池。
                //当调用字符串的intern方法时,如果池中存在一个相同的字符串,则返回池中的该字符串,
                //否则将字符串添加至字符串池中,并返回该字符串的引用
                list.add(String.valueOf(num++).intern());
                System.out.println(list.size());
            }
        }
    }

35899
    Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

直接内存:并不是虚拟机运行时数据区的一部分也不是java虚拟机规范中定义的内存区域

2019121000192\_2.png

经常会被使用,如:NIO(New Input/Output)类中,引入了基于通道与缓冲区的I/O方式,可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作

直接内存分配不会受到堆内存的限制,但是会受到总内存大小和处理器寻址空间(如32位系统最大支持3g内存,非确切值,只是举例,实际内存为4g,-Xmx为3g,则直接内存将在剩下的1g中分配,实际上操作系统不知道如何处理3g-4g的地址部分,就会产生异常)的限制,在实际配置内存参数时(-Xmx),如果忽略直接内存,将会使各个内存区域的总和大于物理内存限制(操作系统级和物理上的限制),从而导致OutOfMemoryError异常

设置方式:-XX:MaxDirectMemorySize,如果不指定,则默认与java堆的最大值(-Xmx指定)一样

ByteBuffer.allocateDirect先判断是否有足够的内存用来分配,然后向操作系统申请内存分配

//-XX:MaxDirectMemorySize=1m
    public class Test {
        public static void main(String[] args){
            Buffer b =  ByteBuffer.allocateDirect(1024*1024*2);
        }
    }

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

public static ByteBuffer allocateDirect(int capacity) {
            return new DirectByteBuffer(capacity);
    }

DirectByteBuffer(int cap) {         
        super(-1, 0, cap, cap, false);
        Bits.reserveMemory(cap);//是否有足够内存
        int ps = Bits.pageSize();
        long base = 0;
        try {
            base = unsafe.allocateMemory(cap + ps); //真正的申请内存分配
        } catch (OutOfMemoryError x) {
            Bits.unreserveMemory(cap);
            throw x;
        }
        unsafe.setMemory(base, cap + ps, (byte) 0);
        if (base % ps != 0) {
            // Round up to page boundary
            address = base + ps - (base & (ps - 1));
        } else {
            address = base;
        }
        cleaner = Cleaner.create(this, new Deallocator(base, cap));
    }

特别说明:

代码示例均在jdk1.6.0_24下运行调试

不同JDK的运行结果并不相同

Java虚拟机栈的测试代码,在使用jdk1.5.0_22时,无论如何调整-Xss的值,最终显示的num值均为固定值,而使用jdk1.6.0_24时,num值将会随着-Xss值的变大而变大

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> JVM笔记1:Java内存模型及内存溢出

相关推荐