Java虚拟机 -- JVM内存区域模块

JVM内存模型  --  JDK8

线程私有

  • 程序计数器
    • 当前线程所执行的字节码行号指示器
    • 通过改变计数器的值来选取下一条需要执行的字节码指示器(比如:循环,跳转,异常处理都需要依赖该计数器来完成)
    • 和线程是一对一的关系即“线程私有”
    • 不会发生内存泄露问题,程序计数器是逻辑计数器而非物理计数器
  • 虚拟机栈
    • 生命周期与线程相同
    • 每个方法在执行时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
      • 局部变量表:存放编译期可知的各种基本数据类型,对象引用
      • 操作数栈:入栈,出栈,复制,交换的一些信息
    • 深递归会导致虚拟机创建栈帧过多而导致StackOverflowError
    • 当虚拟机可以动态扩展时,如果无法申请足够多的内存就会抛出OOM异常
  • 本地方法栈
    • 与虚拟机栈相似,主要标记为native的方法

线程共享

  • Java堆(GC堆)
    • 对象实例与数组的分配区域
    • GC所管理的区域
    • Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可。由于当前主流的虚拟机都是可扩展的(通过 -Xmx和 -Xms控制),如果堆中没有内存完成实例分配,并且堆也无法再扩展时,就会抛出OOM异常。
  • 方法区
    • 用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在JDK8以前的HotSpot虚拟机中,方法区也被称为“永久代”(JDK8已经被元空间取代)
    • 永久代并不意味着数据进入方法区就永久存在,此区域的内存回收主要是针对常量池的回收以及对类型的卸载。JVM规范规定:当方法区无法满足内存分配需求时,将抛出OOM异常。
      • 元空间与永久代的区别
        • 元空间使用的是本地内存,而永久代使用JVM内存
        • 字符串常量池存在永久代中,容易出现内存溢出与性能问题
        • 永久代会为GC带来不必要的复杂性
  • 运行常量池(方法区的一部分)

字面量:字符串(JDK1.7后移动到堆中) 、final常量 、基本数据类型的值

String str = "str";
int i = 1;
这里的"str"与1就是字面量

符号引用 :符号引用就是某个变量,在编译的时候,无法确定其内存地址

String str = "Hello World";
System.err.println(str);
这里的str就相当于符号引用

额为扩展:在上一篇文章中类加载的解析阶段,将符号引用转化为直接引用,就可以理解成将一个漂浮在字符串常量池中的符号引用用一根线将它固定起来,通过这根线可以直接定位到该目标,但是在这个阶段给目标上并没有实际意义上的值,而单纯是一个字面量,只有在初始化阶段才会真正去初始化类变量等信息(个人理解)。

 

JVM三大性能调优参数

  • - Xss:规定了每个线程虚拟机栈的大小
    • 一般情况下256k就足够,此配置将影响此进程中并发线程数的大小
  • - Xms:堆的初始大小
  • - Xmx:堆扩容后所能达到的最大值
    • 通常情况下将 - Xms与 - Xmx设置为一样,因为当heap不够用时发生扩容,会发生内存抖动,影响程序运行时的稳定性
      • 内存抖动是指内存频繁地分配和回收,而频繁的gc会导致卡顿,严重时和内存泄漏一样会导致OOM。

 

Java内存模型中的堆与栈

内存分配策略:

  • 静态存储:编译时确定每个数据目标在在运行时的存储空间需求
  • 栈式存储:数据区需求在编译时未知,运行时模块入口前确定
  • 堆式存储:编译时或运行时模块入口都无法确定,动态分配

堆与栈的联系:栈中保存的是堆内存(引用对象,数组)的首地址。

 

Java内存模型中堆与栈的区别

  • 管理方式:栈是自动释放,堆需要GC回收
  • 分配方式:栈支持静态和动态分配,而堆仅支持动态分配
  • 效率方面:由于栈的操作只有入栈,出栈等基本操作,而堆由于是动态分配所以会使用更加复杂的存储结构,在带来灵活性的同时,效率降低很多
  • 碎片相关:栈产产色的碎片远小于堆
  • 空间大小:栈比堆小

 

不同版本之间的intern()方法的区别

  • 好文传送门 -- String方法中的intern()方法详解

 

你可能感兴趣的:(Java虚拟机)