android虚拟机模糊,Android虚拟机的几个问题探究

0x01 前言

这里说的Android虚拟机指运行在Android平台上的虚拟机,即日常遇到的Dalvik和ART虚拟机。这篇文章记录了自己对Android虚拟机几个问题的理解。只是个人学习和理解过程的记录,如有不当之处万望指正,邮箱yangfan3687@163.com。也希望在面对下面这些所谓JVM常见问题时能给你带来不一样的思考。

问题1:如何理解JVM内存模型?

问题2:什么是GC ROOTS?

问题3:Android虚拟机有没有分代回收?

0x02 堆转储HPROF协议

在看JVM内存模型时,不妨先了解一下堆转储文件的协议。通过HPROF的问题格式可以大致了解JVM在内存中的划分情况。

2.1 HPROF文件格式

总的来说,HPROF文件分为两个大的部分,分别是Hprof Header和Hprof Body。其中Header部分又包括以下几个部分:

fixed header:包含文件描述以’/0’结尾,id字段的长度信息等

string table:包含所有用到的字符串,包括类目、方法名、常量名等。

class table:包含所有的类信息

stack frame:包含所有线程的栈帧信息

stack trace:包含所有线程的虚拟机栈情况

Body部分就是完整的jvm堆信息,将堆上的对象引用状况表述到文件中。其中在表述类对象是静态方法和静态成员变量时稍复杂,详细格式请查看 3.2 ~ 3.4。以下是HPROF文件格式的详细说明:

“JAVA PROFILE 1.0.3/0”(Magic Code)

4byte mIdSize (储存id的字节长度)

8byte (Time Stamp)

0x01(string table)

4 byte

4 byte(字符串length)

mIdSize byte

strlen(length) byte

……

……

……

……

……

0x02(class table)

4 byte

4 byte (length)

4byte(class serial number)

mIdSize byte

4 byte(Stack trace serial number)

4 byte(class name string id)

……

……

……

……

……

0x04(stack frame)

4 byte

4 byte (length)

mIdSize byte

mIdSize byte (methodName string id)

mIdSize byte (methodSignature string id)

mIdSize byte (sourceFile string id )

mIdSize byte (serial)

mIdSize byte (lineNumber )

……

……

……

……

……

0x05(stack trace)

4 byte

4 byte (length)

4 byte serialNumber

4 byte threadSerialNumber

4 byte numFrames

numFrames * mIdSize byte stack frame id

……

……

……

……

……

0x0C(HEAP DUMP)

4 byte

mIdSize byte (length)

0xFF(ROOT_UNKNOWN)

mIdSize byte string id

0x01(ROOT_JNI_GLOBAL)

mIdSize byte string id

mIdSize byte jni global id

0x02(ROOT_JNI_LOCAL)

mIdSize byte string id

4 byte thread serial number

4 byte stack frame number

0x03(ROOT_JAVA_FRAME)

mIdSize byte string id

4 byte thread serial number

4 byte stack frame number

0x04(ROOT_NATIVE_STACK)

mIdSize byte string id

4 thread serial number

0x05(ROOT_STICKY_CLASS)

mIdSize byte string id

0x06(ROOT_THREAD_BLOCK)

mIdSize byte string id

4 thread serial number

0x07(ROOT_MONITOR_USED)

mIdSize byte string id

0x08(ROOT_THREAD_OBJECT)

mIdSize byte string id

4 byte thread serial number

4 byte stackSerialNumber

0x20(ROOT_CLASS_DUMP)

mIdSize byte string id

0x21(ROOT_INSTANCE_DUMP)

mIdSize byte string id

mIdSize byte stackId

mIdSize byte class id

4 byte remaining

0x22(ROOT_OBJECT_ARRAY_DUMP)

mIdSize byte string id

mIdSize byte stack id

num elements

mIdSize byte class id

mIdSize * num elements(skip)

0x23(ROOT_PRIMITIVE_ARRAY_DUMP)

mIdSize byte string id

mIdSize byte stack id

4 byte num elements

4 byte primitive type

mIdSize * num elements(skip)

0xC3(ROOT_PRIMITIVE_ARRAY_NODATA)

mIdSize byte string id

mIdSize byte stack id

4 byte num elements

4 byte primitive type

4 * num elements(skip)

0xfe(ROOT_HEAP_DUMP_INFO)

mIdSize byte heap id

mIdSize byte heap name id(string id)

0x89(ROOT_INTERNED_STRING)

mIdSize byte string id

0x8a(ROOT_FINALIZING)

mIdSize byte string id

0x8b(ROOT_DEBUGGER)

mIdSize byte string id

0x8c(ROOT_REFERENCE_CLEANUP)

mIdSize byte string id

0x8d(ROOT_VM_INTERNAL)

mIdSize byte string id

0x8e(ROOT_JNI_MONITOR)

mIdSize byte string id

4 byte thread serial number

4 byte stack frame number

0x90(ROOT_UNREACHABLE)

mIdSize byte string id

0x1C(HEAP DUMP SEGMENT)

4 byte

mIdSize byte (length)

2.2 ROOT_CLASS_DUMP的格式0x20(ROOT_CLASS_DUMP)

1

id

4

stack serial number

4

super class id

4

class loader id

4

signeres id

4

protection domain id

4

reserved

4

reserved

4

instance size

4

const pool num entries

2

2 * num entries

static fields num entries

2

static fields

static fields num entries * (static fields),下面会再单独列出来

instance fields num entries

2

instance fields

instance fields num entries * (instance fields)

2.3 0x20(ROOT_CLASS_DUMP).Static Fields4

1

4

static fields id

static fields type

type size

2.4 0x20(ROOT_CLASS_DUMP).Instance Fields4

1

instance id

instance type

2.5 HEAP DUMP和HEAP DUMP SEGMENT的区别

如果你仔细研究了上面的内容的话,可能你就会有这样一个问题,HEAP DUMP和HEAP DUMP SEGMENT有什么区别?为什么要有两个标记?

其实堆转储的hprof文件格式中,原本是使用4字节32位存储堆对象的 “HEAP DUMP” (0x0C)的区块长度,但同时也就限制了HEAP DUMP的大小必须在4GB以内。在出现这个问题的情况下,在HPROF文件中新增了”HEAP DUMP SEGMENT” (0x1C)的格式,用来将超过4GB的JVM堆对象信息分别存储到文件的多个区块中。

0x03 理解JVM内存模型

f335ffc72e4f888e0066357bdae6d802.png

程序计数器

在一个确定的时刻都只会执行一条线程中的指令,一条线程中有多个指令,为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。如果线程执行的是个java方法,那么计数器记录虚拟机字节码指令的地址。如果为native【底层方法】,那么计数器为空。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。

Java虚拟机栈

线程私有,每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。

本地方法栈

线程私有,本地方法栈的功能和特点类似于虚拟机栈,不同的是,本地方法栈服务的对象是JVM执行的native方法,而虚拟机栈服务的是JVM执行的java方法。我们常见的HotSpot虚拟机选择合并了虚拟机栈和本地方法栈。

Java堆

所有线程共享的内存区域,所有对象实例及数组都要在堆上分配内存。

方法区

所有线程共享的内存区域,为了区分堆,又被称为非堆。用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。

在谈到JVM内存结构时,从HPROF文件的header和body部分就有体现,从数据上划分结构就可以分为方法区(包括运行时常量池,也就是header中string table的部分)、java堆(body部分)、虚拟机栈、本地方法栈和程序计数器。其中方法区和java堆部分是所有线程共享的数据区,其他则为线程独有的数据区。为什么会有这样的结构划分和设计?

先看设计内存的目的,是因为随着CPU运转速度越来越快,磁盘远远跟不上CPU的读写速度,才设计了内存。但是随着CPU的发展,内存的读写速度也远远跟不上CPU的读写速度,为了解决这一纠纷,CPU厂商在每颗CPU上加入了高速缓存来缓解这种症状。基于高速缓存的存储交互很好的解决了处理器与内存之间的矛盾,也引入了新的问题:缓存一致性问题。在多处理器系统中,每个处理器有自己的高速缓存,而他们又共享同一块内存。回到JVM的内存模型中, Java中通过多线程机制使得多个任务同时执行处理,所有的线程共享JVM内存区域main memory,而每个线程又单独的有自己的工作内存,当线程与内存区域进行交互时,数据从主存拷贝到工作内存,进而交由线程处理。这样也就说的通了,为啥在JVM中有的区域是线程共享的,有的区域是线程独享的。

0x04 什么是GC和GC_ROOTS?

4.1 什么是GC

垃圾回收(Garbage Collection,简称GC),是垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。这种机制也不单单只有Java虚拟机才有,ObjectC和C#都有自己相应的垃圾回收机制,垃圾回收机制是帮助程序员自动管理对象内存空间的机制。

4.2 什么是GC ROOTS

常见的垃圾回收方式,引用计数算法和根搜索算法。

引用计数就像是每个对象有个账本,当一个对象被另一个对象引用时该对象的引用计数器+1,当引用失效时引用计数器-1,任何引用计数为0的对象可以被当作垃圾收集。引用计数有一个先天的缺陷,那就是多个对象相互持有引用形成一个引用环是,那么环中的所有对象引用计数都不为0,这时这些对象都不能被垃圾回收器回收。

根节点搜索指的是从根节点集合出发找到所有引用链可达对象,当一个对象到根节点集合(GC ROOTS)没有任何引用链存在时就证明此对象是不可用的。从对比JVM规范的垃圾回收根节点(来自:Garbage Collection Roots)、HPROF文件协议中的GC ROOTS tag类型和square haha库源码中对GC ROOTS的类型定义,参照下表。

JVM规范名称

HPROF中的TAG

haha库中的RootType枚举类型

描述

System Class

0x05

RootType.SYSTEM_CLASS

被bootstrap/system class加载器加载的类,例如所有rt.jar中包名为 java.util.*的类

JNI Local

0x02

RootType.NATIVE_LOCAL

native代码中的本地变量,例如user defined JNI code or JVM internal code

JNI Global

0x01

RootType.NATIVE_STATIC

native中的全局变量,例如user defined JNI code or JVM internal code

Thread Block

0x06

RootType.THREAD_BLOCK

Thread

Busy Monitor

0x07

RootType.BUSY_MONITOR

所有调用 wait()、 notify()方法的, 或者同步的。For example, by calling synchronized(Object) or by entering a synchronized method. Static method means class, non-static method means object.

Java Local

0x03

RootType.JAVA_LOCAL

本地变量,例如线程栈帧中的参数和方法

Native Stack

0x04

RootType.NATIVE_STACK

In or out parameters in native code, such as user defined JNI code or JVM internal code. This is often the case as many methods have native parts and the objects handled as method parameters become GC roots. For example, parameters used for file/network I/O methods or reflection

Finalizable

0x8a

RootType.FINALIZING

在finalizer等待队列里的对象

Unfinalized

Unreachable

0x90

RootType.UNREACHABLE

从其他根节点都无法到达的对象

Java Stack Frame

0x03

RootType.JAVA_LOCAL

A Java stack frame, holding local variables. Only generated when the dump is parsed with the preference set to treat Java stack frames as objects.

Unknown

0xff

RootType.UNKNOWN

0x89

RootType.INTERNED_STRING

0x8b

RootType.DEBUGGER

0x8c

RootType.REFERENCE_CLEANUP

0x8d

RootType.VM_INTERNAL

0x8e

RootType.NATIVE_MONITOR

对照上面这个表总结起来,所谓JVM GC Roots是进行垃圾回收时根节点的集合。大致包含以下几个方面:

所有Java线程当前活跃的栈帧所指向GC堆里的对象的引用,换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。

VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。

所有当前被加载的Java类

JNI handles,包括global handles和local handles

Java类的引用类型静态变量

Java类的运行时常量池里的引用类型常量(String或Class类型)

String常量池(StringTable)里的引用

按照根搜索GC的思想,从根节点出发的找到的对象就被认定为存活的,其他的对象都是“无用的”,但是GC ROOTS的集合不应该是一成不变的,特别是面对分代GC时。为啥这样说呢?分代GC是一种部分收集(partial collection)的做法。在执行部分收集时,从GC堆的非收集部分指向收集部分的引用,也必须作为GC roots的一部分。具体到分两代的分代式GC来说,如果第0代叫做young gen,第1代叫做old gen,那么如果有minor GC / young GC只收集young gen里的垃圾,则young gen属于“收集部分”,而old gen属于“非收集部分”,那么从old gen指向young gen的引用就必须作为minor GC / young GC的GC ROOTS的一部分。所以针对一次GC来说,GC ROOTS的类型类型范围未必只有jvm规范定义中所列举的那几种情况。

0x05 Android虚拟机有分代GC吗?

Android Q开始google才为ART虚拟机添加分代收集机制。

参考文档

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值