Android面试之「JVM 篇 (上)」

本文为原创文章,如需转载请注明出处,谢谢!

概述

本文将围绕以下3点对 JVM 知识进行总结,知识全部源于《深入理解 JVM》

1.JVM 存储
2.垃圾回收机制
3.类加载

一、JVM 存储

首先盗张图

JVM 内存模型

1. 程序计数器 (Program Counter Register)

概念:一块较小的内存空间,是字节码解释器的行为指示器。

概念比较抽象,我们知道程序执行的过程中会有分支、循环、跳转、异常处理、线程恢复等基础功能,也就是程序运行碰到了关键字或特殊行为,字节码解释器就需要进行特殊处理,而字节码需要怎么做正是由程序计数器去通知。

程序计数器是在每条线程中独立存在的一块内存,不与其他线程共享,所以这类内存叫「线程私有」的内存。

2. Java 虚拟机栈 (VM Stack)

概念:Java 方法执行的内存模型,每个方法执行的时候,都会创建一个栈帧用于保存局部变量表,操作数栈,动态链接,方法出口信息等。一个方法调用的过程就是一个栈帧从 VM 栈入栈到出栈的过程。(平时说的「堆和栈」的栈就是他了)

这个概念就比较友好了,VM 栈主要用于存储方法包含的信息:

  • 局部变量表:
    -基本数据类型(byte,short,int,long,float,double,boolean)
    -引用类型(reference 类型,可能是指向一个对象起始地址的引用指针,或是指向另一个引用指针的引用)
    -returnAddress 类型(我理解的是方法返回值类型)

另外,这个区域中有两种异常:StackOverflowError,OutOfMemoryError,很熟悉对吧。我们都知道递归深度达到一定程度,StackOverflow 就出现了,也就是 VM 栈的深度小于你请求的深度了。在栈的深度增加时,会开辟新内存,如果无法申请到足够的内存,OutOfMemory 就出现了。

最后,VM 栈也是线程私有的。

3. 本地方法栈 (Native Method Stack)

概念:执行 Native 方法的栈

与 VM 栈发挥的作用非常相似,VM 栈执行Java 方法(字节码)服务,Native 方法栈执行的是 Native 方法服务。

同样的,有两种异常:StackOverflowError,OutOfMemoryError。

Native 栈也是线程私有的。

4. Java 堆 (Java Heap)

概念:所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域唯一的目的就是存放对象实例,几乎所有的对象都在这分配内存。

概念也说的比较清楚了,这里存放我们 new 的所有对象。如何管理这些对象呢?一般的虚拟机的垃圾收集器(GC)都采用分代收集算法(在总结垃圾回收的时候会详细说,这里我们先记住),所以 Java 堆可被细分为:新生代和老年代,更细的分法这里就不总结了。

无论 Java 堆怎样划分,都与存储的内容无关,存储的永远都是对象实例,划分区域的目的是为了更好地回收内存,更快地分配内存。

当堆中没有内存完成实例分配时,OutOfMemory 就出现了。

Java 堆是线程共享的。

5. 方法区 (Method Area)

概念:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码数据等。

这部分区域包含「运行时常量池」,所以这个区域主要存储常量。方法区也常被人成为「永久代」(Permanent Generation),但两者本质并不相同,永久代是垃圾收集器的一种分区方法,这样方法区可以和 Java 堆一样用一套回收算法,省的再为方法区单独编写管理内存的代码了。

另外,JDK 1.6 中常量池和 Java 堆是分离的,JDK 1.7 中,常量池和 Java 堆合并共用一块内存。所以 String.intern()在 1.6 和 1.7 中的使用会有所不同。具体参考这篇文章
http://tech.meituan.com/in_depth_understanding_string_intern.html

当方法区无法满足内存分配需求时,OutOfMemory 就出现了。

方法区是线程共享的。

二、垃圾回收机制

首先思考两个问题

1.怎样判定一个对象的存活或死亡?
2.垃圾收集器是怎样管理对象的?

1. 引用计数法

一个教科书式的判断对象存活的算法,简单来说是这样的:给对象增加一个引用计数器,每当有地方引用这个对象时,计数器加一,当引用失效时,计数器减一。这个算法实现简单,判定效率也高,但是,Java 虚拟机中并没有选这种算法来管理内存,原因是它很难解决对象之间循环引用的问题。例子如下

public class ReferenceCountingGC {
    public Object instance = null;
    
    private static final int _1MB = 1024 * 1024;
    
    /**
     * 这个成员的唯一意义就是占用内存,以便 GC 日志中看到对象被回收
     */
    private byte[] bigSize = new byte[2 * _1MB];
    
    public static void testGC() {
        ReferenceCountingGC a = new ReferenceCountingGC();//对象 A
        ReferenceCountingGC b = new ReferenceCountingGC();//对象 B
        a.instance = b;
        b.instance = a;
        
        a = null;
        b = null;
        //假设在这行发生 GC,A 和 B 能否被回收?
        System.gc();
    }
    
}

这里我们先说结论,虽然 A 对象 和 B 对象互相引用,但虚拟机还是回收了它们,所以JVM 采用的不是引用计数法来管理内存的。在之后的小节再详细回答这个问题。

2. 可达性分析算法

带着上面的问题来理解这个算法,首先看张图

GC Roots

我们看图中右半部分,也许上面的问题就有了答案,虽然这三个对象互相引用,但是没有 GC Roots 引用他们,所以这三个对象还是要被回收。那么问题又来了,什么是 GC Roots 呢?

在 Java 语言中,可作为 GC Roots:
1.虚拟机栈(栈帧中的本地变量表)中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中 JNI(一般说的 Native 方法)引用的对象

清楚了 GC Roots 就可以分析上面的问题了,a 和 b 是 testGC()方法中的变量,保存在局部变量表中(虚拟机栈)中,所以 a 和 b 是 GC Roots,所以此时 A 和 B 对象是活着的,不会被回收。然后 a.instance = b,b.instance = a,instance 是 ReferenceCountingGC 对象的一个成员变量,其分配在 Java 堆中,不属于 GC Roots 范围内,所以尽管 A 和 B 对象在互相引用也无济于事,垃圾收集器还是将他们回收了。

3. 标记-清除算法 (Mark-Sweep)

算法如其名,先标记出需要回收的对象,然后一并回收被标记过的对象。这里只简单总结一下标记的过程。

我们上一节知道了如果对象没有 GCRoots 引用,就可判定为对象不可用,这也正是标记的第一步,如果对象已经被标记一次,它将被进行一次筛选,筛选条件为对象是否重写了 finalize()方法,或者 finalize()已经被虚拟机调用。当对象没重写或 finalize()已经被执行过,就证明对象已经没有可能再回到可用状态,待虚拟机第二次标记它时,就会被放到回收的集合中。

上面提到了对象还有可能回到可用状态,秘密就在 finalize()方法中,假如第一次标记后发现对象重写了 finalize(),而且没被虚拟机调用过,对象就会被加入 F-Queue 队列中,然后会有一条优先级低的 Finalizer 线程去执行队列中对象的 finalize()方法,如果对象在 finalize()中完成了自我救赎,比如把 this 指向了其他的对象,那么在 F-Queue 中的第二次标记就会将其移除出回收集合。

接下来说这个算法的不足:

  • 效率不高,标记和清除两个过程的效率都不高
  • 空间问题,标记清除之后会导致内存中出现大量的不连续的内存碎片,之后分配较大对象的内存空间时,导致没有足够的连续内存,可能会触发另一次的垃圾回收,这个问题也间接的反映出了效率不高。

知道了缺点,就应该避免,所以 JVM 并不是只用这一个算法去垃圾回收,而是在对象存活率较高的时候,使用这个算法。

4.标记-整理算法 (Mark-Compact)

这可以说是标记-清除的升级版算法,上一节中说到标记-清扫容易导致内存出现碎片,而这个算法解决了这一问题。标记-整理是在标记后,将存活的对象都向前移动,等到所有存货对象都移动到了内存空间的最前端时,再回收后面的不可用对象。

5.复制算法

为了解决效率问题,复制算法出现了,它将可用内存容量划分为大小相等的两块,每次只用其中的一块。当这块内存用完了,就将还存活的对象复制到另一块上,然后清除已使用的那块。这样每次固定回收一半内存,实现简单,运行高效,并且不会出现内存碎片。

但是这个算法的代价也很大,就是要牺牲一半的内存。

6.分代收集算法

当前商业虚拟机的垃圾收集都是采用的「分代收集算法」,主要是将堆分为新生代和老年代。
新生代:对象存活率低,每次垃圾回收后都只有少量对象存活,所以此时用复制算法。
老年代:对象存活率高,大量存活的对象,此时用标记-清除(标记-整理)算法。

由于篇幅有限,类加载相关内容将放到 JVM 篇(下)中总结

我也是个初学者,如写的有问题,请及时联系我!感谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,927评论 2 31
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,488评论 3 83
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 1,145评论 0 16
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,137评论 11 349
  • 还记得央视前几年在街头采访各种人“你幸福吗”答案有很多种,貌似一夜提高了国人的幸福感。 每个人对幸福的定义不一样,...
    阿蜜儿阅读 263评论 2 3