java程序员面试宝典2

java内存管理

垃圾收集

java中的垃圾收集有哪些优势

java中使用被称为垃圾收集器的技术来监视java程序的运行,当对象不再使用的时候,就会自动释放对象所使用的内存。java使用一系列软指针来跟踪对象的各个引用,并用一个对象表将这些软指针映射为对象的引用。之所以称为软指针,是因为这些指针并不直接指向对象,而是指向对象的引用。使用软指针,java的垃圾收集器能能够以单独的线程在后台运行,并一次检查每个对象。通过更改对象表项,垃圾收集器可以标记对象、移除对象、移动对象或检查对象。
垃圾收集器是自动运行的,一般情况下,无须显式地请求垃圾收集器。程序运行时,垃圾收集器会不时的检查对象的各个引用,并回收无引用对象所占用的内存。调用System类中的gc方法可以运行垃圾回收收集器。但是这样并不能保证立即回收指定的对象。
在JVM垃圾收集器收集一个对象之前,一般要求程序调用适当的方法释放资源,但在没有明确释放资源的情况下,java提供了默认机制终止化该对象的资源,这个方法就是finalize方法。它的原型为 protected void finalize() throws Throwable 。在finalize方法返回之后,对象消失后,垃圾收集器开始执行。

java中如何管理内存

java的内存管理就是对象的分配与释放。在java中,程序员需要通过关键字new来创建对象,为每个对象申请内存空间,所有的对象都在堆中分配空间。另外,对象的释放是有GC决定和执行的。分配和回收这两条线简化了程序员的工作。同时加重了JVM的工作。这也是java程序运行速度慢的原因之一。因为GC为了能够正确的释放对象,必须监视每个对象的运行状态,包括对象的申请、引用、被引用、赋值等。
监视对象状态是为了更加准确的及时的释放对象。而释放对象的根本原则就会说该对象不在被引用。

什么是内存泄露

在java中,内存泄露就是存在一些被分配的对象,这些对象有两个特点:(1)对象是可达的(2)对象是无用的。满足这两个条件则可判定位内存泄露。

内存泄露由什么引起?

保留下来缺永远不再使用的对象的引用
1【全局集合】解决办法:一种是周期性运行的某种清除任务,该任务将验证存储库中的数据,并移除任何不再需要的数据。另一种就是建立反向链接技术,然后集合负责统计集合中每个入口的反向链接的数目,当反向链接数目为0时,从集合中移除该元素。
2【缓存】如果操作时有很多的不同的输入,就会造成有相当多的结果存储在缓存中,引发内存泄露。
解决方案:1)检查结果是否在缓存中,如果在就返回结果。2)如果不在就进行计算。3)如果缓存所占的空间过大,就移除缓存最久的结果。4)将计算出来的结果添加到缓存中,以便之后对该操作的调用可以使用。
另以一种方法就是使用java.lang.ref.SoftReference类跟踪缓存中的对象,若虚拟机的内存用尽而需要更多的堆,这种方式可以保证这些音乐能够被移除。
【ClassLoader】
java中ClassLoader结构的使用为内存泄露提供了可乘之机。正是结构本身的复杂度是ClassLoader在内存泄露方面存在着很多问题。ClassLoader的特别之处就在于它不仅涉及“常规”的对象引用,还涉及到元对象的引用,比如字段、方法和类。这意味着只要对字段、方法、类或ClassLoader对象的引用,ClassLoader就会驻留在JVM中,因为ClassLoader本身可以关联很多类及其静态字段,所以就有许多的内存泄露。

如何确定内存泄露的位置

发生内存泄露的第一迹象就是:在应用程序中出现了OutOFMemoryError。这通常发送在你最不愿意让其发生生产环境中,此时几乎不能进行调试。有可能是因为测试环境运行应用程序的方式与生产系统不完全相同,因而导致了内存泄露只发生在生产环境中。在这种情况下,需要使用一些开销低的工具来监控和查找内存泄露,还需要能无需重启系统或修改代码就可以将这些工具连接到正在运行的系统上。可能最重要的是,当进行分析是,需要能够断开工具而保持系统不受干扰。
虽然OutOfMemoryError通常都是内存泄露的信号,但是也有可能是因为应用程序确实正在使用这么多的内存。对于后者,或者必须增加JVM可用的堆的数量。或者对应用程序进行某种更改,使它使用更少的内存。
不间断地监控GC的活动,确定内存使用量是否随着时间的增加而增加,如果是则发生了内存泄露

clone

Object类中有clone方法,但是Object又没有实现Cloneable接口,这是为什么?对于没有实现Cloneable的类来说,可以用从Object类继承而来的clone方法实现一些基本的值的复制操作,那是不是可以说clone方法并没有对对象是否属于Cloneable类型进行检验?
【答案】
clone方法是Object中定义的,并且实protected类型的,只有实现了这个接口,才可以在该类的实例上调用clone方法;否则会抛出CloneNotSupportException。说clone方法并没有对对象是否属于cloneable类型进行检验这个观点是不对的。因为cloneable接口的的出现跟接口的正常使用没有任何关系,特别是它并不指定clone方法-该方法从Object类中继承而来,该接口只是作为一个标记。

继承与接口

基础知识

覆盖与重载

对于同一个可访问区内被声明的几个具有不同参数列表(参数的类型、个数、顺序不同)的同名函数,程序会根据不同的参数列表来确定具体调用哪个函数,这种机制叫做重载,不关心返回类型。
覆盖是指派生类中存在重新定义的函数,其函数名、参数列表、返回值类型必须同父级中对应被覆盖的函数严格一致。

继承和组合

继承是白盒复用,对父类的修改会影响到子类,会破坏封装性,因为会将父类实现的细节暴露给子类,是一种通过扩展已有对象的实现,从而获得新功能的复用方法。
组合是指通过对现有的对象进行拼装产生新的更复杂的功能。因为在对象之间,各自的内部细节是不可见的。所以,我们也说这种方式的代码时“黑盒代码复用”。
【继承和组合各有优缺点】
继承关系是客观事件及OOP中最基本的关系,而且继承是深化接口规范的基础,没有继承就没有多态等诸多OO特性。继承在编译器静态地定义其层次结构,使得在技术上来说,继承更加直白明了,易于使用,而且容易修改通过继承复用的代码,但是不能在运行期间通过改变继承得到的实现代码。更重要的是,继承使得父类向子类开发了过多的权限,破坏封装性,导致代码依赖,可能会经常需要修改父类来适应不同的场景。同时限制了程序的灵活性和可复用性。
组合是在运行时通过对象之间的引用动态定义的。组合要求对象互相尊重对方的接口,不过这样一来,因为对性之间只能唯一地通过接口相互作用,对象的封装性得到了良好的维护。同时,在运行期间,任何对象都能够被转换为其他同类型的对象。更好的地方在于,因为对象的实现代码只与接口有关,所以由潜在的代码依赖问题减少。
在理想情况下,我们不需要创建新的组件来完成代码复用,而只需要通过对象的组合方法来拼接已存在的组件以获得新的功能。但这种情况很少出现,因为在实际情况中,现有的组件总是不够用的,而通过继承来复用代码往往要比通过组合对象容易的多。
【例子】通过Array实现一个Queue

方法1class Queue extends Array{
    //etc
}
方法2:将Array实例作为Queue的一个属性,即通过组合方式实现
class Queue extends Object{
    private Array anArray;
    //etc
}
分析:方法2更好,因为Queue的实例不用继承多余的Collection类的方法和属性。

this 和 super

this 和 super在使用前都是不需要声明的。this关键字使用在一个成员函数的内部,指向当前对象,当前对象即调用当前正在执行方法的那个对象。super关键字是直接指向超类的构造函数,用来引用超类中的变量和方法。
this出现的情况
1 构造函数中参数列表名和成员变量名完全相同时
2 全面访问当前对象,而非访问某一个个别的实例对象。如System.out.println(this);打印当前对象的信息。
3 构造方法的第一句,可以用this(name,age,sex)这样的方式在一个构造函数中调用同类的另一个构造函数。

不能继承的情况

匿名内部类是否可以extends其他类,是否可以实现接口

【答案】匿名内部类没有类名,不能继承其他类,但是一个内部类可以作为一个接口,由另一个内部类实现。
final类决定不要因为性能的原因将类定义为final的。如果一个类还没有准备好被继承,最好在类文档中标明,而不要将它定义为final,这是因为没有人可以保证会不会由于什么原因需要继承它。
如果整个类是final,就表明自己不希望被继承,或者不允许其他任何人采取这种操作,final类的数据成员既可以是final类型也可以不是,只把类定义为final只是限制了其继承性,由于它禁止了继承,所以一个final类中多有方法都默认是final类型的,因此也无法覆盖它们,只能被调用。但是成员变量如果未定义为final类型,则可修改,否则如果初始化后,则无法修改,未初始化,可以利用反射机制进行修改。
【例子】

class SmallBrain{}
final class Dinosaur{
    int i=7;
    int j =1;
    SmallBrain x = new SmallBrain();
    void f(){
        System.out.println(i);
        System.out.println(j);
    }
}
public class Main11 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Dinosaur d = new Dinosaur();
        d.f();//7 1
        d.i = 40;
        d.j++;
        d.f();//40 2
    }
}

如果是基本类型的变量,其值一旦初始化便不能变了,如果是引用型的变量,则初始化后,引用不能变即地址不能变,而引用的内容可以改变。
【例子】

import java.lang.reflect.*;
class Person{

    private final String name ="chen.lei";
    //private final String name = (null!=null?"ddd":"chen.lei");
    public String getName(){
        return this.name;
    }
}
public class Main11 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        final Person p = new Person();
        System.out.println("berfore change "+p.getName());
        System.out.println("-------------------------");
        changeProperty(p);
    }
    public static void changeProperty(Person p){
        final Class clazz = p.getClass();
        try{
            final Field field = clazz.getDeclaredField("name");
            field.setAccessible(true);
            field.set(p, String.valueOf("lei.chen"));
            System.out.println("update by reflect Field instance value "+field.get(p));
            System.out.println("update by reflect Person instance vaule "+p.getName());
            System.out.println("-----------------------");
        }catch(final NoSuchFieldException e){
            e.printStackTrace();
        }catch(final SecurityException e){
            e.printStackTrace();
        }catch(final IllegalArgumentException e){
            e.printStackTrace();
        }catch(final IllegalAccessException e){
            e.printStackTrace();
        }

    }
}
【输出】
berfore change chen.lei
-------------------------
update by reflect Field instance value lei.chen
update by reflect Person instance vaule chen.lei
-----------------------
【阻止优化后】
berfore change chen.lei
-------------------------
update by reflect Field instance value lei.chen
update by reflect Person instance vaule lei.chen
-----------------------

分析此处,发现虽然动态获取的Filed中内容被改变了但是Person实例中的p.getName()却没有改变,这是由于编译期间final类型的数据自动被优化了。即所有用到该变量的地方都被替换为了常量,所以get方法在编译后被优化为return “chen.lei”而不再是return this.name。可以通过String name =(null!==null?”ddd”:”chen.lei”);即可阻止编译优化
当final修饰的成员变量在定义时没有初始化,而是在构造函数中初始化

import java.lang.reflect.*;
class Person{

    private final String name;
    public Person(){
        this.name = "chen.lei";
    }
    public String getName(){
        return this.name;
    }
}
public class Main11 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        final Person p = new Person();
        System.out.println("berfore change "+p.getName());
        System.out.println("-------------------------");
        changeProperty(p);
    }
    public static void changeProperty(Person p){
        final Class clazz = p.getClass();
        try{
            final Field field = clazz.getDeclaredField("name");
            field.setAccessible(true);
            field.set(p, String.valueOf("lei.chen"));
            System.out.println("update by reflect Field instance value "+field.get(p));
            System.out.println("update by reflect Person instance vaule "+p.getName());
            System.out.println("-----------------------");
        }catch(final NoSuchFieldException e){
            e.printStackTrace();
        }catch(final SecurityException e){
            e.printStackTrace();
        }catch(final IllegalArgumentException e){
            e.printStackTrace();
        }catch(final IllegalAccessException e){
            e.printStackTrace();
        }

    }
}
【输出】
berfore change chen.lei
-------------------------
update by reflect Field instance value lei.chen
update by reflect Person instance vaule lei.chen
-----------------------

分析结果,发现可以改变。

抽象类与接口

抽象类:基类并不具有与具体事物相联系,而是只表达一种抽象的概念,用以派生出一个公共的界面—抽象类。
+ 抽象类只能作为其他类的基类,不能直接被实例化,而且对抽象类不能使用new关键字。抽象类如果包含抽象的变量或值,则它们要么是null,要么包含了对非抽象类的实例的引用。
+ 抽象类允许包含非抽象成员,可以一个抽象成员也没有。
+ 抽象类不能同时定义为final的,因为final类不能被继承,而抽象类总是希望被继承,自己不能直接实例化。
+ 如果一个非抽象类从抽象类中派生,则需要覆盖器继承来的所有抽象成员。
+ 抽象类可以被抽象类继承,结果还是抽象类
+ 抽象类允许被声明

java中的集合类型的继承关系

java中接口的修饰符

  • 接口用于描述系统对外提供的所有服务,因此接口中的成员变量和方法都必须是公开的(public)类型,确保外部可以访问它们。
  • 接口仅仅描述系统能做什么,但不指明如何去做,所以接口中的方法都是抽象的(abstract)方法
  • 接口不涉及和任何具体实例相关的细节,因此接口没有构造方法,不能被实例化,没有实例变量,只有静态变量(static)
  • 接口中的变量是所有实现类所共有的,既然共有,肯定是不可变的东西,因为变化的东西也不能算共有。所以变量是final类型,也就是常量了。

接口是对一类事务的属性和行为更高层次的抽象。对修改关闭,对扩展开发
综上,接口的方法都是public abstract;常量:public static final,且必须赋初始值,因为对于final类型,要不在定义时初始化要不在构造函数中初始化,而接口没有构造函数,所以只能在定义时初始化。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值