Java 虚拟机运行时的五大数据区域

Java虚拟机在执行java程序的过程中会它所管理的内存划分为若干个不同的数据区域。主要有程序计数器、java虚拟机栈、本地方法栈、java堆和方法区五个区域。

1、程序计数器
一块较小的内存空间,属于线程私有的内存,他的作用可以看作是当前线程作执行的字节码的行号指示器。字节码解释器工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。


因为线程之间经常存在切换,所以每个线程都需要一个独立的程序计数器。如果正在执行一个java方法,它记录的是在正在执行的虚拟机字节码指令的地址;如果正在执行Native方法,则计数器的值为空(undefined)。
在Java虚拟机规范中,这个区域是唯一一个不会抛出 OutOffMemoryError 的内存区域。

2、java虚拟机栈
它也是线程私有的,生命周期与线程相同。描述的是java方法执行的内存模型,每个方法被执行的时候都会创建一个栈帧(Stack frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法从被调用到执行完成,对应着一个栈帧在虚拟机中的从入栈到出栈的过程。栈帧是方法执行期的基础数据结构。


在Java虚拟机规范中,这个内存区域可能会抛出 StackOverflowError 和 OutOffMemoryError 两种异常。当线程请求的栈深度大于虚拟机所允许的深度抛出StackOverflowError异常。如果虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存会抛出 OutOffMemoryError 异常。

虚拟机栈StackOverflowError案例:
Eclipse设置虚拟机参数:-Xss256k 虚拟机栈内存容量。
程序代码:

public class MainTest {

    private static int stackLen = 1;
    
    public static void main(String[] args) {
        doStackSOF();
    }
    
    //虚拟机参数  -Xss256k
    private static  void doStackSOF() {
        try {
            stackLeak();    
        }catch(Throwable e){
            System.out.println("stackLen:" + stackLen);
            throw e;
        }    
    }
    
    public static void stackLeak() {
        stackLen ++;
        stackLeak();
    }
    
}


输出:
stackLen:1843
Exception in thread "main"
java.lang.StackOverflowError
    at cn.psq.javavm.MainTest.stackLeak(MainTest.java:26)
    at cn.psq.javavm.MainTest.stackLeak(MainTest.java:27)
......

操作系统的剩余内=总内存-最大堆内存容量(Xmx)-最大方法区容量(MaxPermSize)-程序计数器消耗的内存(很小)-虚拟机本身消耗的内存


剩余内存被虚拟机栈和本地方法瓜分;所以每个线程分配到的栈容量越大,可以建立的线程数量越小,建立线程就越容易把剩下的内存耗尽。如果在建立过多线程导致内存溢出,再不能减少线程数量和更换64位虚拟机时,就只能通过减少最大堆和减少栈容量来换取更多线程。

3、本地方法栈
与虚拟机栈发挥的作用非常相似,为虚拟机执行的本地方法服务。在Java虚拟机规范中,这个内存区域也可能会抛出 StackOverflowError 和 OutOffMemoryError 两种异常。


4、java堆
对于大多数应用,java堆是java虚拟机所管理的内存中最大的一块区域。是被所有线程共享的一片内存区域,虚拟机启动时就创建好了,唯一的目的就是存放对象实例。它也是垃圾搜集器管理的主要区域,有些时候被成为“GC堆”。


在Java虚拟机规范中,java堆可以物理上不连续,但是逻辑上要求连续。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,会抛出 OutOffMemoryError 异常。堆内存 OutOffMemoryError 是实际应用中最常见的内存溢出异常。
堆OutOffMemoryError案例:
Eclipse设置虚拟机参数:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
参数设置说明:
(1)-Xms堆最小值和-Xmx堆最大值都设置成20M是为了避免堆自动扩展。
(2)-XX:+HeapDumpOnOutOfMemoryError 在虚拟机出现内存溢出异常时,dump出当前内存堆转储快照,方便事后分析。
程序代码:

public class MainTest {

    public static void main(String[] args) {
        doOOMObject();
    }


    private static void doOOMObject() {
        List<MainTest> list = new ArrayList<MainTest>();
        while(true) {
            list.add(new MainTest());
        }
    }

}

输出:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid27142.hprof ...
Heap dump file created [29651203 bytes in 0.071 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.base/java.util.Arrays.copyOf(Arrays.java:3720)
    at java.base/java.util.Arrays.copyOf(Arrays.java:3689)
    at java.base/java.util.ArrayList.grow(ArrayList.java:238)
    at java.base/java.util.ArrayList.grow(ArrayList.java:243)
    at java.base/java.util.ArrayList.add(ArrayList.java:486)
    at java.base/java.util.ArrayList.add(ArrayList.java:499)
    at cn.psq.javavm.MainTest.doOOMObject(MainTest.java:16)
    at cn.psq.javavm.MainTest.main(MainTest.java:9)

解决这个区域的异常,一般手段是首先通过内存映像分析工具(如:Eclipse Memory Analyzer)对dunp出来的堆转储快照进行分析,重点确认内存中的对象是否是有必要,也就是要确认的到底是内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。


如果是内存泄漏,需要通过工具进一步查看泄漏对象到GC Roots的引用链,这样就可以比较准确的定位出内存泄漏代码的位置。如果不存在泄漏,内存中的对象确实必须活着,那就应该检查虚拟机的堆参数(-Xms和-Xmx),与物理内存对比看看是否可以调大。从代码层面检查某些对象生命周期和持有状态是否过长,尽量降低程序运行时期的内存西消耗。

5、方法区
它也是被所有线程共享的一片内存区域,用于存储被虚拟机加载的类信息、常量、静态变量、即时编译器便以后的代码等数据。


在Java虚拟机规范中,这个区域不需要连续的内存,且可选择固定或者可扩展大小,还可以选择不实现立即搜集。当方法区无法满足内存分配需求时,会抛出 OutOffMemoryError 异常。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值