JVM虚拟机-读书笔记1-Java存取区域与内存溢出异常(一)

来源:互联网 发布:android源码在哪里 编辑:程序博客网 时间:2024/05/17 01:24

1.运行时数据区域

这里写图片描述

2.数据区域描述

2.1程序计数器

  • 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。在虚拟机概念模型例,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成的。
  • 在任何一个确定的时刻,一个处理器(对于多核处理器来说是一个内核)都会执行一条线程的指令,为了线程切换后能够恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们这类内存区域为“线程私有”的内存。
  • 如果线程正在执行一个JAVA方法,这个计数器记录的是正在执行的虚拟机字节码指令地址;如果正在执行的是Native方法,这个计数器值则为空(Undefined)。此区域是唯一一个在Java虚拟机中没有规定任何OutOfMemory情况的区域。
  • 在java中,native方法是指本地方法,当在方法中调用一些不是由java语言写的代码或者在方法中用java语言直接操纵计算机硬件时要声明为native方法。native方法的执行依赖于JVM的设计者,比如在sun HotSpot(JVM)中,通过JNI(Java Native Interface)的API接口来实现本地化。Native方法常用于两种情况: 1)在方法中调用一些不是由java语言写的代码。 2)在方法中用java语言直接操纵计算机硬件。

2.2Java虚拟机栈

  • Java虚拟机栈也是线程私有的,它的声明周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法执行同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
  • 局部变量表存放了编译器可知的各种基本数据类型(long、short……)、对象引用(可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。
  • 在Java虚拟机中,对这个区域规定了两种异常。如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverFlow异常;如果虚拟机可以动态扩展,如果扩展是无法申请到足够的内存,会抛出OutOfMemory异常。

2.3本地方法栈

  • 与虚拟机栈方法类似,只是保存本地方法。

2.4 Java堆

  • 此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。
  • Java堆可以处于物理上不连续的内存空间中,只要逻辑上连续的即可。在实现时,既可以实现程固定大小的,也可以使可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的。如果堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemory异常。

2.5 方法区

  • 方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • Java虚拟机规范对方法区的限制非常宽松,除了和Java堆一样不需要连续的内存和可以选择固定大小可扩展外,还可以选择不实现垃圾收集。相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入了方法区就如永久代的名字一样”永久”存在了。

2.6 运行时常量池

  • 常量池用于存放编译器生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
  • 运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译器才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量池中,这种特性并开发人员利用的比较多的便是String类的intern()方法(此方法返回一个字符串,这个字符串相同的内容,但保证是从一个独特的字符串池)。

2.7 直接内存

  • 直接内存并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。
  • 在JDK1.4后加入了NIO类(New Input/Output)类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景显著提高性能,避免了Java堆和Native堆中来回复制数据。

3.对象的创建

  • 1.虚拟机遇到遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
  • 2.在类加载完成后,接下来虚拟机将为新生对象分配内存。对象所需内存的大小在类加载完成后便可完全确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中分配出来。
    Java堆内存是绝对规整的,采用分配方式“指针碰撞”。如果是不绝对规整的,虚拟机必须维护一个列表,这种分配方式称为“空闲列表”。
    除了如何划分可用空间外,还有一个需要考虑问题是对象创建在虚拟机中是非常频繁的行为,即使是仅仅修改一个指针所指向的位置,在并发情况下也并不是线程安全的,可能出现正在给对象A分配内存,指针还没来得及修改,对象B又同时使用了原来的指针来分配内存的情况。
    解决方法有两种:
    (1).对分配内存空间的动作进行同步处理
    (2).把内存分配的动作按照线程划分在不同的空间中执行,即每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB)。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才需要同步锁定。
  • 3 虚拟机要对对象进行不要的设置,例如这个对象是哪个类的实例,如何才能找到类的元数据信息、对象的哈希吗、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。
  • 4.上面的工作都完成后,从虚拟机的视角来看,一个新的对象已经产生,但从Java程序角度来看,对象创建才刚刚开始–方法还没有执行,所有的字段都还未零。所以,一般来说,执行new指令之后,会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。
0 0
原创粉丝点击