1:上图为java虚拟机运行时数据区
程序计数器:是一块较小的内存,可以看作是当前线程所执行的字节码的行号指示器。字节码的解释器工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支循环跳转异常处理线程回复等都要依赖于这个计数器完成。
java虚拟机多线程通过线程轮流切换分配处理器时间片实现的,因为为了切换能恢复到正确位置,每条线程都需要有一个独立的程序计数器,各线程互不影响,独立存储。这块内存为线程私有内存。
java虚拟机栈:为虚拟机执行java方法,即字节码服务
线程私有,生命周期和线程相同。
描述的是java方法执行的内存模型:每个方法执行时都会创建一个栈帧,栈帧用于存储局部变量,操作数,动态链接,方法返回地址等。每一个方法从调用到执行,对应一个栈帧在虚拟机栈中入栈到出栈的过程。
其中局部变量表存放编译器可知的各种基本数据类型,对象引用类型等,该表所需的内存空间在编译期间完成分配,当进入一个方法时,该方法需要在帧中分配多大局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
人们常说的栈内存指的就是虚拟机栈或者说是虚拟机栈中的局部变量表
堆:jvm所管理内存的最大一块。
java堆被所有线程共享,在虚拟机启动时创建,唯一的目的就是存放对象实例,几乎所有对象实例都在这里分配内存。
堆可处于物理上不连续的内存空间中,只要逻辑上连续就可。
目前堆都可扩展:-Xmx和-Xms控制(具体可参考jvm调优)
方法区:
是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即使编译器编译后的代码等数据。jvm规范中将其描述为堆的一个逻辑部分,但Non-heap
运行时常量池:
是方法区的一部分。Class文件除了类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
相对于class文件常量池运行时常量池具备动态性,比如String类的intern()方法。
public native String intern();
文档告诉我们该方法返回一个字符串对象的内部化引用。
众所周知:String类维护一个初始为空的字符串的对象池,当intern方法被调用时,如果对象池中已经包含这一个相等的字符串对象则返回对象池中的实例,否则添加字符串到对象池并返回该字符串的引用。
从程序的角度上怎么来看这个方法呢,我们假设有两个字符串s1,s2,当s1.equals(s2)时,s1.intern()==s2.intern(),也就是说这两个字符串在内存中使用的是同一个实例。
intern是一个native的方法,但按照其文档解释,应该是JVM维护了一个当前进程曾经出现过的字符串的hash表,在调用intern时,会查询该表。如果已经存在,则直接返回对该String的引用;如果没有,则创建一个,并加入到hash中。
从文件中读取的词,所以只需要在读取词后,对每个词调用下intern再存储,就可以保证同样内容的字符串只有一份存储。更确切的说,只有一个对象。即不仅字符串内容只有一份,其overhead(String类的非内容部分,例如其他成员变量)也只有一份。
另外,如果使用字面量(literal)来定义字符串,则自动会调用intern,从而减少内存占用。
直接内存:分配不受java堆大小的限制
2:对象的创建
(克隆,反序列化,深浅拷贝)
当虚拟机遇到一条new指令的时候,数显检查这个指令的参数能否在常量池中定位到一个类的符号引用,检查这个符号引用是否被加载,解析和初始化过。如果没有,则先执行类的加载过程。
加载检查后,虚拟机为新生对象分配内存,对象所需内存的大小在类加载完成后便可完全确定。为对象分配空间等同于将一块确定大小的内存从java堆中划分出来。如果堆在内存中规整,则采用“指针碰撞”,如果不规整则“空闲列表”。而是否规整又取决于采用的垃圾收集器是否带有压缩整理功能。
内存分配完后,虚拟机将分配到内存空间都初始化为0,接下来jvm根据对象头对对象进行必要的设置,如果字节码跟随invokespecial指令,执行new指令后会接着执行<init>方法,将对象初始化。
3:对象的内存布局
对象在内存中存储的布局可以分为3块区域:对象头,实例数据,对齐填充
对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID等。
他被设计成一个非固定的数据结构以便在极小的空间存储尽量多的信息,会根据对象的状态复用自己的存储空间。
另一部分是类型指针,即对象执行它的类元数据的指针,虚拟机通过指针来确定该对象是哪个类的实例。
4:对象的访问定位
java程序通过虚拟机栈中变量表中的reference的数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针。
优势是reference中存的是稳定的句柄地址,如果垃圾收集时对象被移动,只会改变句柄中,实例数据的指针,reference本身不用修改
速度快,一次地址直接定位
内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。