目录
一. 垃圾回收的优点和原理. 并考虑两种回收机制
二. 垃圾回收器的基本原理是什么? 垃圾回收器可以马上回收内存吗? 有什么办法主动通知虚拟机进行垃圾回收?
三. Java 中会存在内存泄露嘛? 请简单描述
四.Ststem.gc() 和 Runtime.gc() 会做什么事情
五. finalize() 方法是什么时候被调用? 析构函数(finalization) 的目的是什么
六. 什么是分布式垃圾回收(DGC)? 它是如何工作的?
七. 串行(serial) 收集器和吞吐量(throughput) 收集器的区别是什么
八. 简述 Java 内存分配与回收频率以及 Minor GC 和 Major GC
九. JVM 的永久代中会发生垃圾回收么
十. Java 中垃圾收集的方法有哪些
十一. 什么是类加载器, 类加载器有哪些
十二. 类加载器双亲委派模型机制
Java语言中有一个显著的特点就是引入了垃圾回收机制, 使 C++ 程序员最头疼的内存管理的问题迎刃而解. 它使得 Java 程序员在编写程序的时候不需要考虑内存管理. 由于有个垃圾回收机制, Java 中的对象不再有 "作用域" 的概念, 只有对象的引用才有 "作用域". 垃圾回收可以有效的防止内存泄漏, 有效的使用可以使用的内存. 垃圾回收器通常是作为一个单独的低级别的线程运行, 不可预支的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收, 程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收.
对于 GC 来说, 当程序员创建对象时, GC 就开始监控这个对象的地址、大小以及使用情况. 通常, GC 采用有向图的方式记录和管理堆(heap) 中的所有对象. 通过这种方式确定哪些对象是"可达的", 哪些对象是"不可达的". 当 GC 确定一些对象为 "不可达" 时, GC 就有责任回收这些内存空间. 程序员可以手动执行 System.gc() 通知 GC 运行, 但是 Java 语言规范不保证 GC 一定会执行.
所谓内存泄漏就是指一个不再被程序使用的对象或变量一直被占据在内存中. Java 中有垃圾回收机制, 它可以保证一对象不再被引用的时候将自动被垃圾回收器从内存中清除掉. 由于 Java 使用有向图的方式进行垃圾回收管理, 可以消除引用循环的问题.
Java 中的内存泄露的情况: 长周期的对象持有短生命周期对象的引用就很可能发生内存泄漏, 尽管短生命周期对象已经不再需要, 但是长生命周期对象持有它的引用而导致不能回收, 这就是 Java 中内存泄漏的发生场景. 通俗的说, 就是程序员可能创建了一个对象, 以后一直不在使用这个对象, 这个对象却一直被引用, 即这个对象无用但是却无法被垃圾回收器回收, 这就是 Java 可能出现内存泄漏的情况, 例如: 缓存系统, 我们加载了一个对象放在缓存中(例如放在一个全局 map 对象中), 然后一直不再使用它, 这个对象一直被缓存引用, 但却不再被使用.
检查 Java 中的内存泄漏, 一定要让程序将各种分支情况都完整执行到程序结束, 然后看某个对象是否被使用过, 如果没有, 则才能判定这个对象属于内存泄漏.
如果一个外部类的实例对象的方法返回了一个内部类的实例对象, 这个内部类对象被长期引用了, 即使那个外部类实例对象不再被使用, 但由于内部类持有外部类的实例对象, 这个外部类对象将不会被垃圾回收, 这也会造成内存泄漏.
这两个方法用来提示 JVM 要进行垃圾回收. 但是立即开始还是延迟进行垃圾回收是取决于 JVM 的.
GC 决定回收某对象时, 就会运行该对象 finalize() 方法, 但是在 Java 中很不幸, 如果内存总是充足的, 那么垃圾回收永远不可能进行, 也就是说 finalize() 可能永远不会被执行, 显然指望它做收尾工作是靠不住的. 那么 finalize() 最主要的用途就是回收特殊渠道申请的内存. Java 程序有垃圾回收器, 所以一般情况下内存问题不用程序员操心. 但是有一种 JNI (Java Native Interface) 调用 non-Java 程序(C 或 C++), finalize() 的工作就是回收这部分的内存.
RMI 使用 DGC 来做自动垃圾回收. 因为 RMI 包含了跨虚拟机的远程对象的引用, 垃圾回收是很困难的. DGC 使用引用技术算法来给远程对象提供自动内存管理.
吞吐量收集器使用的并行版本的新生代垃圾收集器, 它用于中等规模和大规模数据的应用程序. 而串行收集器对大多数的小应用(在现代处理器上需要大概 100M 左右的内存) 就足够了
当 Eden 区没有足够的空间进行分配时, 虚拟机会执行一次 Minor GC. Minor GC 通常发生在新生代的 Eden 区, 在这个区的对象生存期短, 往往发生 GC 的频率较高, 回收速度比较快; Full GC/Major GC 发生在老年代, 但是通过配置, 可以在 Full GC 之前进行一次 Minor GC 这样可以加快老年代的回收速度.
垃圾回收不会发生在永久代, 如果永久代满了或者超过了临界值, 会触发完全垃圾回收(Full GC)
注: Java8 中完全移除了永久代, 新加了一个叫做元数据区的 native 内存区
标记-清除:
这是垃圾收集算法中最基础的, 根据名字九可以知道, 它的思想就是标记哪些要被回收的对象, 然后统一回收. 这种方法很简单, 但是主要会有两个主要问题:
复制算法:
为了解决效率问题, 复制算法将可用内存按容量划分为相等的两部分, 然后每次只使用其中的一块, 当一块内存用完时, 就将还存活的对象复制到第二块内存上, 然后一次性清除完第一块内存, 再将第二块上的对象复制到第一块. 但是这种方式, 内存的代价太高, 每次基本上都要浪费内存.
于是将该算法进行了改进, 内存区域不再是按照 1:1 去划分, 而是将内存划分为 8:1:1 三部分, 较大那份内存存交 Eden 区, 其余是两块较小的内存区叫做 Survior 区. 每次都会优先使用 Eden 区, 若 Eden 区满, 就将对象复制到第二块内存上, 然后清除 Eden 区, 如果此时存活的对象太多, 以至于 Survivor 不够时, 就将这些对象通过分配担保机制复制到老年代中.
标记-整理:
该算法主要是为了解决标记-清除产生的大量内存碎片问题; 当对象存活率比较高时, 也解决了复制算法的效率问题. 它的不同之处就是在清除对象时候先将可回收对象移动到右端, 不可清除对象移动到左端, 随即清除掉右端的对象, 这样就不会产生内存碎片了. 但缺点是整理阶段移动了可用对象, 需要去更新引用
分代收集:
现在的虚拟机垃圾收集大多采用这种方式, 它根据对象的生存周期, 将堆分为新生代和老年代. 在新生代中, 由于对象生存周期短, 每次回收会有大量对象死去, 那么这时就采用复制算法. 老年代里的对象存活率高, 回收频率低, 采用标记-清除算法或标记-整理算法
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器.
主要有以下四种类加载器:
当一个类收到了类加载请求时, 不会自己先去加载这个类, 而是将其委派给父类, 由父类去加载, 如果此时父类不能加载, 反馈给子类, 由子类去完成类的加载.