JAVA漫谈——内存不应是瓶颈!

栏目: Java · 发布时间: 5年前

内容简介:Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java语言的运行环境;因此所有的java的内存都是通过JVM来管理的(本文都是基于HotSpot虚拟机),要了解java的内存,就需要了解jvm的内存结构。JVM内存结构如图:

Jvm运行时数据区

Java虚拟机(Java Virtual Machine 简称JVM)是运行所有 Java 程序的抽象计算机,是Java语言的运行环境;因此所有的java的内存都是通过JVM来管理的(本文都是基于HotSpot虚拟机),要了解java的内存,就需要了解jvm的内存结构。

JVM内存结构如图:

JAVA漫谈——内存不应是瓶颈!

  • 方法区:方法区存储虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据;是jvm规范中的一部分,并不是实际的实现,在实际实现上并不相同(HotSpot在1.7版本以前和1.7版本,1.7后都有变化)。当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。
  • Java堆:Java堆用于存放对象实例和数组的内容。是垃圾收集器管理的主要区域。可细分为:新生代和老年代;新生代又可分为Eden,from Survivor,to Survivor。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。
  • Java虚拟机栈:每一条java虚拟机线程都有自己私有的java虚拟机栈,这个栈和线程同时创建,用于存储栈帧。Java虚拟机栈是Java方法执行的内存模型,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。栈帧(Stack Frame)存储局部变量表,操作数栈,动态链接,方法出口等信息,随着方法的调用而创建,随着方法的结束而销毁。在Java虚拟机规范中,对这个区域规定了两种异常状况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可以动态扩展(当前大部分的Java虚拟机都可动态扩展,只不过Java虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常。
  • 本地方法栈:本地方法栈和虚拟机栈非常相似,不同的是虚拟机栈服务的是Java方法,而本地方法栈服务的是Native方法。HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。与虚拟机栈一样,本地方法栈区域也会抛出StackOverflowError和OutOfMemoryError异常。
  • 程序计数器:java虚拟机可以支持多个线程同时运行,每个java虚拟机线程都有自己的程序计数器(PC寄存器),在任一时刻,一个java虚拟机的线程,只会执行一个方法的代码。那么程序计数器记录当前线程所执行的Java字节码的地址。当执行的是Native方法时,程序计数器为空。程序计数器是JVM规范中唯一一个没有规定会导致OOM(OutOfMemory)的区域。
  • 除开上述jvm的内存区域,其实还有直接内存区域,直接内存区并不是 JVM 管理的内存区域的一部分,而是其之外的。该区域也会在 Java 开发中使用到,并且存在导致内存溢出的隐患,如果使用这块内存,一定要注意内存的回收。如果你对 NIO 有所了解,可能会知道 NIO 是可以使用 Native Methods 来使用直接内存区的。ex: DirectByteBuffer

从持久代到metaspace

方法区在物理上存在于堆,而且在堆的持久代里(java8之前,用持久代实现方法区,在java8之后—包括java8,用metaspace来实现方法区),但是在逻辑上,方法区和堆是独立的。方法区是jvm的规范,而持久代是方法区的具体实现,并且只有hotspot才有持久代。虽然java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。从jdk1.7已经开始准备“去永久代”的规划,jdk1.7的HotSpot中,已经把原本放在方法区中的静态变量、字符串常量池等移到堆内存中。

Metaspace的组成:

Klass Metaspace:Klass Metaspace就是用来存klass的,klass是我们熟知的class文件在jvm里的运行时数据结构,不过有点要提的是我们看到的类似A.class其实是存在heap里的,是java.lang.Class的一个对象实例。这块内存是紧接着Heap的,和我们之前的perm一样,这块内存大小可通过-XX:CompressedClassSpaceSize参数来控制,这个参数前面提到了默认是1G,但是这块内存也可以没有,假如没有开启压缩指针就不会有这块内存,这种情况下klass都会存在NoKlass Metaspace里,另外如果我们把-Xmx设置大于32G的话,其实也是没有这块内存的,因为会这么大内存会关闭压缩指针开关。还有就是这块内存最多只会存在一块。

NoKlass Metaspace:NoKlass Metaspace专门来存klass相关的其他的内容,比如method,constantPool等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。这块内存是必须的,虽然叫做NoKlass Metaspace,但是也其实可以存klass的内容。

Klass Metaspace和NoKlass Mestaspace都是所有classloader共享的,所以类加载器们要分配内存,但是每个类加载器都有一个SpaceManager,来管理属于这个类加载的内存小块。如果Klass Metaspace用完了,那就会OOM了,不过一般情况下不会,NoKlass Mestaspace是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。

持久代和metaspace(元数据空间)的区别:

  • 持久代物理上处于堆的连续内存上,但是metaspace不再占用jvm的内存,而是直接占用本地内存
  • 持久代在应用启动时,就已经确定了,很难进行调优;metaspace 直接占用本地内存,理论上,只要本地内存足够大,就可以无限占用,但是,metaspace也可以设置最大占用内存。
  • 每个类加载器在metaspace中都有专门的存储空间,如果GC发现某个类加载器不再存活了,会把相关的空间整个回收掉,还给操作系统。

为什么用metaspace替换持久代:

  • 字符串常量存在永久代中,容易出现性能问题和内存溢出。
  • 类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
  • 永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
  • 使得原来受限于持久代的一些改进未来有可能实现。比如Oracle 可能会将HotSpot 与 JRockit 合二为一。

之后我们会谈谈java最重要的特性JVM内存调优(java垃圾回收机制),以及java对象在内存中如何分配的,请持续关注哈。


以上所述就是小编给大家介绍的《JAVA漫谈——内存不应是瓶颈!》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Introduction to Computation and Programming Using Python

Introduction to Computation and Programming Using Python

John V. Guttag / The MIT Press / 2013-7 / USD 25.00

This book introduces students with little or no prior programming experience to the art of computational problem solving using Python and various Python libraries, including PyLab. It provides student......一起来看看 《Introduction to Computation and Programming Using Python》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

随机密码生成器
随机密码生成器

多种字符组合密码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具