Android-JVM基础
GC机制
垃圾回收需要完成两件事:找到垃圾,回收垃圾。
找到垃圾一般的话有两种方法:
1、引用计数法: 当一个对象被引用时,它的引用计数器会加一,垃圾回收时会清理掉引用计数为0的对象。但这种方法有一个问题,比方说有两个对象 A 和 B,A 引用了 B,B 又引用了 A,除此之外没有别的对象引用 A 和 B,那么 A 和 B 在我们看来已经是垃圾对象,需要被回收,但它们的引用计数不为 0,没有达到回收的条件。正因为这个循环引用的问题,Java 并没有采用引用计数法。
2、可达性分析法: 我们把 Java 中对象引用的关系看做一张图,从根级对象不可达的对象会被垃圾收集器清除。根级对象一般包括 Java 虚拟机栈中的对象、本地方法栈中的对象、方法区中的静态对象和常量池中的常量。
回收垃圾的话有这么四种方法:
标记清除算法: 顾名思义分为两步,标记和清除。首先标记到需要回收的垃圾对象,然后回收掉这些垃圾对象。标记清除算法的缺点是清除垃圾对象后会造成内存的碎片化。
复制算法: 复制算法是将存活的对象复制到另一块内存区域中,并做相应的内存整理工作。复制算法的优点是可以避免内存碎片化,缺点也显而易见,它需要两倍的内存。
标记整理算法: 标记整理算法也是分两步,先标记后整理。它会标记需要回收的垃圾对象,清除掉垃圾对象后会将存活的对象压缩,避免了内存的碎片化。
分代算法: 分代算法将对象分为新生代和老年代对象。那么为什么做这样的区分呢?主要是在Java运行中会产生大量对象,这些对象的生命周期会有很大的不同,有的生命周期很长,有的甚至使用一次之后就不再使用。所以针对不同生命周期的对象采用不同的回收策略,这样可以提高GC的效率。
新生代对象分为三个区域:Eden 区和两个 Survivor 区。新创建的对象都放在 Eden区,当 Eden 区的内存达到阈值之后会触发 Minor GC,这时会将存活的对象复制到一个 Survivor 区中,这些存活对象的生命存活计数会加一。这时 Eden 区会闲置,当再一次达到阈值触发 Minor GC 时,会将Eden区和之前一个 Survivor 区中存活的对象复制到另一个 Survivor 区中,采用的是我之前提到的复制算法,同时它们的生命存活计数也会加一。这个过程会持续很多遍,直到对象的存活计数达到一定的阈值后会触发一个叫做晋升的现象:新生代的这个对象会被放置到老年代中。 老年代中的对象都是经过多次 GC 依然存活的生命周期很长的 Java 对象。当老年代的内存达到阈值后会触发 Major GC,采用的是标记整理算法。
JVM内存区域的划分,哪些区域会发生 OOM?
JVM 的内存区域可以分为两类:线程私有和区域和线程共有的区域。
线程私有的区域:程序计数器、JVM 虚拟机栈、本地方法栈
线程共有的区域:堆、方法区、运行时常量池
程序计数器。 每个线程有有一个私有的程序计数器,任何时间一个线程都只会有一个方法正在执行,也就是所谓的当前方法。程序计数器存放的就是这个当前方法的JVM指令地址。
JVM虚拟机栈。 创建线程的时候会创建线程内的虚拟机栈,栈中存放着一个个的栈帧,对应着一个个方法的调用。JVM 虚拟机栈有两种操作,分别是压栈和出站。栈帧中存放着局部变量表、方法返回值和方法的正常或异常退出的定义等等。
本地方法栈。 跟 JVM 虚拟机栈比较类似,只不过它支持的是 Native 方法。
堆。 堆是内存管理的核心区域,用来存放对象实例。几乎所有创建的对象实例都会直接分配到堆上。所以堆也是垃圾回收的主要区域,垃圾收集器会对堆有着更细的划分,最常见的就是把堆划分为新生代和老年代。方法区。方法区主要存放类的结构信息,比如静态属性和方法等等。运行时常量池。运行时常量池位于方法区中,主要存放各种常量信息。其实除了程序计数器,其他的部分都会发生 OOM。堆。 通常发生的 OOM 都会发生在堆中,最常见的可能导致 OOM 的原因就是内存泄漏。JVM虚拟机栈和本地方法栈。 当我们写一个递归方法,这个递归方法没有循环终止条件,最终会导致 StackOverflow 的错误。当然,如果栈空间扩展失败,也是会发生 OOM 的。方法区。方法区现在基本上不太会发生 OOM,但在早期内存中加载的类信息过多的情况下也是会发生 OOM 的。
类加载过程Java 中类加载分为 3 个步骤:加载、链接、初始化。
加载。
加载是将字节码数据从不同的数据源读取到JVM内存,并映射为 JVM 认可的数据结构,也就是 Class 对象的过程。数据源可以是 Jar 文件、Class 文件等等。如果数据的格式并不是 ClassFile 的结构,则会报 ClassFormatError。
链接。链接是类加载的核心部分,这一步分为 3 个步骤:验证、准备、解析。
验证。 验证是保证JVM安全的重要步骤。JVM需要校验字节信息是否符合规范,避免恶意信息和不规范数据危害JVM运行安全。如果验证出错,则会报VerifyError。
准备。 这一步会创建静态变量,并为静态变量开辟内存空间。
解析。 这一步会将符号引用替换为直接引用。
初始化。 初始化会为静态变量赋值,并执行静态代码块中的逻辑。
双亲委派模型类加载器大致分为3类:启动类加载器、扩展类加载器、应用程序类加载器。
启动类加载器主要加载 jre/lib下的jar文件。
扩展类加载器主要加载 jre/lib/ext 下的jar文件。
应用程序类加载器主要加载 classpath 下的文件。
所谓的双亲委派模型就是当加载一个类时,会优先使用父类加载器加载,当父类加载器无法加载时才会使用子类加载器去加载。这么做的目的是为了避免类的重复加载。
https://juejin.cn/post/6844904136937324552
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!