借用一句话:Java与C++之间有一堵内存动态分配和垃圾收集技术围成的高墙,墙外面的人想进来,墙里面的人却想出去。
一.我们为什么要了解JAVA内存 因为虚拟机帮我们JAVA程序员管理着内存,我们在new Object()申请了内存创建对象之后,便不需要再去delete/free来释放内存。也因此不容易出现内存泄漏和内存溢出的问题,看起来一切都很美好。 但是,如果一个程序员不了解虚拟机是怎么管理内存的,那么在排查内存相关的错误是便会成为一个巨大的难题。
二.内存区域有哪些内存区域分为两种,一种随着虚拟机的进程启动而存在。另一种则依赖用户进程的启动和结束而建立和销毁。 1.程序计数器一块较小的线程私有的内存空间,可以看作是当前线程的所执行的字节码的行号指示器。 如果线程正在执行的是一个JAVA方法,那么计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是native方法,那么计数器值为空(Undefined)。 该内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError(OOM)情况的区域。
2.虚拟机栈线程私有的,每个Java方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用到完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
3.本地方法栈线程私有,同虚拟机栈,为native方法服务。在HotSpot虚拟机中,直接把虚拟机栈和本地方法栈合二为一。
4.堆线程共享的区域。存放实例的区域,几乎所有的对象实例都在这里分配内存。同时,因为空间固定,而用户可能需要不断生成实例,故该区域还是垃圾收集的主要区域。垃圾收集将在后面提到。 Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
5.方法区线程共享的区域,存储已被虚拟机加载的类信息、常量、静态变量等数据。 很多人称之为“永生代”,因为HotSpot使用永生代来实现方法区。Java规范中对方法区的限制十分宽松,可以选择不实现垃圾收集。
6.运行时常量池方法区的一部分,用于存放编译器生成的各种字面量和符号引用,在类加载完成后进入方法区的运行时常量池中存放。 关于这快区域,有一个需要注意的地方。代码如下:
public class t18 { public static void main(String[] args){ Integer a1 = 128 ; Integer a2 = 128 ; System.out.println(a1==a2); Integer b1 = 127; Integer b2 = 127; Integer b3 = 1 + b1; Integer b4 = a1 -1 ; System.out.println(b1==b2); System.out.println(b3==a1); System.out.println(b4 == b1); } } 上面代码的运行结果为 false ,true ,false ,true 。这是很多人第一次见到时都无法理解的,因为这里涉及到了常量池的知识。JVM会把一些int,String等数据进行在常量池中缓存,但是重点在于,对于int型数据,只会缓存 -128~127 范围内的数据。因此: a1、a2超过了127,在堆中分配内存,两者指向不同对象,返回false; b1、b2都指向常量池中的127,故b1、b2指向地址相同,返回true; 第3、4个同理,Integer b3 = 1+b1 ----> Integer b3 =Integer.valueOf(1+b1)。
7.直接内存JDK1.4后加入了NIO (new I/O)类,引入了基于通道与缓冲区的IO方式,可以使用native函数库分配机器内存,如电脑8g内存,JVM可以使用电脑的剩余内存,只需要在java堆中存储DirectByteBuffer对象作为内存的引用进行操作。这样在某些场景中提高性能。 三.在new一个对象时发生了什么
四.一个对象在内存中有哪些部分以HotSpot虚拟机为例,对象在内存中存储的区域可以分为三个部分 1.对象头(Object Header)对象头包括两部分,一部分用于存储对象自身的运行时数据,官方称之为“Mark Word”,包括:HashCode、GC年龄、锁状态、线程持有锁、偏向锁线程id、偏向时间戳等。占一个字长(32bit或64bit,取决于虚拟机)。 另一部分是类型指针,对象指向的类元数据指针,通过这个来确定该对象是哪个类的实例。另外如果一个对象是一个数组,那么还有一块用于记录数组长度的数据。 2.对象数据即实例中存储的,程序员设计的应该存储的数据。 3.对齐填充不是必须的,仅仅起着占位的作用,HotSpot内存管理规定对象的起始地址必须是8字节的整数倍,换句话说对象的大小必须是9字节的整数倍,因此,当实例大小没有对齐时,需要通过对齐填充来补全。 五.如何访问定位对象创建对象是为了使用对象,java虚拟机使用栈上的reference数据来操作堆上的具体对象,目前的访问方式主流有两种: 1.使用句柄访问 Java堆中会划分出一块内存作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象的实例数据与类型数据各自的地址信息。 即访问时refenrence(存句柄地址) --> 句柄池(堆中,存对象地址) --> 具体对象(堆或方法区中)。 2.使用直接指针访问 直接访问,reference(存对象地址)-->具体对象(堆中或方法区中),一次跳转。HotSpot虚拟机使用的就是这种方式。 |
|
来自: 清明幻境 > 《Android开发》