JVM内存结构图解

一 真实系统中的概念

  JVM(Java Virtual Machine),顾名思义是对真实计算机系统的模拟,正因如此才能屏蔽物理机器的变化,从而实现“一次编译,到处运行”。

  相信很多Java程序员经常听到堆、栈等概念,也会进行设置调优以让Java应用能够更好地运行,但对于JVM与真实计算机系统之间的关系并没有特别清晰的认识。因此,这里先简单介绍下真实计算机系统中的一些概念。
  右图是Linux系统的内存管理的近似模型

  现代计算机系统中,也有寄存器、栈、堆等概念,这些与JVM中的概念相似,但有本质的不同。

  现代计算机系统中,内存是由操作系统配合CPU的段寄存器来管理的,主要分为内核空间(内核代码段,内核数据区)、代码段(.text)、数据段(.data 和 .bss)、栈、堆、共享内存区等。
  内核空间只能由操作系统访问,用户进程不能直接访问。如果用户进程要访问这部分数据,只能调用系统函数,否则会引发系统错误。
  栈保存局部变量等,一般由操作系统自动分配,由编译程序管理。
  栈由高地址向低地址发展,堆由低地址向高地址发展,如果两者地址发生重叠,那么就必定会出现程序错误。但操作系统一般会有内存保护机制,且因为有虚拟内存设计,其它存储设备可以映射到物理内存,因此可以降低物理内存的使用。

  JVM作为进程运行在操作系统之上,那么操作系统也需要为JVM分配栈空间。
  JVM作为进程启动或运行期间,向操作系统申请内存,操作系统在其管理的堆中为JVM分配内存,JVM再将这些内存划分成不同的区域。所以,JVM管理的运行时数据区实质上是处于系统堆中。

  所以,JVM中的堆并非操作系统管理的堆,JVM的栈也不是操作系统管理的栈。

  聊了聊真实计算机系统,再接着谈谈JVM。

二 JVM运行时数据区


㈠ PC寄存器(Program counter register)

  PC寄存器又称作程序计数器,其作用类似于cpu中的代码段寄存器:指针寄存器(汇编中CS:EIP总是指向下一条要运行的指令地址)。
  线程中正在运行的方法被称为当前方法(current method)。如果当前方法是非native的,PC寄存器保存的是当前方法的字节码指令的地址;否则,值为undefined。

㈢ 堆(Heap)

⑴ 系统堆 与Java堆

  这里的堆指的是Java堆,与操作系统管理的堆是两个不同的概念,但作用类似。
  C语言中,可以使用malloc()向操作系统申请堆内存,使用完毕后一般需要显式调用free()来释放内存,如果未释放则可能导致内存耗尽。同时,这种内存申请、释放的方式容易产生内存碎片(C/C++程序员有些会使用第三方库来管理内存,有些则自己实现内存池来管理内存)。
  但在Java中,这些由JVM来处理,因此避免了复杂繁琐的内存管理。
  JVM运行过程中,可以动态地向操作系统申请内存作为Java堆或归还未使用的内存,堆内存可以是非连续的内存空间。当触发预设条件时,JVM会调用垃圾收集器来回收未被使用的对象。
  Java堆是垃圾收集器最重要的工作区域,另一个区域是非堆(永久代)。

  以下内容中,除非特别说明,堆均指的就是JVM堆。

⑵  内存分配与垃圾回收

  堆保存类实例对象和数组对象,堆是共享数据区,各线程均可使用此区域。

  堆内存空间分配和垃圾收集机制会因垃圾收集器不同而不同,这里以Parallel new + CMS垃圾收集器为例。

  堆分为新生代(young generation)和老年代(old generation);新生代又可分为Eden, From Survivor, To Survivor。
  
  当Eden空间足够时,大部分新创建对象会被分配在Eden区(部分大对象会被直接分配到老年代)。
  当Eden空间不足时,会发生一次Minor GC,未被引用的对象会被回收,Eden中仍然存活的对象会被移动到From Survivor。
  Survivor中的对象每熬过1次MinorGC增加1岁,默认超过15岁依然存活的对象会被移入Tenured。
  当发生Minor GC时:如果Survivor的空间不足以保存Eden区仍然存活的对象,那么该对象会被直接移入 Tenured;如果Survivor 中同年对象的占用空间的总和达到或超过其中一个Survivor的一半,那么所有同年对象都会被移入Tenured。
  通常情况下,只有其中一个Survivor持有对象,另一个在下次GC之前总是为空。当再次发生GC时,Eden中的对象被复制到标记为To的空的Surivivor中,原来From中依然存活的未到达年龄的对象也会复制到To,此时To被标记为From,原来的From置空并被标记为To,轮换是为了避免Surivivor中因没有连续空间而导致对象被直接移入老年代。
  当Tenured空间使用达到一定比例时会触发Full GC,并且可能伴随着进行Minor GC。
  除了CMS和新的G1垃圾收集器以外,其它的垃圾收集器都会触发Stop The World,所有其它线程暂停。

⑶ 线程本地分配缓冲区(Thread-Local Allocation Buffer, TLAB)

  为保证线程安全和避免内存争用,JVM会为每一个线程在Eden中设置一小块私有的缓冲区,称为TLAB。每一个TLAB都只有一个线程可以分配对象,因此可以避免采用全局锁来控制内存分配,而只需要在最后一个分配对象的末端顺序写入即可(指针碰撞),可以快速分配内存。
  当一个线程的TLAB的空间不足需扩充内存时,那么就需要多线程方式来保证不会出现数据覆写。

⑷ 注意事项

  1.为了减少短期存活的大对象进入老年代,应尽可能缩短其生命周期,一种比较好的方式是在最后使用的地方手动置为null。
  幸运的话它会在Eden区被回收,即使进入Survivor也很难熬过15次Minor GC。
  2. 数据库查询只获取必要数据,而不是全表查询。
  3. 严格限定对象作用域,避免作用域溢出,导致对象总是被引用而无法回收。
  4. 多用单例,少用new。

㈣ 非堆(Non-Heap Memory)

  非堆也称作永久代(permanent generation),逻辑上属于堆的一部分,但老年代的对象并不会移入永久代。
  永久代只用于存储元数据(Metadata),譬如类的数据结构、字符串常量池等数据。
  运行时常量池与字符串常量池是完全不同的概念,运行时常量池归属于具体的类,是类数据结构的一部分
  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值