文章目录
Java 运行时数据区域
Java的内存布局介绍
Java的内存布局分为5大块,分别是
堆区
、方法区
、虚拟机栈
、本地方法栈
、程序计数器
。按照线程是否共享: 堆区和方法区是线程共享的,创建进程就创建该区域
虚拟机栈、本地方法栈、程序计数器是线程私有的,创建线程时该区域才会产生。
关注维度:区域作用、生命周期、是否是线程共享、异常(OOM)
程序计数器
- 作用: 程序计数器是内存中一块较小的内存空间,主要是用来表示当前线程所执行的字节码的行号指示器。
字节码解释器工作时通过计数器的值来选取下一个需要执行的字节码指令,分支、循环、跳转、异常等功能都是需要依赖这个计数器来完成。线程切换恢复到正确的执行位置都需要依靠程序计数器 - 线程私有: 每个线程都有单独的一个程序计数器,线程之间的计数器是互不影响,独立存储的
- 生命周期: 随着线程的创建而创建,随着线程的结束而消亡
- 是否有OOM问题: 程序计数器是唯一一个不会抛出OutOfMemoryError的内存区域
虚拟机栈
虚拟机栈是描述Java的方法执行的内存区域,每个方法被执行时都会同时创建一个栈帧,用于存放局部变量表,操作栈,动态链接、方法出口信息等,每一个方法被调用直至程序执行完成的过程,就是对应一个栈帧在虚拟机战马的出栈的过程。
特点:
● 内部结构是栈帧,每个方法在执行的时候都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法返回地址等信息
● 某方法在调用另一个方法是通过动态链接在常量池中查询方法的引用,进而完成方法调用
● 某方法在调用另一个方法的过程,即是一个栈帧在虚拟机中的入栈到出栈的过程
● 虚拟机中的方法入栈的顺序和方法的调用顺序是一致的
局部变量表
主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
其中64 位长度的long 和double 类型的数据会占用2 个局部变量空间(Slot),其余的数据类型只占用1 个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
详细的虚拟机栈解读请查看下图,一目了然:
Java 虚拟机栈会出现两种异常:StackOverFlowError
和 OutOfMemoryError
。
StackOverFlowError
:
若Java虚拟机栈的内存大小不允许动态扩展,当线程请求栈的深度超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。OutOfMemoryError
:
若Java虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出OutOfMemoryError异常。
Java 虚拟机栈也是线程私有的,每个线程都有各自的Java虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。
本地方法栈
和虚拟机栈所发挥的作用非常相似。
区别:虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。
本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError
和 OutOfMemoryError
两种异常。
堆区域
Java 虚拟机所管理的内存中最大的一块, Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
根据Java 虚拟机规范的规定,Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms 控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 异常。
特点:
● 存放 Java 对象和数组
● 虚拟机中存储空间比较大的区域
● 可能出现 OOM 异常区域
● 该区域是 GC 的主要区域,堆区由年轻代和老年代组成,年轻代又分为 Eden 区、S0区(from survivor)、S1 区(to survivor);新生代对应 Minor GC(Young GC),老年代对应 Full GC(Old GC)。
Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap).从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:
在细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
(1) 堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的
(2) Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
(3) TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
(4) 所有新创建的Object 都将会存储在新生代Yong Generation中。如果Young Generation的数据在一次或多次GC后存活下来,那么将被转移到OldGeneration。新的Object总是创建在Eden Space。
方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于类的信息(名称、修饰符等)、类中的静态变量、类中定义为final类型的常量、类中的Field信息、类中的方法信息,当开发人员在程序中通过Class对象中的getName、isInterface等方法来获取信息时,这些数据都来源于方法区域,同时方法区域也是全局共享的,在一定的条件下它也会被GC,当方法区域需要使用的内存超过其允许的大小时,会抛出OutOfMemory的错误信息。
运行时常量池(Runtime Constant Pool):存放的为类中的固定的常量信息、方法和Field的引用信息等,其空间从方法区域中分配。
在JDK 1.8之前的版本,方法区在HotSpot VM中的实现就是 PermGen (永久代),在其他JVM上不存在永久代。在JDK 1.8的时候,HotSpot VM将方法区的实现替换成了 Meatspace
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/95489.html