第二章 Java内存区域与内存溢出异常

时间:2023-11-24 20:49:50
  • 运行时数据区域:

第二章 Java内存区域与内存溢出异常

    • 程序计数器(Program Counter Register):
      当前线程执行码行号指示器,属于线程私有内存。字节码解释器工作时就是通过调整这个计数器的值来选取下一条需要执行
      字节码指令。当执行Java代码时,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果是本地Native方法时为空,
      该区域不会出现OutOfMemmoryError错误的区域。
    • Java虚拟机栈(Java VM Stack):
      线程私有,生命周期与线程一致。其描述了Java方法执行的内存模型:每个方法执行时都会创建一个对应的栈帧(Stack Frame)
      用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到执行完成的过程,就对应着一个栈帧在虚拟机
      栈中从入栈到出栈的过程。局部变量中存放了编译期可知的各种基本数据类型(64位long、double占用2个局部变量空间Slot)、
      对象引用、returnAddress类型。局部变量表所需内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大
      的局部变量表空间是完全确定的,在方法运行期不会改变局部变量表的大小。
      可能出现的异常:1.线程请求的栈深度大于虚拟机所允许的深度,*Error异常。2.如果虚拟机栈可以动态扩展,当扩展
      时无法申请足够的内存时,OutOfMemoryError异常。
    • 本地方法栈(Native Method Stack):
      与Java虚拟机栈相似,只是其描述的执行本地方法时内存区域,也是线程私有。
    • Java堆(Heap):线程共享,随虚拟机启动而创建,用途为存放对象实例,几乎所有的对象实例均在此处存放,是垃圾收集器工作
      的主要区域。现代收集器大都采用分代收集算法,故Java堆还可细分为:新生代和老年代,再细分为:Eden空间、From Survivor空
      间、To Survivor空间等。-Xmx -Xms调整其大小。
    • 方法区(Method Area):线程共享,存储虚拟机加载的类信息、常量、静态常量、即时编译器编译的代码等信息。
    • 运行时常量池(Runtime Constant Pool):是方法区一部分
    • 直接内存(Direct Memory):NIO直接分配堆外内存。
  • 虚拟机对象
    • 对象的创建:
      内存分配:虚拟机遇到一条new指令时,首先先去检查指令参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用
      代表的类是否已被加载、解析、初始化。如果没有,则执行相应的类加载过程。在类加载检查通过后,接下来虚拟机将为新对象分配内
      存,对象所需内存的大小在类加载完成后便可完全确定。虚拟机为对象分配内存空间策略有两种:1.若果内存空间绝对规整,则采用“指
      针碰撞(Bump The Pointer)”,使用过的内存放在一侧,未使用过的内存放在另一侧,中间放着一个指针作为分界点的指示器,分配时
      只需将指针想空闲区域移动所需大小的区域即可完成分配。2.若果内存空间是不规整的,则需要采用“空闲列表(Free List)”,将空闲内
      存空间记录下来,每次分配时需要检查空闲列表来分配所需内存大小。选择哪种分配策略需要根据内存时否规整决定,而Java堆是否规
      整又由所采用的垃圾收集器是否带有压缩整理功能所决定的。防止内存分配过程中的并发问题:1.同步,CAS失败重试方案。2.本地线程
      缓冲(TLAB),线程分配内存隔离。
      内存初始化:内存分配完成后,将内存初始化为零值。
      对象设置:对象头(Object Head)
    • 对象的内存布局:对象头(Object Head)、实例数据(Instance Data)、对齐填充(Padding)
      对象头:1.存储对象自身运行时数据,哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳。
                       2.类型指针,指向该对象属于哪个类。
    • 对象的访问:栈上reference来操作堆上的具体对象。访问方式有两种:1.句柄 2.直接引用