深入理解Java虚拟机——自动内存管理机制(图文版)

(注: 本文中所指虚拟机均为HotSpot虚拟机)

一、Java虚拟机运行时数据

虚拟机结构图


1. 程序计数器

(1)线程私有
(2)可看作当前线程执行的虚拟机字节码指令的行号指示器
(3)执行Java方法时值为正在执行的字节码地址;执行Native方法时值为
(4)无OutOfMemoryError情况。


2. Java虚拟机栈

(1)线程私有
(2)存放局部变量表、操作数栈、动态链接、方法出口等信息。
(3)方法调用对应着栈帧的入栈、出栈
(4)栈深度超过虚拟机允许的深度——StackOverflowError异常;动态扩展无法申请到足够的内存——OutOfMemoryError异常。

注:

  • 局部变量表:
    存放方法参数和局部变量。
  • 操作数栈:
    计算过程的临时存储空间。
  • 动态链接:
    存储在运行期间会转换为直接引用的符号。
  • 方法出口:
    包括正常返回出口和异常返回出口。

3. 本地方法栈

作用同虚拟机栈。


4. Java堆

(1)线程公有
(2)存放对象实例
(3)垃圾收集器主要管理区域。
(4)堆无法再扩展时——OutOfMemoryError异常。


5. 方法区

(1)线程公有
(2)存储类信息、常量、静态变量、即时编译器编译后的代码
(3)无法满足内存分配需求——OutOfMemoryError异常。
(4)包括运行时常量池

  • 运行时常量池:
    存储编译期生成的各种字面量和符号引用,运行期间也可以将新的常量放入池中。会抛出OutMemoryError的异常。

二、Java对象探秘

1. 对象的创建

(1)遇到new关键字。
(2)检查参数(即new后面紧跟的类名)是否能在常量池中定位一个类的符号引用。
(3)如果能定位,检查类是否已经被加载、解析和初始化过。没有加载过则加载。
(4)检查通过后为新对象分配内存(从堆中划分)。
(5)虚拟机将分配的内存空间初始化为0(不含对象头)。
(6)将对象信息放入对象头中。
(7)调用构造函数或初始化文件对新对象进行初始化。


2. 对象的内存布局

对象结构图

  • Mark Word:包括哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。

3. 对象的访问定位

(1)使用句柄
在这里插入图片描述

(2)直接指针在这里插入图片描述


三、垃圾收集算法

1. 引用计数算法

基本思想:给对象中添加一个引用计数器。每引用这个对象一次计数器就加一;每引用失效一次就减一。

非常简单的一种算法,即高效又便于实现。但是很可惜,这种算法有一种天然的缺陷:当两个对象相互引用时,两个对象都无法被回收。

A a = new A();
B b = new B();
a.friend = b;
b.friend = a;
a = null;
b = null;
System.gc();

从上述例子中我们可以看出,对象a和对象b都已经不再使用,但是他们中的相互引用并没有失效,即a.friend ≠ null,因此双方的计数器都不会为0,从而导致两个对象均无法回收。


2. 可达性分析算法

基本思想:从“GC Roots”开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC roots没有任何引用链时,就认定其失效。

补充:可以作为GC Roots的对象

  1. 虚拟机栈中的本地变量表中引用的对象。
  2. 方法区类静态属性引用的对象。
  3. 方法区常量引用的对象。
  4. 本地方法栈Native方法中引用的对象。

3. 对象的最后一次自救机会

在被回收以前,对象还有最后一次拯救自己的机会

众所周知,Java类在被回收前会触发一次finalize函数,如果在此函数运行时,对象能成功呼叫一名外援(引用链上的任何一个对象),并使其成功引用自己,这个对象就可以“死里逃生”(当然触发过的就不会再次触发了,这也意味着自救的小把戏只能用一次)。

@Override
protected void finalize throws Throwable{
	super.finalize();
	// 某个引用链上的对象引用一下这个类
}

4. 回收方法区

主要回收内容:废弃常量和无用的类。

  1. 废弃常量:没有任何对象引用常量就回收。
  2. 无用的类
    (1)类的所有实例被回收。
    (2)加载类的ClassLoader已回收。
    (3)对应的java.lang.Class对象没有被引用,并且无反射访问此类的方法。
    同时满足上述三个条件就会被回收。

5. 垃圾收集算法

  1. 标记清除算法:标记所有需要回收的对象,标记完成后回收。
    缺点:
    (1)效率低。
    (2)产生大量不连续内存碎片。
  2. 复制算法:将可用容量分为相等的两块,一块用完就让依旧存活的对象搬家到另一块上,然后将原内存块全部回收。
    缺点:内存利用率低。

    注: 复制算法升级版
    IBM研究表明,新生代中的对象98%是“朝生夕死”的。因此可以将内存划分比例调整为8:1:1 (Eden:Survivor1:Survivor2)。
    使用方法:每次使用一个Eden一个Survivor,回收时将存活的对象搬家到另一个Survivor上,然后将已使用的Eden和Survivor清空。如果Survivor空间不足,则需要老年代等其他内存进行分配担保(空间不足时,利用担保者的内存进行存储)。
  3. 标记-整理算法:标记所有需要回收的对象,标记完成后让存活对象向一端移动,然后清除其他内存。
  4. 分代收集算法:根据对象存活周期将内存分为不同几块,然后在块上再采用最适合的收集算法。

四、垃圾收集器

1.Serial收集器

特点:进行垃圾收集时必须暂停其他所有的工作线程。

2.ParNew收集器

特点:Serial收集器的多线程版本。

3.Parallel Scavenge收集器

特点:达到一个可控制的吞吐量。

注:吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间)

4.Serial Old收集器

特点:Serial的老年代版本。

5.Parallel Old收集器

特点:Parallel Scavenge的老年代版本。

6.CMS收集器

特点:重视服务响应速度希望系统停顿时间最短。基于“标记——清除”算法实现。

7.G1收集器

特点:并发、分代收集、空间整合、可预测的停顿。


五、内存分配与回收策略

1.对象优先在Eden上分配

2.大对象直接进入老年代

3.长期存活的对象进入老年代

4.动态对象年龄判定

5.空间分配担保

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

羽路星尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值