Tip-关于JVM和Hotspot,你也许有这么几个容易晕的问题

1.JVM的结构到底有哪些?

       快速过一遍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正在执行的指令的地址(若是是本地方法的话它的值则未定义)程序员

2.为何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虚拟机舍弃了永久代。编程

3.JDK7中的永久代会回收吗?它回收哪些东西?

       通常的垃圾回收不会发生在永久代,若是永久代满了或者是超过了临界值,会触发彻底垃圾回收(FullGC)。若是仔细查看垃圾收集器的输出信息,就会发现永久代也是被回收的。那么永久代能回收什么呢?一,废弃的字符串常量,二,再也不被引用的class对象。正确的设置永久代大小对避免FullGC是很是重要的缘由。因此引发full GC的缘由不光是老年代满了或者超过临界值,也有多是永久代满了或者超过临界值。缓存

4.永久代已经没了。那么JVM规范中的方法区HotSpot是如何实现的?

       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

       若是要想知道关于元空间更多的内容,能够访问这个连接,这个连接专门讲述了关于元空间一些更多的细节。编程语言

5.Hotpsot垃圾回收的原理是什么?

       hotspot的垃圾收集的断定主要是使用可达性分析算法。在目前主流的编程语言(java,C#等)的主流实现中,都是称经过可达性分析(Reachability Analysis)来断定对象是否存活的。这个算法的基本思路就是经过一系列的称为“GC Roots”的对象做为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(用图论的话来讲,就是从GC Roots到这个对象不可达)时,则证实此对象是不可用的。以下图所示,对象object 五、object 六、object 7虽然互相有关联,可是它们到GC Roots是不可达的,因此它们将会被断定为是可回收的对象。

仅仅是了解这些是不够的。在个人博客中,有一篇JAVA内存白皮书的翻译java内存管理白皮书,很是经典。

6.被断定为垃圾地对象必定会被回收吗?

       即便在可达性分析算法中不可达的对象,也并不是是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:
       若是对象在进行可达性分析后发现没有与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()方法已经被虚拟机调用过,虚拟机都视为“没有必要执行”(即意味着直接回收)。

相关文章
相关标签/搜索