JVM内存模型(一)

本文详细介绍了JVM内存架构,包括虚拟机栈、本地方法栈、程序计数器和堆。强调了虚拟机栈中栈帧的工作原理,以及可能出现的StackOverFlowError和OutOfMemoryError异常。此外,还讨论了方法区、线程安全和垃圾回收等相关知识点。
摘要由CSDN通过智能技术生成

目录

一.JVM内存架构

二.虚拟机内存结构:

1.1 虚拟机栈

异常

1.2 本地方法栈

1.3 程序计数器

1.4 堆

异常:


一.JVM内存架构

        JVM内存模型总体预览: 

 

JVM内存结构主要分为图中的几个内存区域:

        1.虚拟机栈 

        2.本地方法栈

        3.程序计数器

        4.堆

        5.方法区

二.虚拟机内存结构:

1.1 虚拟机栈

        此区域是线程私有的,虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息(如果想对栈帧有进一步的理解可以看看我写的Class文件结构笔记)。局部变量表中存放了编译器可知的数据类型1.存放基本数据类型 2.引用数据类型。如果是基本数据类型,那么将数据的值直接放到栈帧里面,如果是引用类型指向的对象是放在堆上的。【reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向代表对象的句柄或者其它与此对象相关的位置】

        通俗来说,程序的每一个方法在执行的时候,都会在虚拟机栈创建一个栈帧来供程序运行,当该方法运行完成之后,虚拟机栈就会将这个方法的栈帧弹出,方法运行完成。

        例如:

        测试程序代码:

public class JVMStack {
    public void testStack1(){
        System.out.println("调用方法一");
        testStack2();
    }

    private void testStack2() {
        System.out.println("调用方法二");
        testStack3();
    }

    private void testStack3() {
        System.out.println("调用方法三");
    }

    @Test
    public void test(){
        testStack1();
    }
}

1.运行test函数,此时只调用了test方法 ,还未进入testStack1方法,可以看到此时的虚拟机栈中只有一个栈帧-test 

 

 

2,程序继续运行 

程序运行到第八行,这时候test方法调用了 testStack1方法,此时虚拟机栈中应该加入了testStack1栈帧。结果也说明了这一点。

 

 3.继续调用,可以看到testStack2 和testStack3都加入到了栈帧中。

 

 4.testStack3调用完成,对应的方法栈帧被弹出

 

 5.最终所有方法执行完成,栈帧都被弹出,栈被清空。

 

异常

Java 虚拟机栈会出现两种错误:StackOverFlowError 和 OutOfMemoryError。

  • StackOverFlowError: 若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 错误。

  • OutOfMemoryError: 若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 错误。

 StrackOverFlowError:

    private void testStack2() {
        System.out.println("调用方法二");
        testStack2();
    }

        自己循环调用之后就会出现该错误:

 OutOfMemoryError:

        修改虚拟机栈的内存大小或者增大被调用的栈帧的大小就可以出现该错误。

问题辨析:
        1.垃圾回收是否涉及栈内存?
        不需要。因为虚拟机栈中是由一个个栈帧组成的,在方法执行完毕后,对应的栈帧就会被弹出栈。所以无需通过垃圾回收机制去回收内存。
        2.栈内存的分配越大越好吗?
不是。因为物理内存是一定的,栈内存越大,可以支持更多的递归调用,但是可执行的线程数就会越少。
        3.方法内的局部变量是否是线程安全的?
        如果方法内局部变量没有逃离方法的作用范围,则是线程安全的,如果局部变量引用了对象,并逃离了方法的作用范围,则需要考虑线程安全问题。(逃离作用范围:局部变量可以被外部调用或者修改)

1.2 本地方法栈

        此区域是线程私有的,与虚拟机栈所发挥的作用是非常相似的,不过主要用于处理native方法。

1.3 程序计数器

        此区域是线程私有的,我们知道线程执行的是一条条指令,那么线程是如何知道执行到哪条指令了呢?就是通过程序计数器来记录的,简单点说就是,程序计数器是当前线程所执行的字节码的行号指示器。通俗来说就是用一个指示器标注程序运行到了什么地方。

1.4 堆

        通过new关键字创建的对象都会被放在堆内存。

        此区域是线程共享的,堆(heap) JVM的最大的内存空间,此区域的唯一目的就是保存对象实例。堆空间可以分为新生代和老年代,其中的新生代可以继续细分为Eden空间 From Survivor 空间和 To Survivor空间 Old Generation。

        特点:

        所有线程共享,堆内存中的对象都需要考虑线程安全问题。
        有垃圾回收机制:垃圾回收主要发生在堆中。

异常:

        OutOfMemoryError: GC Overhead Limit Exceeded : 当JVM花太多时间执行垃圾回收并且只能回收很少的堆空间时,就会发生此错误。

        java.lang.OutOfMemoryError: Java heap space :假如在创建新的对象时, 堆内存中的空间不足以存放新创建的对象, 就会引发java.lang.OutOfMemoryError: Java heap space 错误。(和本机物理内存无关,和你配置的内存大小有关!)

        可以使用jvisualvm查看堆内存的变化和GC情况。

        

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值