快速过一遍JVM的内存结构,JVM中的内存分为5个虚拟的区域:
javascript
堆
▪ 你的Java程序中所分配的每个对象都须要存储在内存里。堆是这些实例化的对象所存储的地方。是的——都怪new操做符,是它把你的Java堆都占满了的!
▪ 它由全部线程共享
▪ 当堆耗尽的时候,JVM会抛出java.lang.OutOfMemoryError 异常
▪ 堆的大小能够经过JVM选项-Xms和-Xmx来进行调整java
堆被分为:
▪ Eden区 —— 新对象或者生命周期很短的对象会存储在这个区域中,这个区的大小能够经过-XX:NewSize和-XX:MaxNewSize参数来调整。新生代GC(垃圾回收器)会清理这一区域。
▪ Survivor区 —— 那些历经了Eden区的垃圾回收仍能存活下来的依旧存在引用的对象会待在这个区域。这个区的大小能够由JVM参数-XX:SurvivorRatio来进行调节。
▪ 老年代 —— 那些在历经了Eden区和Survivor区的屡次GC后仍然存活下来的对象(固然了,是拜那些挥之不去的引用所赐)会存储在这个区里。这个区会由一个特殊的垃圾回收器来负责。年老代中的对象的回收是由老年代的GC(major GC)来进行的。
方法区
▪ 也被称为非堆区域(在HotSpot JVM的实现当中)
▪ 它被分为两个主要的子区域
持久代
这个区域会 存储包括类定义,结构,字段,方法(数据及代码)以及常量在内的类相关数据。它能够经过-XX:PermSize及 -XX:MaxPermSize来进行调节。若是它的空间用完了,会致使java.lang.OutOfMemoryError: PermGen space的异常。
代码缓存
这个缓存区域是用来存储编译后的代码。编译后的代码就是本地代码(硬件相关的),它是由JIT(Just In Time)编译器生成的,这个编译器是Oracle HotSpot JVM所特有的。
JVM栈
▪ 和Java类中的方法密切相关
▪ 它会存储局部变量以及方法调用的中间结果及返回值
▪ Java中的每一个线程都有本身专属的栈,这个栈是别的线程没法访问的。
▪ 能够经过JVM选项-Xss来进行调整
本地栈
▪ 用于本地方法(非Java代码)
▪ 按线程分配
PC寄存器
▪ 特定线程的程序计数器
▪ 包含JVM正在执行的指令的地址(若是是本地方法的话它的值则未定义)程序员
根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分:
算法
绝大部分 Java 程序员应该都见过 "java.lang.OutOfMemoryError: PermGen space "这个异常。这里的 “PermGen space”其实指的就是方法区。前者是 JVM 的规范,然后者则是 JVM 规范的一种实现,而且只有 HotSpot 才有 “PermGen space”,而对于其余类型的虚拟机,如 JRockit(Oracle)、J9(IBM) 并无“PermGen space”。因为方法区主要存储类的相关信息,因此对于动态生成类的状况比较容易出现永久代的内存溢出。最典型的场景就是,在 jsp 页面比较多或者加载的包较多的状况,容易出现永久代内存溢出。所以,你会看到Java8虚拟机规范和Java7并无什么不一样,可是全部人都在告诉你:Hotspot虚拟机舍弃了永久代。编程
通常的垃圾回收不会发生在永久代,若是永久代满了或者是超过了临界值,会触发彻底垃圾回收(FullGC)。若是仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。那么永久代能回收什么呢?一,废弃的字符串常量,二,再也不被引用的class对象。正确的设置永久代大小对避免FullGC是很是重要的缘由。因此引发full GC的缘由不光是老年代满了或者超过临界值,也有多是永久代满了或者超过临界值。缓存
JDK8的Hotspot使用Metaspace(元空间)代替了永久代。相比较永久代,有以下的一些改变。这个改变在以下文章中有描述:
blogs.oracle.com/poonam/entr…oracle
其中有以下一段话:jvm
元空间被直接接分配在本地内存。默认的元数据空间分配只受到本地内存的限制。咱们可使用一个新的MaxMetaspaceSize选项来设置元空间占用本地内存的大小。这个选项与MaxPermSize相似。当元空间使用量MetaspaceSize设置的值(32位的client模式默认是12M,32位的server模式是16M,64位的server模式则会更多)达到了垃圾收集器会收集那些再也不使用的classloader和class会被回收。全部设置一个较大的MetaspaceSize值会延迟垃圾收集发生的时间。在触发一次垃圾收集之后和下一次垃圾收集以前,元空间的使用值值会随着使用不断增长。jsp
若是要想知道关于元空间更多的内容,能够访问这个连接,这个连接专门讲述了关于元空间一些更多的细节。编程语言
hotspot的垃圾收集的断定主要是使用可达性分析算法。在目前主流的编程语言(java,C#等)的主流实现中,都是称经过可达性分析(Reachability Analysis)来断定对象是否存活的。这个算法的基本思路就是经过一系列的称为“GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来讲,就是从GC Roots到这个对象不可达)时,则证实此对象是不可用的。以下图所示,对象object 五、object 六、object 7虽然互相有关联,可是它们到GC Roots是不可达的,因此它们将会被断定为是可回收的对象。
仅仅是了解这些是不够的。在个人博客中,有一篇JAVA内存白皮书的翻译java内存管理白皮书,很是经典。
即便在可达性分析算法中不可达的对象,也并不是是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:
若是对象在进行可达性分析后发现没有与GC Roots相链接的引用链,那它将会被第一次标记而且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种状况都视为“没有必要执行”。(即意味着直接回收)
若是这个对象被断定为有必要执行finalize()方法,那么这个对象将会放置在一个叫作F-Queue的队列之中,并在稍后由一个由虚拟机自动创建的、低优先级的Finalizer线程去执行它。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束,这样作的缘由是,若是一个对象在finalize()方法中执行缓慢,或者发生了死循环(更极端的状况),将极可能会致使F-Queue队列中其余对象永久处于等待,甚至致使整个内存回收系统崩溃。
finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,若是对象要在finalize()中成功拯救本身——只要从新与引用链上的任何一个对象创建关联便可,譬如把本身(this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;若是对象这时候尚未逃脱,那基本上它就真的被回收了。
代码示例:
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;
public void isAlive() {
System.out.println("yes,i am still alive:)");
}
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize mehtod executed!");
FinalizeEscapeGC.SAVE_HOOK = this;
}
public static void main(String[] args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGC();
// 对象第一次成功拯救本身
SAVE_HOOK = null;
System.gc();
// 由于finalize方法优先级很低,因此暂停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no,i am dead:(");
}
// 下面这段代码与上面的彻底相同,可是此次自救却失败了
SAVE_HOOK = null;
System.gc();
// 由于finalize方法优先级很低,因此暂停0.5秒以等待它
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
} else {
System.out.println("no,i am dead:(");
}
}
}复制代码
运行结果:
finalize mehtod executed!
yes,i am still alive:)
no,i am dead:(复制代码
SAVE_HOOK对象的finalize()方法确实被GC收集器触发过,而且在被收集前成功逃脱了。另一个值得注意的地方是,代码中有两段彻底同样的代码片断,执行结果倒是一次逃脱成功,一次失败,这是由于任何一个对象的finalize()方法都只会被系统自动调用一次,若是对象面临下一次回收,它的finalize()方法不会被再次执行,所以第二段代码的自救行动失败了。由于finalize()方法已经被虚拟机调用过,虚拟机都视为“没有必要执行”(即意味着直接回收)。