在一篇《初步了解JVM第一篇》中,咱们已经了解了:html
那在这一篇中咱们来聊一聊方法区、栈和堆。java
5.方法区架构
在JVM的架构图中,Java栈、本地方法栈、程序计数器都是线程私有的。而方法区跟堆同样,是一个内存共享的区域,他的主要做用就是存储每个类的结构信息,例如运行时常量池(Runtime Constant Pool)、字段和方法数据、构造函数和普通方法的字节码内容。编程语言
再简单来讲方法区就是一个类的模板,在上一篇咱们已经说了ClassLoader将class文件加载完成以后会把类的字节码内容放到方法区中,就像把Car.class文件经过类加载器加载后,会把car这个类的结构信息存放在方法区中。当你要实例化的时候再经过这个模板去new出你想要的car1,car2,car2,而你建立出来这些类对象是存放在堆(heap)中的。函数
图一是方法区中存放的内容spa
图一操作系统
方法区的实现:线程
方法区只是一个定义、一个规范。在不一样的虚拟机里头实现是不同的。这里咱们主要介绍的是JDK7和JDK8的实现方式指针
永久代
在JDK7中方法区的实现方式叫永久代,可是它存储的部分数据是存放在JVM的一块地方的,这会形成一个问题:
当类加载太多了,可能会致使内存栈溢出:java.lang.OutOfMemoryError: PermGen,这样一来就不够灵活,为了提升灵活性(这只是其中一个缘由)就有了元空间
元空间:
在JDK8中,JVM的开发者就把永久代移除了,移至元空间中。其实做用是差很少的,只是元空间再也不使用JVM的内存了,而是直接使用本地堆内存(native heap),说白了就是直接使用系统的内存,这样就几乎不会发生内存溢出的状况,提升了灵活性。
因此为何在网上会看到关于方法区不少不一样的说法就是由于方法区的实现方式在不一样的JVM中是不一样,最典型的就是永久代和元空间。
以上咱们总结出:
以上就是方法区的介绍,在介绍堆的时候还会说起。
6.Stack栈
栈是一个线程私有的,主要用来管理Java程序的运行。是在线程建立的时候建立的,它的生命周期跟随这线程的结束而结束,当线程结束了栈的内存也就释放了,对于栈来讲,不会存在垃圾回收问题,由于只要线程一结束该栈就结束了。
栈中主要存储的内容:
栈就相似一个子弹夹,它的特色就是“后进先出,先进后出”,在Java中须要实现不少方法,而这些方法就是一个一个被压进栈中的,而后再依次调用。在日常中,咱们所说的Java中的方法在栈其实有一个专有名词叫栈帧,栈帧主要存放三类数据:
栈运行原理:
Java中的方法存放在栈中,可是这些方法究竟是怎么执行的呢?
接下来咱们就用一个例子来讲明一下:
package testJVM;
public class TestStack { public static void method_one(){ System.out.println("This is the method_one"); } public static void method_two(){ System.out.println("This is the method_two"); } public static void main(String[] args) { System.out.println("This is the main method"); //调用方法一 method_one(); //调用方法二 method_two(); //输出程序结束 System.out.println("The program is finish"); } }
以上的运行结果为:
这样的输出结果,相信已经在你们的预料之中,可是这些方法在栈中是怎么运行的呢?废话不说,上图二
图二
咱们都知道main方法是一切程序的入口,因此程序一执行碰到的是main方法,main方法就第一个入栈了,因此他们的执行过程是这样的:
因此就造成了图二,当他运行的时候:
这样就造成了相似一条执行链,依次执行了main方法。
总结栈运行原理:
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,当一个方法A被调用时就产生了一个栈帧 F1,并被压入到栈中, A方法又调用了 B方法,因而产生栈帧 F2 也被压入栈, B方法又调用了 C方法,因而产生栈帧 F3 也被压入栈, 执行完毕后,先弹出F3栈帧,再弹出F2栈帧,再弹出F1栈帧…… 遵循“先进后出”和“后进先出”原则。每一个方法执行的同时都会建立一个栈帧,用于存储局部变量表、操做数栈、动态连接、方法出口等信息,每个方法从调用直至执行完毕的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。栈的大小和具体JVM的实现有关,一般在256K~756K之间,与等于1Mb左右。
栈溢出
讲完了栈的内容,如今咱们来看一个你们在实际开发中会碰到的一个错误,请看下列代码:
package testJVM;
public class TestStack { public static void method_one(){ //递归调用 method_one(); } public static void main(String[] args) { method_one(); } }
上述是一个递归调用的例子,如今来执行一下,看看会出现一个什么结果:
相信你们多多少少都会遇到过上述的错误,栈溢出。缘由以下:
因为咱们的方法method_one一直在递归调用本身,并且并无中止的条件。因此method_one这个方法就会被一直压入栈中,JVM中的内存又是有限的,上述咱们也提到了Java中的栈是随着线程的生命周期结束而结束的,不会存在垃圾回收机制,内存得不到释放而方法又不断的进栈,最终内存不够形成栈溢出的现象。图三
图三
以上就是本人对栈的理解,最后来到了重头戏堆(heap),那就下篇再进行介绍吧,哈哈哈。
在下篇将会介绍: