jvm内存区域划分介绍

7 篇文章 0 订阅
2 篇文章 0 订阅

Java内存划分主要分为以下几块:

程序计数器

线程私有,可以看做是当前线程所执行的字节码的行号,用于下一次线程切换的时候虚拟机定位到上一次执行的位置。

虚拟机栈
  • 线程私有,生命周期与线程相同。描述的是方法执行的内存模型

  • 进入方法时对应入栈,方法结束的时候对应出栈。

  • 该区域存储着局部变量,操作数,方法出口等信息。

方法区

线程共享。主要用来存储类的元信息。 在1.7和1.8之后的实现逻辑有所不同。

由于该区域大小一般较小,一般不会对该区域进行垃圾回收。所以在1.7之前的版本,有可能会因为字符串常量池过大导致该区域内存溢出(Permgen space out of memory error)。

永久代(PermGen)和方法区之间是什么关系?主要如下几点:

  1. 方法区是虚拟机规范的一部分,而永久代是hotspot虚拟机用来实现方法区提出的,其他虚拟机未必有永久代。

  2. 在1.7之前,永久代就是方法区,永久代的内存地址和堆地址是连续的。

  3. 在1.8之后,hotspot移除了永久代,将原先永久代中的静态变量和常量池并入堆中,将类的元信息放入元空间(metaspace)中。移除永久代的目的是因为PermGen常常会发生内存溢出, 引发恼人的 java.lang.OutOfMemoryError: PermGen

总结以上,归纳为两点:

  1. 存储位置不同,永久代物理上是堆的一部分(但是并不是垃圾回收的主要战场),和新生代,老年代地址是连续的,而元空间属于本地内存。
  2. 存储内容不同,元空间存储类的元信息,静态变量和常量池等并入堆中。相当于永久代的数据被分到了堆和元空间中。

下面介绍其中几项概念:

  • 类型信息。包括类的完整名称,父类名称等,该类型信息是在类加载器加载类的时候从类文件中提取出来的
  • 类的静态变量(所以静态变量也被称为类变量)
  • 常量池。包括实际定义的常量和对类型,域,方法的符号引用。它在java的动态链接中起到了核心的作用。这里面有个需要注意的地方,字符串常量池在1.7之后已经从永久代中移除,放入堆中。

注意此处的元空间metaspace , Java将其放在本地内存中, 默认只受本地内存大小的限制,也就是说本地内存剩余多少,理论上Metaspace就可以有多大。也可以使用参数 -XX:MaxMetaspaceSize 参数来指定 Metaspace 区域的大小。

直接内存(堆外内存)

不是虚拟机运行时数据区的一部分。主要是NIO库中一些直接操作本地内存的操作, 例如DirectByteBuffer。

其内存大小虽然不受堆最大内存的制约,但是也会受到操作系统最大内存的制约。

Java中NIO的核心缓冲就是ByteBuffer,所有的IO操作都是通过这个ByteBuffer进行的;

Bytebuffer有两种: HeapByteBuffer和DirectByteBuffer

//分配HeapByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(int capacity);
//分配DirectByteBuffer
ByteBuffer buffer = ByteBuffer.allocateDirect(int capacity);

两者区别:

DirectByteBufferHeapByteBuffer
涉及到IO时拷贝情况不需要拷贝,直接使用需要拷贝到看是的HeapByteBuffer后再使用
创建开销需要调用原生方法从系统申请内存, 所以创建开销较大。 不过一般应用否提前申请一大块内存, 然后自己实现内存管理机制, 例如netty从JVM堆上分配,速度很快,所以创建开销小
对于GC的影响不存在与堆栈, 但是有冰山现象的问题频繁申请新的对象会引发GC

为啥要使用堆外内存?

  1. 可以在进程间共享,减少虚拟机间的复制
  2. 对垃圾回收停顿的改善:如果应用某些长期存活并大量存在的对象,经常会出发YGC或者FullGC,可以考虑把这些对象放到堆外。过大的堆会影响Java应用的性能。如果使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
  3. 在某些场景下可以提升程序I/O操纵的性能。少去了将数据从堆内内存拷贝到堆外内存的步骤。

空间占比最大的一块区域。被所有线程共享。几乎所有的对象实例和数组都会存储在此地。垃圾收集主要是针对此处进行工作。堆具体介绍见下面分析。

主要分为新生代和老年代,见下图分配示意.

img

先讲述一下新生代的事。以下垃圾回收的算法其实也就是复制算法的实现。

新生代,顾名思义,Java中绝大部分对象都是在该区域被创建,存放新建创建的对象。其特点是对象更新速度快,因为Java中大多数对象都不需要存活很长时间(典型的就是局部变量)。该区域是进行垃圾回收频繁的区域,且进行的垃圾回收类型是Minor GC(GC发生的区域不是整个新生代,而是新生代中的Eden区).

新生代又被分为Eden区,S0区,S1区。默认参数是Eden区占新生代的绝大部分空间(8:1)。当一个对象被创建的时候,首先会在Eden区分配空间。当Eden区没有足够的空间时,会触发一次Minor GC,此时会将存活的对象移动到S0,再将Eden清空。若再次发生Minor GC,则将Eden,S0中存活的对象移动到S1,再将Eden,S0清空。

这样对象就会反复在新生代的三个区之间来回移动,随着对象的移动,其GC年龄也会不断增加,当GC年龄达到一个默认值(15)的时候,就会将该对象实例移动到老年代,如此,老年代的数据就出来了。所以,老年代的数据都是新生代中那些存活年龄很大的对象。

经过以上步骤,老年代已经呼之欲出了。当老年代空间不足时,也会触发一次GC,此时的GC又叫FULL GC,比新生代发生的Minor GC要慢得多。

几个问题:
  1. A a = new A(); 各个部分分别属于哪个内存区域?

答:new A()存储在堆上, 然后 a存储在该方法的虚拟机栈上,A 存储在方法区上

参考资料:

  1. https://zhuanlan.zhihu.com/p/161939673
  2. 深入理解Java虚拟机
    ew A()存储在堆上, 然后 a存储在该方法的虚拟机栈上,A 存储在方法区上

参考资料:

  1. https://zhuanlan.zhihu.com/p/161939673
  2. 深入理解Java虚拟机
  3. https://www.sczyh30.com/posts/Java/jvm-metaspace/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值