1.java运行时数据域:
根据《Java虚拟机规范(Java SE 7 版)》的规定,Java虚拟机所管理的内存区域分为如下部分:方法区、堆内存、虚拟机栈、本地方法栈、程序计数器.。如图 所示
程序计数器:
程序计数器是一块非常小的内存空间,可以看做是当前线程执行字节码的行号指示器,每个线程都有一个独立的程序计数器,因此程序计数器是线程私有的一块空间,此外,程序计数器是Java虚拟机规定的唯一不会发生内存溢出的区域。
虚拟机栈:
虚拟机栈描述的是方法运行的内存模型,在每个方法运行的时候,都自己会 创建一个栈。同C语言所描述的栈一样。在栈中,存储的信息有方法的局部变量表,操作数栈,动态 链接,方法出口等消息,该区域会报的异常信息有两种情况:1.栈溢出(StackOverflowError),即线程创建的栈深度大于虚拟机规定的最大深度,2.内存溢出(OutOfMemoryError)
本地方法栈:
本地方法栈特性跟虚拟机栈一样,但是本地方法栈的方法都是native方法,用C或者C++实现。
方法 区:
方法区同堆一样,也是被所有线程共享的。存储的信息是被虚拟机已经加载的常量,类信息等,因此方法区也被称为永久代。jdk1.8之前方法区是隶属于堆内存,jdk1.8之后,原方法区中存储的类信息、编译后的代码数据等已经移动到了元空间(MetaSpace)中(元空间存储在本地内存中)在jdk1.7中 ,原方法区中的静态变量,字符串常量次也从方法区移动到堆中。下图为网上方法区在jdk版本的变化:
堆内存:
堆在被线程所共享的一块区域。在 虚拟机刚刚启动的时候创建,该内存存放的是对象的实例和数组。虚拟机规范规定,堆内存空间在物理上不一定相连,但在逻辑上一定相连。当堆中中的内存不够时或者无法扩容的时候 ,则会爬出内存溢出(OutOfMemoryError)的异常。在垃圾收集的层面上来看,由于现在收集器基本上都采用分代收集算法,因此堆还可以分为新生代(YoungGeneration)和老年代(OldGeneration),新生代还可以分为Eden、From Survivor、To Survivo
如上图所示,为新生代垃圾回收例子,在GC开始的时候,对象只会存在于Eden区和名为“From”的Survivor区,Survivor区“To”是空的。紧接着进行GC,Eden区中所有存活的对象都会被复制到“To”,而在“From”区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值(年龄阈值,可以通过-XX:MaxTenuringThreshold来设置)的对象会被移动到年老代中,没有达到阈值的对象会被复制到“To”区域。经过这次GC后,Eden区和From区已经被清空。这个时候,“From”和“To”会交换他们的角色,也就是新的“To”就是上次GC前的“From”,新的“From”就是上次GC前的“To”。不管怎样,都会保证名为To的Survivor区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,“To”区被填满之后,会将所有对象移动到年老代中。
二:判断对象 是否已经死亡
分析了Java运行时内存区域,可知,在虚拟机栈,本地方法栈,程序 计数器都死随着线程的死亡而销毁,线程的启动而开辟。所以JavaGC主要针对的内存区域为运行时数据区域的堆内存和方法区。
1.引用计数法
给对象添加一个引用计数器,当对象被引用一次的时候,计数器的值就加一 ;引用 失效的时候就减一,计数器为零则表示该对象不可能再被使用。但是引用计数算法不能清除内存中相互应用的的实例 ,如下代码所示:
内存会一直有两个无法应用的对象存在。
2. 可达性分析算法
可达性分析算法是指在一系列的”GCROOT”节点向下搜索,形成 一条引用链,当一个对象不存在 任何 引用 链的时候,则说明该 对象是 不可用的。
GCROOT的类型:
a:虚拟机栈中的本地变量列表中的引用对象。
b:方法区中的静态常量的引用对象。
c:方法区中的常量引用对象。
d:native方法区中的引用对象。
GC ROOT的类型,可思考平时编码中,对象可以哪些地方被引用。
在主流的商业语言中,如java,C#都是通过可达性分析判断对象是否存活。
参考:
《深入理解Java虚拟机(第2版)》