java虚拟机之内存模型理解

前言

     时间过得真快,眨眼间已经毕业一年了,最近经常性的反思自己,发现这一年里竟然没什么让自己印象很深刻的东西,工作上了解了业务背景后,也没遇过一些对自己进步有帮助的难题之类的。仔细想想,在个人能力还是很初级水平的阶段,这样的处境是很危险的。所以最近会多做一些基础原理的知识整合,包括JVM、Java、Android方法的内容,一边扫盲一边加深理解。​

一. jvm运行时内存的划分

JVM的内存模型结构如下:


JVM内存区域模型

按照是否线程共享

  • 线程共享:方法区、堆
  • 线程私有 :程序计数器,虚拟机栈,本地方法区

按照实际存储的内容

  • 数据区:方法区,堆
  • 指令区:程序计数器,虚拟机栈,本地方法区

看到这个结构,也能想的明白,用户数据(非指令,指令是和具体数据无关的,用来处理数据的、做计算 工作的操作数)是多线程共享的,如果说某些数据只能某个线程自己可见,那么多线程也就失去了意义。

1.运行时内存区域划分

1.1 程序计数器(Program Counter Register)-- 指令指针

  • 1.1 作用
    jvm中一块比较小的内存区域,指示当前字节码执行到哪一行,字节码的解释工作需要这个计数器来选取下一条被执行字节码指令,分支、循环、跳转、异常处理、线程恢复类的功能都要程序计数器来完成。
  • 1.2 多线程
    考虑在多线程情况下,这个区域的内存是线程私有的。
  • 1.3 异常
    计数器记录的是正在执行的虚拟机字节码指令的地址,假如这个区域内存泄漏,那么整个字节码的执行也就会无法进行,这种状态下,程序的异常处理,只要是程序层面上也都没法进行了。

1.2 虚拟机栈 -- 记录方法信息和调用栈(函数调用栈,栈内每一个元素称之为栈帧,栈帧存储着栈帧信息,用javap命令查看栈帧信息进行理解)

  • 描述的是方法执行的内存模型,每个方法在执行的同时会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口信息。
    每一个方法从调用开始,就会创建一个虚拟机栈,方法结束调用栈也就会回收,响应的方法内的局部变量也都会被回收。
    当调用栈过长,超过虚拟机定义的最大长度时,会抛出StackOverflowError,常见的情况就是定义了无递归出口的方法,会导致栈溢出
  • 局部变量表:存放编译期就能知道的类型,包括基本数据类型和对象引用,所需的内存空间大小也在编译期完成分配。也就是说进入一个方法时就可以确定,他在栈帧中需要占用多少内存空间,并且在方法运行期间局部变量表的大小不会再被改变。
  • 操作数栈:函数在调用发起和结构本质上是其实入栈和出栈的动作。
  • 动态链接:对多态支持的关键。
  • 方法返回地址:记录方法调用结束的地址,即使是void类型的方法也有返回地址。
    虚拟机栈的结构示意图如下:
    虚拟机栈结构示意图

1.3 方法区

存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。内存回收在这个区域和堆区是一样的规则。存储Class信息

  • 运行时常量区
    属于方法区的一部分,存储编译期生成的各种字面量和符号引用,是在类加载后进入到方法区的运行时常量池中。

存储Class文件中的符号引用和直接引用。

  • Class文件常量池
  • 运行时常量池

1.4 本地方法区

和虚拟机栈的作用类似,虚拟机栈是为Java字节码提供服务支撑的,本地方法栈是为Native方法提供服务。会有StackOverflowError和OutOfMemoryError异常。
虚拟机规范并没有限制本地方法区使用什么语言,使用方法和数据结构,所以不同的虚拟机可以自己去实现这部分区域。注:Sun HotSpot虚拟机是将本地方法区和虚拟机栈合在一起了。

1.5 堆区

存放对象的实例,对象实例分配内存几乎都在这里进行。对象和数组都是在堆上分配内存的。是所有线程共享的区域,垃圾进行回收也主要在堆中进行,也被称为“GC堆”。

  • Java堆还可以分为新生代和老年代
  • 新生代:(2/3)默认比例eden:s0:s1=8:1:1
  • 老年代:(1/3)
  • 永久代:

2. 直接内存区

不是Java虚拟机规范中定义的内存区域,但是这部分内存被频繁的使用,并且内存不足时也有会有OutOfMemoryError异常出现。
直接内存的分配不受Java堆大小的限制。只受本机内存大小以及处理器寻址空间的限制。

二. 对象的四种引用类型

1.强引用

Object strongRef = new Object(); 

程序运行过程中就不会被回收, 当内存不足时会抛出OutOfMemoryError错误, 也不会选择回收强引用对象来缓解内存不足, 一般这种强引用从逻辑上确定是可以回收了,可以赋值为null, 提高被GC回收的可能性.

strongRef = null; //注意:并不是这样做了就立马被回收,只是能被回收了,具体回收的时机由GC决定

2.软引用: SoftReference

  • 2.1 软引用特性

如果一个对象只有软引用, 只要内存足够, 就不会回收它. 如果内存不足了, 就会回收这些只有软引用的对象. 可以看出软引用的回收时机是由内存的使用情况决定的.

软引用在内存充足的时候对象不会被回收, 也就是说内存充足的情况下, 即使对象只有软引用, 对象也不会被回收. 所以软引用也能解决内存泄露问题, 但是相对来说对象回收的周期更长, 因为某个对象仅有软引用时, 要等到内存不足才会被回收.

软引用能感知内存不足的发生, 所以适用于内存敏感的使用场景.

  • 2.2 软引用和引用队列的结合使用
    首先看SoftReference的构造方法, 有两个:
public SoftReference(T referent) {
        super(referent);
        this.timestamp = clock;
}

 /**
     *  当软引用对用的对象被回收了,就会将这个软引用对象加入到队列中
     */
    public SoftReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
        this.timestamp = clock;
    }

使用方式:

ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
String str = new String("abcd");
SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);
str = null;
System.gc();
System.out.println(softReference.get());
Reference<? extends String> reference = referenceQueue.poll();
System.out.println(reference);

3.弱引用: WeakReference

对象只有弱引用, 不会导致对象在被回收时, 因为还有引用关系不能被GC回收. 相反, 如果GC在扫描对象只有弱引用时, 不管内存是否充足, 都会回收该对象内存. 但是GC的线程优先级很低, 不会很快扫描到这种情况的对象引用.

和软引用相比, 若引用的生命周期很短暂.

弱引用也可结合引用队列使用:

public WeakReference(T referent) {
        super(referent);
}
// 当弱引用对应的对象的被回收了,jvm会将该弱引用加入到引用队列中
public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
}

使用:

 Object object = new Object();
ReferenceQueue<Object> weakReferenceQueue = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<>(object, weakReferenceQueue);

4.虚引用: PhantomReference

虚引用不会决定对象的生命周期, 如果一个对象只有虚引用, 那么它在任何时候都有可能被回收. 虚引用必须和一个引用队列联合使用.

和软引用,弱引用不同的是虚引用必须和引用结合使用, 可以看到它只有一个构造方法:

public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
}

使用:

Object phantomRefObj = new Object();
ReferenceQueue<Object> phantomReferenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(phantomRefObj, phantomReferenceQueue);
System.out.println(phantomReference.get()); //null

一般不用虚引用来做实际的工作, 可以用来跟踪垃圾回收器的活动.

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