本期面试题
-
简述垃圾回收机制
-
如何判断一个对象是否存活(或GC对象的判定方法)
-
垃圾回收的优点和原理,并考虑两种回收机制
-
垃圾回收器的基本原理是什么,垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
-
java中会存在内存泄漏吗?请简单描述
-
深拷贝和浅拷贝
-
system.gc()和Runtime.gc()会做什么事情?
-
finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
-
如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
-
什么是分布式垃圾回收(DGC)?它是如何工作的?
要先自己尝试作答哟~ 下面是答案~~
简述java垃圾回收机制
在java中,程序员是不需要显式的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,他是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。
如何判断一个对象是否存活?(GC对象的判定方法)
判断一个对象是否存活有两种方法:
-
引用计数法
所有引用计数法就是给每一个对象设置一个引用计数器,每当有一个地方引用这个对象时,就将计数器加一,引用失效时,计数器就减一。当一个对象的引用计数器为零时,说明此对象没有被引用,也就是“死对象”,将会被垃圾回收。
引用计数法有一个缺陷就是无法解决循环引用问题,也就是说当对象A引用对象B,对象B又引用着对象A,那么此时A、B对象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流的虚拟机都没有采用这种算法。
-
可达性算法(引用链)
该算法的思想是:从一个被称为GC Roots的对象开始向下搜索,如果一个对象到GC Roots没有任何引用链相连时,则说明此对象不可用。在java中可以作为GC Roots 的对象有以下几种。
- 虚拟机栈中引用的对象
- 方法去类静态属性引用的对象
- 方法区常量池引用的对象
- 本地方法栈JNI引用的对象
虽然这些算法可以判定一个对象是否能被回收,但是当满足上述条件时,一个对象并不一定会被回收。当一个对象不可达GC Roots时,这个对象不会立马被回收,而是出现一个死缓的阶段,若要被真正的回收需要经历两次标记。
如果对象在可达性分析中没有与GC Root的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或者已被虚拟机调用过,那么久认为是没必要的。如果该对象有必要执行finalize()方法,那么这个对象会放在一个成为F-Queue的队列中,虚拟机会触发一个Finalize()线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完,这是因为如果finalize()执行缓慢或者发生了死锁,那么就会造成F-Queue队列一直等待,造成了内存回收系统的崩溃。GC对处于F-Queue中的对象进行第二次被标记,这时,该对象将被移除“即将回收”集合,等待回收。
垃圾回收的有点和原理。并考虑两种回收机制。
java语言中一个显著的特点就是引入了垃圾回收机制,使C++程序员最头疼的内存管理的问题迎刃而解,它使得java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制,java中的对象不再有“作用域”的概念,只有对象的引用才有“作用域”。垃圾回收可以有效地防止内存泄漏,有效地使用可以使用的内存。垃圾回收器通常作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。
回收机制有分代复制垃圾回收和标记垃圾回收,增量垃圾回收。
垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收?
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。通常,GC采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是“可达的”,哪些对象是“不可达的”。当GC确定一些对象为“不可达”时,GC就有责任回收这些内存空间。
可以。
程序员可以手动执行System.gc(),通知GC运行,但是java语言规范并不保证GC一定会执行。
java中会存在内存泄漏吗?请简单描述。
所谓内存泄漏就是指一个不再被程序使用的对象或变量一直被占据在内存中。java中有垃圾回收机制,它可以保证一对象不再被引用的时候,即对象变成了孤儿的时候,对象将自动被垃圾回收器从内存中清除掉。由于java使用有向图的方式进行垃圾回收管理,可以消除引用循环的问题,例如有两个对象,相互引用,只要他们和根进程不可达,那么GC也是可以回收他们的,例如下面的代码可以看到这种情况的内存回收:
public static void main(String[] args) throws IOException {
try {
getTest();
} catch(IOException e){
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(Personother) {
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集合中单独删除当前对象,造成内存泄漏。
深拷贝和浅拷贝
简单来讲就是复制,克隆。
Persoon p = new Person("张三");
浅拷贝就是对对象中的数据成员进行简单复制,如果存在了动态成员或者指针就会报错。
深拷贝就是对对象中存在的动态成员或指针重新开辟内存空间。
System.gc() 和Runtime.gc()会做什么事情?
这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。
finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
垃圾回收期(grabage collector)决定回收某对象时,就会运行该对象的finalize()方法,但是在java中很不幸,如果内存总是充足的,那么垃圾回收能永远不会进行,也就是说finalize()可能永远不被执行,显然指望它做收尾工作是靠不住的。那么finalize()究竟是做什么的呢?它最主要的用途是回收特殊渠道申请的内存。java程序有垃圾回收器,所以一般情况下内存问题不用程序员操心。但有一种JNI(java native)调用non-java程序(C或C++),finalize()的工作就是回收这部分的内存。
如果对象的引用被置为null,垃圾收集器是否会立即释放对象占用的内存?
不会,在下一个垃圾回收周期中,这个对象将是可被回收的。
什么是分布式垃圾回收(DGC)?它是如何工作的?
DGC叫做分布式垃圾回收。RMI使用DGC来做自动垃圾回收。因为RMI包含了跨虚拟机的远程对象的引用,垃圾回收是很困难的。DGC使用引用计数算法来给远程对象提供自动内存管理。