Java虚拟机内存模型初步学习

Java虚拟机—Java8内存模型

1.概述


       对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要手动释放内存,不容易出现内存泄露和内存溢出问题。一旦出现内存泄露和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,排查错误将会异常艰难。

2. 运行时数据区域


       Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。Java虚拟机hotspot所管理的内存包括以下几个运行时数据区域,如图所示:

     

 2.1 程序计数器


       程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的Java字节码的行号指示器。
    每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。

 2.2 Java虚拟机栈


  与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

  局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关位置)和returnAddress类型(指向了一条字节码指令的地址)。

 2.3 本地方法栈


  本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。

  参数设置:

-Xss 设置每个线程的栈大小。JDK1.5+ 每个线程栈大小为1M,一般来说如果栈不是很深的话,1M是绝对够用的啦。
  参数含义解析:

以-X开头的参数是和实现有关的,第一个s表示stack,第二个s表示size;
  注意:

    在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

 2.4 Java堆


  对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

  Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”(Garbage Collected Heap)。

  根据Java虚拟机规范的规定,Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx和-Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

  参数设置:

-Xms 设置堆的最小空间大小;通常为操作系统可用内存的1/64大小即可。
-Xmx 设置堆的最大空间大小;通常为操作系统可用内存的1/4大小。
-Xmn 设置新生代大小,是对-XX:newSize、-XX:MaxnewSize两个参数的同时配置,这个参数是在JDK1.4版本以后出现的;通常为Xmx的1/3或1/4。新生代 = Eden + 2个Survivor空间。实际可用空间 = Eden + 1个Survivor,即90%。
-XX:NewSize 设置新生代最小空间大小;
-XX:MaxNewSize 设置新生代最大空间大小;
-XX:NewRatio 新生代与老年代的比例,如-XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3。
-XX:SurvivorRatio 新生代中 Eden 与 Survivor的比值。默认值为 8 。即Eden占新生代空间的8/10,另外两个Survivor各占1/10。

 2.5 方法区(永久代)


  方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,即存放静态文件,如Java类、方法等。

  根据Java虚拟机规范的规定,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。

   运行时常量池和静态变量都存储到了堆中,MetaSpace存储类的元数据,MetaSpace直接在本地内存中(Native memory),这样类的元数据分配只受本地内存大小的限制,OOM问题就不存在了。

   参数设置:

-XX:PermSize设置永久代最小空间大小;
-XX:MaxPermSize设置永久代最大空间大小;

2.5.1 运行时常量池
  运行时常量池(Runtime Constant Pool)是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

  既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出OutOfMemoryError异常。

2.6 直接内存(堆外内存)


  直接内存(Direct Memory),也叫堆外内存,它并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,而是Java虚拟机的堆以外的内存,直接受操作系统管理。

  在JDK1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。

   参数设置:可以通过 -XX:MaxDirectMemorySize参数来设置最大可用直接内存,如果Java虚拟机启动时未设置则默认为最大堆内存大小,即与 -Xmx相同。即假如最大堆内存为1G,则默认直接内存也为1G,那么JVM最大需要的内存大小为2G多一些。当直接内存达到最大限制时就会触发GC,如果回收失败则会引起OutOfMemoryError。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页