《深刻理解java虚拟机》学习笔记系列——java内存区域划分

Java 运行时数据区域的学习,是学习 jvm 以及 GC 机制的基础,也是深刻理解 java 对象建立及运行过程的前提。
废话很少说,直接进入正题:java

一张图总结

图片描述

详细介绍

程序计数器

概念

程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,能够理解为是当前线程的行号指示器。字节码解释器在工做时,会经过改变这个计数器的值来取下一条语句指令。算法

做用

因为 Java 虚拟机的多线程是经过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个肯定的时刻,一个处理器(严谨点,多核处理器时指其中一个内核),只会执行一条线程的指令。segmentfault

所以,为了实现线程切换后回复到正确的执行位置,各个线程私有的程序计数器时必不可少的。数组

注: 若是程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;若是正在执行的是一个本地(native,由C语言编写完成)方法,则计数器的值为Undefined多线程

另: 因为程序计数器只是记录当前指令地址,因此不存在内存溢出的状况,所以,程序计数器也是全部JVM内存区域中惟一一个没有定义OutOfMemoryError的区域。jvm

Java 虚拟机栈

概念

一个线程的每一个方法在执行的同时,都会建立一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操做站、动态连接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。性能

做用

局部变量表中存储着方法的相关局部变量,包括各类基本数据类型,对象的引用,返回地址等。在局部变量表中,只有long和double类型会占用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。学习

须要注意的是,局部变量表是在编译时就已经肯定好的,方法运行所须要分配的空间在栈帧中是彻底肯定的,在方法的生命周期内都不会改变优化

注: 每一个线程对应着一个虚拟机栈,所以虚拟机栈也是线程私有的。编码

另: 虚拟机栈中定义了两种异常,若是线程调用的栈深度大于虚拟机容许的最大深度,则抛出StatckOverFlowError(栈溢出);不过多数Java虚拟机都容许动态扩展虚拟机栈的大小(有少部分是固定长度的),因此线程能够一直申请栈,直到内存不足,此时,会抛出OutOfMemoryError(内存溢出)。

本地方法栈

本地方法栈在做用,运行机制,异常类型等方面都与虚拟机栈相同,惟一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在不少虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一块儿使用。

本地方法栈也是线程私有的。

Java 堆

概念

JVM用来存储对象实例以及数组值的区域,能够认为Java中全部经过new建立的对象的内存都在此分配,Heap中的对象的内存须要等待GC进行回收(不过现代技术里,也不是这么绝对的,也有栈上直接分配的)。

补充介绍

(1)堆是JVM中全部线程共享的,所以在其上进行对象内存的分配均须要进行加锁,这也致使了new对象的开销是比较大的

(2)Sun Hotspot JVM为了提高对象内存分配的效率,对于所建立的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的状况计算而得,在TLAB上分配对象时不须要加锁,所以JVM在给线程的对象分配内存时会尽可能的在TLAB上分配,在这种状况下JVM中分配对象内存的性能和C基本是同样高效的,但若是对象过大的话则仍然是直接使用堆空间分配

(3)TLAB仅做用于新生代的Eden Space,所以在编写Java程序时,一般多个小的对象比大的对象分配起来更加高效。

关于堆区的内容还有不少,在后续的垃圾回收算法中还有更多的介绍。

方法区

概念

方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时须要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。

补充介绍

方法区在物理上也不须要是连续的,能够选择固定大小或可扩展大小,而且方法区比堆还多了一个限制:能够选择是否执行垃圾收集。通常的,方法区上执行的垃圾收集是不多的,这也是方法区被称为永久代的缘由之一(HotSpot),但这也不表明着在方法区上彻底没有垃圾收集,其上的垃圾收集主要是针对常量池的内存回收和对已加载类的卸载。

在方法区上定义了OutOfMemoryError:PermGen space异常,在内存不足时抛出。

运行时常量池

概念

方法区的一部分,用于存储编译期就生成的字面常量、符号引用、翻译出来的直接引用(符号引用就是编码是用字符串表示某个变量、接口的位置,直接引用就是根据符号引用翻译出来的地址,将在类连接阶段完成翻译);运行时常量池除了存储编译期常量外,也能够存储在运行时间产生的常量(好比String类的intern()方法,做用是String维护了一个常量池,若是调用的字符“abc”已经在常量池中,则返回池中的字符串地址,不然,新建一个常量加入池中,并返回地址)

总结

本系列文章将从4个方面介绍Java GC机制,1,内存是如何分配的;2,如何保证内存不被错误回收(即:哪些内存须要回收);3,在什么状况下执行GC以及执行GC的方式;4,如何监控和优化GC机制。

了解 Java 内存区域划分,是学习 GC 概念的前提。解铃还需系铃人,相信完整学习完了底层的垃圾回收机制,读者也将对 java 世界中对象建立与运行的原理有个清晰的认识吧:-)

参考文档

联系做者

zhihu.com
segmentfault.com
oschina.net

相关文章
相关标签/搜索