java7的内存,重走JAVA之路(七):你要的JAVA内存结构

一、运行时区域

虽然作为Java程序员,不用太多关心Java内存管理这一块,是由JVM自动管理,这一点确定比C++深得我心(最近在学C++,很痛苦,不说了....),不了解JVM的内存结构和各个内存区域的工作职责,将对解决问题带来很大的麻烦,先来张图表示下,《深入理解Java虚拟机(第2版)》中的描述是下面这个样子的:

ac159e2986c1c678ade05a6caafdb34f.png

1.程序计数器

程序计数器属于内存中比较小的一块空间,属于线程私有的,其作用可以大概理解为记录当前线程所执行的字节码位置,或者通俗来说可以理解为代码执行到第几行了,为什么需要这么一小块空间做这种事情呢,因为JVM的多线程操作实际上并不是真正意义上的并行,是通过线程轮流切换并分配CPU执行时间片的方式来实现的,也就是一个CPU在某一个时间点只会执行一个线程中的指令,那么在线程切换后,不知道自己之前执行的位置,岂不是很懵逼。。所以每个线程都需要有一个独立的程序计数器存储,而且注意,此区域是唯一不会OutOfMemoryError情况的区域,因为程序计数器是由虚拟机内部维护的。

2.虚拟机栈

栈也是属于线程私有的一块区域,既然是栈,那里面肯定是要放东西的,虚拟机栈里面存着的是一种叫“栈帧”的东西,每个方法会创建一个栈帧,栈帧中存放了局部变量表、操作数栈、动态链接、方法出口等信息。常常所说的栈内存也就是局部变量表这一部分内存空间,随着方法的调用到执行的完成,也就对应着栈帧的进栈出栈

这里需要注意一点,每个栈帧的大小是在编译期就已经分配好了的,运行时并不会改变其内存的大小。

栈的内存在固定大小的情况下,如果调用深度大于JVM的范围,就会抛出StackOverflowError异常,我们来模拟一下看看

public class TestClass{

public static void main(String []agrs){

new TestClass().testStackOverFlowError();

}

public void testStackOverFlowError(){

System.out.println("run");

testStackOverFlowError();

}

}

Exception in thread "main" java.lang.StackOverflowError

atcom.example.hik.lib.TestClass.testStackOverFlowError(TestClass.java:9)

可以看到直接就抛出异常了

3.本地方法栈

和虚拟机栈其实类似,只不过一个是执行Java方法服务,而本地方法栈是执行native方法的,其他就不过多介绍了,这块区域涉及的比较少,但是与虚拟机栈一样,本地方法栈也会抛StackOverflowError和OutOfMemoryError异常

4.堆

堆算是整个JVM管理的最大的一块区域,是属于线程共享区的,也称GC堆,在JVM启动时创建。该内存区域存放了对象实例及数组(所有new的对象)。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)

从内存回收的角度上看,可分为新生代(Eden空间,From Survivor空间、To Survivor空间)及老年代(Tenured Gen),具体回收算法就不多介绍了

5.方法区

方法区也是线程共享区,用于存储【虚拟机加载的类信息(类的版本、字段、方法、接口),常量,静态变量,即时编译器编译后的代码等数据】,Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

二、实例

public class TestClass{

public static void main(String []agrs){

A a =new A();

a.test();

}

}

有这么一个类A,里面有个test方法,那么在执行这两段代码时,具体流程是怎么样的呢?

首先,创建一个A对象,运行时JVN先会去方法区找A的类型信息,如果没有找到,那说明类A之前没有被加载过,则使用Classloader将A.class字节码文件加载到内存的方法区,将A类的类型消息存放至方法区,方便下次查找

第二步,JVM会在堆中给A的实例对象分配内存空间,一般是会被分配在新生代区域,这个实例是有着指向方法区中的A类型信息的引用,也就是第一步中的信息,指向方法区的内存地址

第三步,因为当前方法肯定是执行在一个线程中的,那么这个线程创建的同时,也创建一个虚拟机栈,在调用一个方法的时候就会创建一个栈帧,上面的a是A对象的引用,并持有着堆中A对象的内存地址

JVM通过a引用找到堆中A对象的内存地址,找到A对象实例,再通过指向方法区中地址的引用找到A类型信息,拿到test()方法的字节码信息,然后再执行test()方法

总结

线程共享区:堆、方法区 线程私有区:栈、程序计数器、本地方法栈

栈中存放的东西:一个个栈帧,保存着基本数据变量以及对象的引用(指向真实对象的地址),方法中的形参,方法调完之后自动进行回收,引用对象的地址,引用完后,栈空间地址立即被回收,堆空间等待GC

堆中存放的东西:真实的对象实例,并且每个对象包含着一个Class信息,也就是栈引用-->堆对象及引用-->方法区类型信息

方法区:存放着Class类型信息及static静态变量,其中还包括着常量池。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值