前言
看了第二章的内容,大致了解了下Java虚拟机的内存分划,以及内存操作导致的内存溢出异常,对于我以后使用Java编程有很大帮助
内容
Java 存储区域与内存溢出异常
前言
对于c/c++的程序开发人员来说,内存管理既是他们的权力也是让他们头疼的问题,而对于Java开发人员来说,在虚拟机的帮助下,我们不需要为每个new操作去配对delete/free代码,因此也不容易出现内存泄漏和内存溢出的问题,但是如果不了解虚拟机的内存管理机制,一旦出现内存问题,很难排查问题,笔者在读完深入理解Java虚拟机后做了以下的笔记,希望对于一些初识者有一些帮助。
运行时数据区域
Java虚拟机在执行Java程序的过程中把它所管理的内存划分成若干个不同的数据区域,各自在运行时起到不同的用途。其具体分区情况,如下图所示。
Java虚拟机运行事数据区
下面对各个区域进行简单的介绍
- 程序计数器: 一块较小的内存单元,是当前线程所执行的字节码的行号指示器;
- Java虚拟机栈:描述Java方法执行的内存模型,每个方法从调用到执行完毕,对应于一个栈帧在虚拟机栈中入栈到出栈的过程;
- 本地方法栈: 与虚拟机栈发挥的作用比较相似,虚拟机栈为虚拟机执行Java方法服务,而本地方法栈是虚拟机使用到的Native方法服务。
- Java堆: Java堆是Java虚拟机所管理的内存中最大的一块,是所有线程共享的一块内存区域,在虚拟机启动时创建,用以存放对象实例。
- 方法区: 与Java堆一样,是各个线程共享的内存区域,用以存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
- 运行时常量池: 方法区的一部分,用以存放编译期生成的各种字面量和符号作用;
- 直接内存: 这部分不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区,但是在运行是这个部分内存也频繁的使用。
虚拟机对象
对象的创建
Java是一门面向对象的编程语言,在Java程序运行过程种无时无刻都有对象被创建出来。而创建对象的命令就是new,当虚拟机遇到一条new指令的时候,首先将去检查这个指令的参数是否能在常量池种定位到一个类的符号作用,并且检查这个符号引用代表的类是否被加载、解析和初始化过。如果没有,那就先执行相应的类加载过程,在类加载检查通过后,虚拟机才会对新生的对象分配内存,并且内存大小可确定,在内存分配完后,虚拟机要将内存分配到的内存空间初始化为零值,为了保证对象实例在Java代码中可以不赋值直接使用,接下来虚拟机将会对对象进行必要的设置。
在完成以上工作后,一个对象才算真正的被创建起来了。
对象的内存布局
在虚拟机中,对象在内存中的存储的布局被分为3块区域:对象头、实例数据和对齐填充。
- 对象头: 对象头一部分用以存储对象自身的运行数据,一部分是它指向的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;
- 实例数据: 是对象真正存储的有效数据,也是在程序代码中所定义的各种类型的字段内容;
- 对齐填充: 这部分并不是必然存在的,也没有特殊的含义,只是起占位符的作用。
对象的访问定位
建立对象是为了使用对象,我们的Java程序需要通过栈上的reference数据来操作堆上的具体对象。
Java虚拟机规范规定了一个指向对象的引用,但是没有定义这个引用应该通过何种方式去定位、访问堆中的对象的具体位置,而是取决于虚拟机的实现而定。目前主流的访问方式有使用句柄和直接指针这两种方式。
- 使用句柄,Java堆中会划出一块内存来做句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,其原理如下图所示。
使用句柄访问
- 使用直接指针访问,那么Java堆对象的布局需要考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址,其原理如下图所示。
使用直接指针访问
两种对象访问方式各有优势,使用句柄来访问的最大好处就是reference中存储的是隐定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改;而使用直接指针访问的最大好处就是速度快,节省时间开销。
内存溢出异常
在Java虚拟机规范中,定义了两种异常,一种是线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError;另一种是虚拟机在扩展栈的时候无法申请到足够的内存空间,会抛出OutOfMemoryError异常,这两个异常都是我们平常编写程序时,必须要注意的,比如慎用static,数据库处理游标、文件等处理后是否释放资源,图片处理是否进行压缩、回收等。
版权声明:本文为博主原创文章,转载请注明出处KidSea