JVM GC调优(1)-----JVM体系结构概述(部分摘自深入理解Java虚拟机)

JVM体系结构概述


首先图片展示一下虚拟机的结构图,这里我们主要介绍运行时数据区

####
首先分为:线程共享区和线程独享区。

线程独享区

  • 程序计数器( Program Counter Register)
    线程独享区是一块较小的内存空间, 它可以看作是当前线程所执行的字节码的行号指示器。 在虚拟机的概念模型里( 仅是概念模型, 各种虚拟机可能会通过一些更高效的方式去实现) , 字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令, 分支、 循环、 跳转、 异常处理、 线程恢复等基础功能都需要依赖这个计数器来完成。
    在一个确定的时刻,一个单核的处理器只能执行一条线程指令。**因此为了线程切换后可以恢复到正确的执行位置,每条线程都有一个独立的程序计数器。**可以让各个线程之间保持独立。

  • Java虚拟机栈
    虚拟机栈描述的是Java方法执行的内存模型每个方法执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈,动态连接,方法的出口等信息。每个方法从调用都执行结束,都对应一个栈帧在虚拟机栈中入栈到出栈的过程。
    经常有人把Java内存区分为堆内存( Heap) 和栈内存( Stack) ,这里所指的“栈”就是现在讲的虚拟机栈, 或者说是虚拟机栈中局部变量表部分。

    • 局部变量表存放了编译期可知的各种基本数据类型( boolean、 byte、 char、 short、 int、float、 long、 double) 、 对象引用( reference类型, 它不等同于对象本身, 可能是一个指向对象起始地址的引用指针, 也可能是指向一个代表对象的句柄或其他与此对象相关的位置) 和returnAddress类型( 指向了一条字节码指令的地址)。
      其中64位的long和double类型的数据会占用2个局部变量空间,其余的数据只占一个。局部变量表所需要的内存空间在编译器完成分配,当进入一个方式时候,这个方法需要的桢中分配的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
      这个区域有两种异常:栈溢出 StackOverflowError,虚拟机栈动态扩展没有申请到足够的内存:抛出OutOfMemoryError。
  • 本地方法栈
    本地方法栈与虚拟机栈的功能相似,只是他是为了执行本地方法native方法服务的,而虚拟机栈是为执行Java代码服务的。它是没有规定的设计模型,可以自由实现。

线程共享区

在这里插入图片描述

  • Java堆

    • 存储的内容
      用来存放实例对象,所有的对象实例以及数组都要在堆中分配。但是随着JIT编译器发展逃逸分析的成熟,栈上分配标量替代导致发生一些变化,变得不那么绝对了。

    • 内部分类
      垃圾收集的主要作用区域在这一部分,从内存回收的角度来看,Java堆还可以分为
      新生代和老年代
      新生代可以分为Eden空间,From Survivor空间,To Servivor空间等。还有从内存分配角度的分类方法,但是无论怎么分,这里存储的都是实例对象,分类只是为了更好的执行垃圾回收,方便内存管理。

    • 存储方式
      根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中, 只要逻辑上是连续的即可, 就像我们的磁盘空间一样。

    • 异常
      当对Java堆无法再申请到空间的时候,会抛出异常有OutOfMemoryError异常。

  • 方法区
    -存储内容
    用于存储虚拟机加载的类信息,常量,静态变量,即时编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap( 非堆),目的应该是与Java堆区分开来。部分虚拟机不存在永久代的概念,垃圾收集器会统一管理共享区的内存。

    • 存储方式
      这里除了存储方式不需要连续,可以制定大小,还可以选额不实现垃圾收集。

    • 垃圾回收
      这个区域垃圾回收的光顾比较少,主要是针对常量池的回收和类型的卸载。只是效果差强人意。类型卸载回收条件非常严苛,但是回收的确是必要的,在Sun公司的BUG列表中, 曾出现过的若干个严重的BUG就是由于低版本的HotSpot虚拟机对此区域未完全回收而导致内存泄漏。

    • 运行时常量池
      运行时常量池属于方法区的一部分,主要存储编译器生成的各种自变量和符号引用,还有翻译出来的直接引用,这部分内容再类加载后进入方法区的运行时常量池存放。这里对于.class文件的语法要求非常严格,但是对于运行时常量池没有任何细节要求,每个厂商可以自己实现。
      运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性, Java语言并不要求常量一定只有编译期才能产生, 也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池, 运行期间也可能将新的常量放入池中, 这种特性被开发人员利用得比较多的便是String类的intern( ) 方法。

  • 直接内存
    就是本机的内存,独立于Java虚拟机之外。这里引出是因为
    在JDK 1.4中新加入了NIO( New Input/Output) 类, 引入了一种基于通道( Channel) 与缓冲区( Buffer) 的I/O方式, 它可以使用Native函数库直接分配堆外内存, 然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。 这样能在一些场景中显著提高性能, 因为避免了在Java堆和Native堆中来回复制数据。
    使用了独特的方式,在Java堆中创建对象,操作外部内存,减少数据的调动。


内存模型就介绍到这里

猜你喜欢

转载自blog.csdn.net/weixin_42229056/article/details/82931103