JAVA内存学习总结

351 次查看

从最开始学习java的时候,老师就讲过,java主要分为堆和栈两个内存区域,随着不断的学习和深入,也对java的内存有了更细致的了解。本文是个人通过以前老师所讲知识、查看各位大牛的博客,总结而来,纯属个人学习总结体会,不喜勿喷。

java内存划分

这里写图片描述
如上图所示,java内存主要分五个部分:

  1. 方法区(Method Area):方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。比如String str = “const”;const就会存放在常量池中,当String strC = new String(“const”).intern(); str==strC 返回true。intern()方法会去常量池找到const这个常量,没有则重新生成const常量放入常量池。这里字符串常量才存到方法区的常量池,其他8种基本数据类型是不存放在这里的。
  2. 堆(Heap)对于java程序员再熟悉不过了,几乎所有的对象实例和数组是在堆中分配内存,它是被所有线程贡献的一块内存区域。GC主要管理的也是这部分区域,关于GC相关的在后续再说。堆分为:新生代和老年代。新生代又分为:Eden区,From Survivor、To Survivor区,默认比例为 8:1:1。通过参数-Xmx -Xms控制,最大和最小一般保持一致,避免动态扩容以及每次GC后重新分配内存。此区域内存不够时,会发生OutOfMemoryError。具体堆内存还可以细分:这里写图片描述
    这里细分主要涉及到垃圾回收机制时,后面有GC的详细介绍;
  3. Java虚拟机栈(JVM Stack):一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。
    局部变量表中存储着方法的相关局部变量,包括各种基本数据类型,对象的引用,返回地址等。在局部变量表中,只有long和double类型会占 用2个局部变量空间(Slot,对于32位机器,一个Slot就是32个bit),其它都是1个Slot。需要注意的是,局部变量表是在编译时就已经确定 好的,方法运行所需要分配的空间在栈帧中是完全确定的,在方法的生命周期内都不会改变。
    一般来说,一个Java的引用访问涉及到3个内存区域:JVM栈,堆,方法区。
    以最简单的本地变量引用:Object obj = new Object()为例:
    Object obj表示一个本地引用,存储在JVM栈的本地变量表中,表示一个reference类型数据;
    new Object()作为实例对象数据存储在堆中;
    堆中还记录了Object类的类型信息(接口、方法、field、对象类型等)的地址,这些地址所执行的数据存储在方法区中;
    在Java虚拟机规范中,对于通过reference类型引用访问具体对象的方式并未做规定,目前主流的实现方式主要有两种:

    1.通过句柄访问(图来自于《深入理解Java虚拟机:JVM高级特效与最佳实现》):
    这里写图片描述
    通过句柄访问的实现方式中,JVM堆中会专门有一块区域用来作为句柄池,存储相关句柄所执行的实例数据地址(包括在堆中地址和在方法区中的地址)。这种实现方法由于用句柄表示地址,因此十分稳定。

    2.通过直接指针访问:(图来自于《深入理解Java虚拟机:JVM高级特效与最佳实现》)(一般学习java基础的时候,老师都是讲的这种)
    这里写图片描述
    通过直接指针访问的方式中,reference中存储的就是对象在堆中的实际地址,在堆中存储的对象信息中包含了在方法区中的相应类型数据。这种方法最大的优势是速度快,在HotSpot虚拟机中用的就是这种方式。

  4. 本地方法栈(Native Method Statck):本地方法栈在作用,运行机制,异常类型等方面都与虚拟机栈相同,唯一的区别是:虚拟机栈是执行Java方法的,而本地方法栈是用来执行native方法的,在很多虚拟机中(如Sun的JDK默认的HotSpot虚拟机),会将本地方法栈与虚拟机栈放在一起使用。
  5. 程序计数器(Program Counter Register):程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。
    每个程序计数器只用来记录一个线程的行号,所以它是线程私有(一个线程就有一个程序计数器)的。
    如果程序执行的是一个Java方法,则计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是一个本地(native,由C语言编写 完成)方法,则计数器的值为Undefined,由于程序计数器只是记录当前指令地址,所以不存在内存溢出的情况,因此,程序计数器也是所有JVM内存区 域中唯一一个没有定义OutOfMemoryError的区域。

至此,就对java中内存有了一个详细的了解,对后面的垃圾回收机制和性能优化打下基础。

参考博文:

Java内存区域概述

优秀Java程序员必须了解的GC工作原理

赞赏


微信赞赏

支付宝赞赏

java架构师历程,欢迎扫描关注