在 Java 中可以作为GC Roots 的对象主要有以下几种:
1.Java 栈中引用对象。
2.本地方法栈中 JNI 引用的对象。
3.方法区中运行时常量池引用的对象。
4.方法区中静态属性引用的对象。
5.运行中的线程
6.由引导类加载器加载的对象
7.GC控制的对象
顺便说一下,内存泄漏和内存溢出的区别(OOM)
系统已经不能再分配出你所需要的空间,比如你需要100M的空间,系统只剩90M了,这就叫内存溢出,比如大图片加载。
内存泄漏:指我们创建开辟了一块内存区域(如创建了一个对象),这个对象已经不使用了,但是其他对象还持有他的引用,导致他不能被回收,这时候就会内存泄漏。
现在 GC 都是可达性算法,当一个对象没有指向 GcRoot 的引用链时,这个对象可以被回收。
堆、方法区
方法区和堆一样,是各个线程共享的内存区域,方法区用来存储已被虚拟机加载的类信息、常量、静态变量、及时编译器比以后的戴拿等数据。
堆是用来存放对象实例的。几乎所有的对象实例都在这里分配内存。官方描述:所有的对象实例以及数组都要在堆上分配。
运行时的常量池是方法区的一部分。
class文件中除了有类的 版本、字段、方法、接口等信息外,还有一项信息就是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中的存放。
Java虚拟机栈
Java虚拟机栈也是线程私有得,它的生命周期与线程相同。
虚拟机栈描述的是 Java 方法执行的内存模型。
每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
每一个方法从调用到完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
程序计数器
程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
程序计数器是线程私有的。
如果线程执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果执行的是Native方法,这个计数器值则为空。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMenoryError情况的区域。
判断一个类是否“无用”,则需同时满足三个条件:
(1)、该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例。
(2)、加载该类的ClassLoader已经被回收。
(3)、该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
虚拟机可以对满足上述3个条件的无用类进行回收,这里说的是可以回收而不是必然回收。
内存优化
使用软引用、弱引用,内存不够的时候会自动回收
不使用的对象置位null,便于回收
上下文(context)的合理使用,非必要尽量使用Application的上下文,弹窗(dialog)必须得使用activity的上下文
合理的创建变量
持有资源对象的引用时,最好采用弱引用
非静态内部类(包括匿名内部类)默认就会持有外部类的引用,当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。
非静态内部类导致的内存泄露在Android开发中有一种典型的场景就是使用Handler,很多开发者在使用Handler是这样写的:
资源及时释放(数据库,广播,流等等)
Java 的回收机制会自动帮你回收不再被引用的对象,你说的问题里面(A对象里面包含B对象)当A被销毁的时候,B要不要销毁取决于系统里面还有没有引用了B对象的变量,如果有的话则B不会被销毁,否则B也会被回收的