- public static void main(String[] args) throws IOException {
- // TODO Auto-generated method stub
- try {
- gcTest();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println("has exited gcTest!");
- System.in.read();
- System.in.read();
- System.out.println("out begin gc!");
- for(int i=0;i<100;i++){
- System.gc();
- System.in.read();
- System.in.read();
- }
- }
- private static void gcTest() throws IOException {
- System.in.read();
- System.in.read();
- Person p1 = new Person();
- System.in.read();
- System.in.read();
- Person p2 = new Person();
- p1.setMate(p2);
- p2.setMate(p1);
- System.out.println("before exit gctest!");
- System.in.read();
- System.in.read();
- System.gc();
- System.out.println("exit gctest!");
- }
- private static class Person{
- byte[] data = new byte[20000000];
- Person mate = null;
- public void setMate(Person other){
- mate = other;
- }
- }
- }
Java 中的内存泄露的情况:
长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露, 尽管短生命周期对象已经不再需要, 但是因为长生命周期对象持有它的引用而导致不能被回收, 这就是 Java 中内存泄露的发生场景, 通俗地说, 就是程序员可能创建了一个对象, 以后一直不再使用这个对象, 这个对象却一直被引用, 即这个对象无用但是却无法被垃圾回收器回收的, 这就是 java 中可能出现内存泄露的情况, 例如, 缓存系统, 我们加载了一个对象放在缓存中 (例如放在一个全局 map 对象中), 然后一直不再使用它, 这个对象一直被缓存引用, 但却不再被使用. 检查 Java 中的内存泄露, 一定要让程序将各种分支情况都完整执行到程序结束, 然后看某个对象是否被使用过, 如果没有, 则才能
判定这个对象属于内存泄露. 如果一个外部类的实例对象的方法返回了一个内部类的实例对象, 这个内部类对象被长期引用了, 即使那个外部类实例对象不再被使用, 但由于内部类持久外部类的实例对象, 这个外部类对象将不会被垃圾回收, 这也会造成内存泄露.
- public class Stack {
- private Object[] elements=new Object[10];
- private int size = 0;
- public void push(Object e){
- ensureCapacity();
- elements[size++] = e;
- }
- public Object pop(){
- if( size == 0) throw new EmptyStackException();
- return elements[--size];
- }
- private void ensureCapacity(){
- if(elements.length == size){
- Object[] oldElements = elements;
- elements = new Object[2 * elements.length+1];
- System.arraycopy(oldElements,0, elements, 0, size);
- }
- }
- }
上面的原理应该很简单, 假如堆栈加了 10 个元素, 然后全部弹出来, 虽然堆栈是空的, 没有我们要的东西, 但是这是个对象是无法回收的, 这个才符合了内存泄露的两个条件: 无用, 无法回收. 但是就是存在这样的东西也不一定会导致什么样的后果, 如果这个
堆栈用的比较少, 也就浪费了几个 K 内存而已, 反正我们的内存都上 G 了, 哪里会有什么影响, 再说这个东西很快就会被回收的,
有什么关系. 下面看两个例子.
- public class Bad{
- public static Stack s=Stack();
- static{
- s.push(new Object());
- s.pop(); // 这里有一个对象发生内存泄露
- s.push(new Object()); // 上面的对象可以被回收了, 等于是自
愈了
} }
因为是 static, 就一直存在到程序退出, 但是我们也可以看到它有自愈功能, 就是说如果你的 Stack 最多有 100 个对象, 那么最
多也就只有 100 个对象无法被回收其实这个应该很容易理解, Stack 内部持有 100 个引用, 最坏的情况就是他们都是无用的,
因为我们一旦放新的进取, 以前的引用自然消失! 内存泄露的另外一种情况: 当一个对象被存储进 HashSet 集合中以后, 就不能修改这对象中的那些参与计算哈希值的字段了, 否则, 对象修改后的哈希值与最初存储进 HashSet 集合中时的哈希值就不同了, 在这种情况下, 即使在 contains 方法使用该对象的当前引用作为的参数去 HashSet 集合中检索对象, 也将返回找不到对象的结果, 这也会导致无法从 HashSet 集合中单独删除当前对象, 造成内存泄露.
深拷贝和浅拷贝.
简单来讲就是复制, 克隆.
Person p=new Person("张三");
浅拷贝就是对对象中的数据成员进行简单赋值, 如果存在动态成员或者指针就会报错. 深拷贝就是对对象中存在的动态成员或指针重新开辟内存空间.
finalize() 方法什么时候被调用? 析构函数 (finalization) 的目的是什么?
垃圾回收器 (garbage colector) 决定回收某对象时, 就会运行该对象的 finalize() 方法 但是在 Java 中很不幸, 如果内存总是充
足的, 那么垃圾回收可能永远不会进行, 也就是说 filalize() 可能永远不被执行, 显然指望它做收尾工作是靠不住的.
那么 finalize() 究竟是做什么的呢?
它最主要的用途是回收特殊渠道申请的内存. Java 程序有垃圾回收器, 所以一般情况下内存问题不用程序员操心. 但有一种 JNI(Java Native Interface)调用 non-Java 程序(C 或 C++), finalize() 的工作就是回收这部分的内存.
如果对象的引用被置为 null, 垃圾收集器是否会立即释放对象占用的内存?
不会, 在下一个垃圾回收周期中, 这个对象将是可被回收的.
什么是分布式垃圾回收(DGC)? 它是如何工作的?
DGC 叫做分布式垃圾回收. RMI 使用 DGC 来做自动垃圾回收. 因为 RMI 包含了跨虚拟机的远程对象的引用, 垃圾回收是很困难的. DGC 使用引用计数算法来给远程对象提供自动内存管理.
简述 Java 内存分配与回收策率以及 Minor GC 和 Major GC.
? 对象优先在堆的 Eden 区分配
? 大对象直接进入老年代
? 长期存活的对象将直接进入老年代
当 Eden 区没有足够的空间进行分配时, 虚拟机会执行一次 Minor GC.Minor GC 通常发生在新生代的 Eden 区, 在这个区的对象生存期短, 往往发生 Gc 的频率较高, 回收速度比较快;
Full GC/Major GC 发生在老年代, 一般情况下, 触发老年代 GC 的时候不会触发 Minor GC, 但是通过配置, 可以在 Full GC 之
前进行一次 Minor GC 这样可以加快老年代的回收速度.
JVM 的永久代中会发生垃圾回收么?
垃圾回收不会发生在永久代, 如果永久代满了或者是超过了临界值,
会触发完全垃圾回收(Full GC).
注: Java 8 中已经移除了永久代, 新加了一个叫做元数据区的
native 内存区.
Java 中垃圾收集的方法有哪些?
标记 - 清除: 这是垃圾收集算法中最基础的, 根据名字就可以知
道, 它的思想就是标记哪些要被回收的对象, 然后统一回收. 这种
方法很简单, 但是会有两个主要问题:
1. 效率不高, 标记和清除的效率都很低;
2. 会产生大量不连续的内存碎片, 导致以后程序在分配较大的
对象时, 由于没有充足的连续内存而提前触发一次 GC 动作.
复制算法:
为了解决效率问题, 复制算法将可用内存按容量划分为相等的两部分, 然后每次只使用其中的一块, 当一块内存用完时, 就将还存活的对象复制到第二块内存上, 然后一次性清楚完第一块内存, 再将第二块上的对象复制到第一块. 但是这种方式, 内存的代价太高, 每次基本上都要浪费一般的内存. 于是将该算法进行了改进, 内存区域不再是按照 1:1 去划分, 而是将内存划分为 8:1:1 三部分, 较大那份内存交 Eden 区, 其余是两块较小的内存区叫 Survior 区. 每次都会优先使用 Eden 区, 若 Eden 区满, 就将对象复制到第二块内存区上, 然后清除 Eden
区, 如果此时存活的对象太多, 以至于 Survivor 不够时, 会将这些对象通过分配担保机制复制到老年代中.(java 堆又分为新生代和老年代)
标记 - 整理:
该算法主要是为了解决标记 - 清除, 产生大量内存碎片的问题; 当对象存活率较高时, 也解决了复制算法的效率问题. 它的不同之处就是在清除对象的时候现将可回收对象移动到一端, 然后清除掉端边界以外的对象, 这样就不会产生内存碎片了.
分代收集:
现在的虚拟机垃圾收集大多采用这种方式, 它根据对象的生存周期, 将堆分为新生代和老年代. 在新生代中, 由于对象生存期短, 每次回收都会有大量对象死去, 那么这时就采用复制算法. 老年代里的对象存活率较高, 没有额外的空间进行分配担保.
什么是类加载器, 类加载器有哪些?
实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器.
主要有一下四种类加载器:
? 启动类加载器 (Bootstrap ClassLoader) 用来加载 Java 核心类库, 无法被 Java 程序直接引用.
? 扩展类加载器(extensions class loader): 它用来加载 Java 的扩展库. Java 虚拟机的实现会提供一个扩展库目录. 该类加载器在此目录里面查找并加载 Java 类.
? 系统类加载器 (system class loader): 它根据 Java 应用的类路径(CLASSPATH) 来加载 Java 类. 一般来说, Java 应用的类都是由它来完成加载的. 可以通过 ClassLoader.getSystemClassLoader() 来获取它.
? 用户自定义类加载器, 通过继承 java.lang.ClassLoader 类的方式实现.
类加载器双亲委派模型机制?
当一个类收到了类加载请求时, 不会自己先去加载这个类, 而是将其委派给父类, 由父类去加载, 如果此时父类不能加载, 反馈给子类, 由子类去完成类的加载.
来源: http://www.bubuko.com/infodetail-3527264.html