转载

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

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对象在内存中如何分配的,请持续关注哈。

原文  http://rdc.hundsun.com/portal/article/950.html
正文到此结束
Loading...