盒子
盒子
文章目录
  1. 前言
  2. JVM 介绍
  3. JVM 内存模型
    1. 对象的那些事
  4. 相关虚拟机参数

JVM内存模型

前言

本文内容大多来自《深入理解Java虚拟机》,但是经过整理,语言更加简练。

本篇为JVM入门系列的第一篇。本系列一共分为:

  1. JVM 内存模型。
  2. 垃圾收集算法。
  3. 类加载机制。

JVM 介绍

JVM(Java Virtual Machine)是Java程序员很熟悉的名字,但是他不只局限于Java语言,实际上JVM还支持其他语言运行在JVM上。

总的来说,JVM具有以下两个特性:

  1. 跨平台:Java程序员应该很清楚这一点,由于JVM,Java语言能够实现”Write Once, Run Anywhere”。
  2. 跨语言:近年来出现了很多基于JVM的语言,比如 Groovy, Scala 等,之所以称为”基于JVM的语言”是因为JVM的输入其实是字节码格式的文件(而不是Java程序),而这些语言经过各自编译器编译后变成了字节码格式文件,因此能够在JVM上运行。

虚拟机其实有很多,比如 Sun Hotspot VM(目前所说的JVM就是这个),Dalvik VM(Android 的虚拟机),JRocket VM(另一款JVM,目前也被Oracle收购)。

JVM 分为 Client 模式和 Server 模式,可以用 java -version 查看。

JVM 是基于栈的体系结构(其他还有累加器的体系结构、寄存器-寄存器的体系结构等),这就限制了字节码指令的输入格式,比如 pop 指令表示将栈顶元素出栈,iadd 指令表示将栈顶两个元素相加后再放入栈顶(pop,iadd都是字节码指令)。

JVM 内存模型

JVM 内存模型图

JVM拥有自己的内存空间,这块内存空间可以划分成以下几大区域:

  1. 程序计数器(Program Counter Register): 线程私有。值为一个整数,表示当前正在执行的字节码指令的地址。
  2. 虚拟机栈(VM Stack): 线程私有。他是一个栈,每个元素是一个栈帧(Stack Frame),一个栈帧表示一个方法调用的状态,栈顶的栈帧为当前正在执行的方法的栈帧。
  3. 本地方法栈(Native Method Stack): 线程私有。与虚拟机栈的唯一区别是虚拟机栈中所说的方法是Java方法,而本地方法栈中的方法是Native方法。Hotspot将虚拟机栈和本地方法栈合二为一,即在Hotspot中只有虚拟机栈,没有本地方法栈。
  4. (Heap): 线程共享。存放几乎所有的对象实例和数组。他也被称为”GC堆”,因为堆是垃圾收集器的主要管理区域。为了使分配内存和回收内存更方便,堆被分为:
    • 新生代(Young Generation):之所以称为新生代,是因为每次GC时这块区域的对象都有大批死去,即来得快去得也快(IBM统计约为98%的对象死去)。这部分的垃圾收集也称为 Young GC/Minor GC。新生代区可以继续分为 Eden 区,From Survivor 区,To Survivor 区(即一块 Eden 区,两块 Survivor 区),From Survivor 区和 To Survivor 区只能使用一个,不能同时使用。一般刚创建的对象被分配在年轻代的 Eden 区(例外是如果一个对象很大,以至于在年轻代的 Eden 区放不下,那么直接进入老年代),熬过一个 Young GC 后进入 Survivor 区。当年轻代的对象熬过若干个 Young GC 后进入老年代(因为年纪大了)。
    • 老年代(Tenured Generation):之所以称为老年代,是因为每次GC时这块区域的对象存活率较高。这部分的垃圾收集也称为 Full GC/Major GC。一般来说年轻代的空间比老年带的空间小。
    • 对于上面不同类型的堆内存都存在不同的GC算法,因此这种GC算法称为”分代回收算法”。Young GC 比 Major GC 频率更高,速度更快。
    • 当多个线程同时分配对象时,为了线程安全,需要对整个堆加锁,这很影响性能(锁的粒度太大),因此提出了 TLAB(Thread Local Allocation Buffers),特点是为每个线程在Eden区分配一个缓冲区,每个线程只使用自己的TLAB,等到空间用完了再使用全局锁。
  5. 方法区(Method Area): 线程共享。存储类元数据、常量、静态变量。Hotspot将其称为永久代(Permanent Generation),目的是为了表示这块区域的数据一旦生成就不太改变(虽然不是一直不变),但是这部分区域也需要GC。运行时常量池(Runtime Constant Pool)是方法区的一部分,用来存放符号引用,常量,直接引用等。JDK 1.7开始,字符串常量池从方法区中移出。

对象的那些事

问题1:新创建的对象如何在堆中分配内存?

  1. 若堆中内存是规整的,即可以有一道分界线来划分出两块区域,一块是已分配的内存,另一块是未分配的内存,则使用”指针碰撞“(Bump the Pointer),即用指针来作为分界线。
  2. 若堆中内存是不规整的,则使用”空间列表“(Free List),即用一个列表记录未分配的内存,列表的每个元素为一块未分配的内存,包含起始地址和长度。若要分配对象,则只需要在空间列表中找出一个足够大的区域即可。

堆是否规整和垃圾收集器采用的GC算法有关,即回收之后是否对堆内存进行压缩整理。

问题2:如何找出对象的类型?

对象实例可以划分为对象头、实例数据、对齐填充。对象头中包含了很多信息,比如对象的hashcode、类型指针等,其中类型指针就是指向方法区的类元数据。

相关虚拟机参数

  • -Xmx2g: 堆的最大允许内存为2G。
  • -Xms2m: 堆的最小允许内存为2M。
  • -Xmn1m: 堆中年轻代的内存为1M。
  • -XX:PermSize=128m: 永久代大小为128M。
  • -XX:MaxPermSize=128m: 永久代最大允许128M。
  • -Xss128k: 虚拟机栈为128K。
  • -XX:SurvivorRatio=8: 年轻代中Eden区与Survivor区的容量比值为8。
  • -XX:PrintGCDetails: 每次GC时输出日志。
  • -XX:+HeapDumpOnOutOfMemoryError: 在OOM时Dump出堆的日志文件。
  • -XX:+DisableExplicitGC: 禁用显式GC,即忽略程序中调用的System.gc()
支持一下
扫一扫,支持xiazdong