java web经典面试问题

来源:互联网 发布:大趋势炒股软件 编辑:程序博客网 时间:2024/05/21 17:14

一、Tomcat怎么进行性能调整?

1.加速JSP编译速度。JSP会被转换为Java serverlet再编译成Java字节码。可以使用更快的编译器, Jikes编译器,需要修改web.xml;或在没有启动一个新的JVM的情况下,使用Ant。

2.不要把所有的实现都使用JSP页面,而是使用java模板引擎。


二、HashTable 与HashMap,ArrayList与HashSet

HashMap:Map的子类 键值允许为空 非线程安全

HashTable:no no no

ArrayList:线性列表 有序  元素可以重复 

HashSet:散列集合 no no


三、java面向对象

1.抽象:把一类东西提取出来,并不是全部,而只是选择其中的一部分。数据抽象——对象的属性。过程抽象——对象的行为特征。
2.继承:对象的新类可以从现有的类中派生,这个过程称为类继承。派生类可以从它的基类那里继承方法和实例变量并且可以修改或增加。继承是为了重用父类代码,同时为实现多态性作准备。
3.封装:封装是把过程和数据保护起来,对数据的访问只能通过已定义的界面。
4.多态:多态性是指允许不同类的对象对同一消息作出响应。多态性包括参数化多态性和包含多态性,解决了应用程序函数同名问题。重写、重载接构成多态性,弥补继承的不足。


四、线程同步

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

Thread.sleep(1)和wait()

把竞争访问的资源类的变量标识为private;使用synchronized关键字同步方法或代码。
 
1、锁的原理
 
一个对象只有一个锁。如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。即其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。
释放锁是指持锁线程退出了synchronized同步方法或代码块。
 
锁和同步:
1)只能同步方法,而不能同步变量和类;
2)每个对象只有一个锁,当提到同步时,应该清楚在哪个对象上同步。
3)不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)如果两个线程要执行一个类中的synchronized方法,并且使用相同的实例来调用方法,那么只能有一个线程能执行,另一个需要等待,直到锁被释放。
5)如果线程拥有同步和非同步方法,其中非同步方法可以被多个线程自由访问而不受锁的限制。
6)线程睡眠时,它所持的任何锁都不会释放。 
7)线程可以获得多个锁。在一个对象的同步方法里面调用另一个对象的同步方法,就获取了两个对象的同步锁。
8)同步损害并发性,应该尽可能缩小同步范围。同步可以同步整个方法,也可以同步方法中一部分代码块。
9)在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:
    public int fix(int y) {
        synchronized (this) {
            x = x - y;
        }
        return x;
    }
 
当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:
    public synchronized int getX() {
        return x++;
    }
    public int getX() {
        synchronized (this) {
            return x;
        }
    }
效果是完全一样的。
 
2、静态方法同步
 
要同步静态方法,需要一个用于整个类对象的锁(XXX.class)。
例如:
public static synchronized int setName(String name){
      Xxx.name = name;
}
等价于
public static int setName(String name){
      synchronized(Xxx.class){
            Xxx.name = name;
      }
}

 
3、如果线程不能获得锁会怎么样
 
线程进入同步方法,如果锁已经被占用,则线程被阻塞(等待)
注意是哪个对象被锁定:
1)调用同一个对象的非静态同步方法将彼此阻塞。如果是不同对象,因为是不同的锁,所以线程间彼此互不干预
2)调用同一个类中的静态同步方法将彼此阻塞,它们都是锁定在相同的Class对象上。
3)静态同步方法和非静态同步方法将永远不会彼此阻塞,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。
4)对于同步代码块(synchronized后面括号的内容),在同一个对象上进行同步的线程将彼此阻塞,在不同对象上锁定的线程将永远不会彼此阻塞。
 
4、何时需要同步
 
同步用于确保两个线程不会同时修改数据!
对于非静态字段中可更改的数据,通常使用非静态方法访问。对于静态字段中可更改的数据,通常使用静态方法访问。
如果需要在非静态方法中使用静态字段,或者在静态字段中调用非静态方法,问题将变得非常复杂。
 
5、线程安全类
 
一个类能够同步以保护数据,这个类就称为“线程安全的”。即使是线程安全类,操作的线程之间仍然不安全。
 
比如一个集合是线程安全的,有两个线程在操作同一个集合对象,当第一个线程查询集合非空后,删除集合中所有元素的时候。第二个线程也来执行与第一个线程相同的操作,也许在第一个线程查询后,第二个线程也查询出集合非空,但是当第一个执行清除后,第二个再执行删除显然是不对的,因为此时集合已经为空了。
 
public class NameList { 
    private List nameList = Collections.synchronizedList(new LinkedList()); 

    public void add(String name) { 
        nameList.add(name); 
    } 

    public String removeFirst() { 
        if (nameList.size() > 0) { 
            return (String) nameList.remove(0); 
        } else { 
            return null
        } 
    } 
}
 
public class Test { 
    public static void main(String[] args) { 
        final NameList nl = new NameList(); 
        nl.add("aaa"); 
        class NameDropper extends Thread{ 
            public void run(){ 
                String name = nl.removeFirst(); 
                System.out.println(name); 
            } 
        } 

        Thread t1 = new NameDropper(); 
        Thread t2 = new NameDropper(); 
        t1.start(); 
        t2.start(); 
    } 
}
 
虽然集合对象
    private List nameList = Collections.synchronizedList(new LinkedList());
是同步的,但是程序还不是线程安全的。
出现这种事件的原因是,上例中一个线程操作列表过程中无法阻止另外一个线程对列表的其他操作。
 
解决上面问题的办法是,在操作集合对象的NameList上面做一个同步:
public class NameList { 
    private List nameList = Collections.synchronizedList(new LinkedList()); 

    public synchronized void add(String name) { 
        nameList.add(name); 
    } 

    public synchronized String removeFirst() { 
        if (nameList.size() > 0) { 
            return (String) nameList.remove(0); 
        } else { 
            return null
        } 
    } 
}
 
6、线程死锁
 
当两个线程互相等待时就发生死锁。
 
public class DeadlockRisk { 
    private static class Resource { 
        public int value; 
    } 

    private Resource resourceA = new Resource(); 
    private Resource resourceB = new Resource(); 

    public int read() { 
        synchronized (resourceA) { 
            synchronized (resourceB) { 
                return resourceB.value + resourceA.value; 
            } 
        } 
    } 

    public void write(int a, int b) { 
        synchronized (resourceB) { 
            synchronized (resourceA) { 
                resourceA.value = a; 
                resourceB.value = b; 
            } 
        } 
    } 
}
 
假设read()方法由一个线程启动,write()方法由另外一个线程启动。读线程将拥有resourceA锁,写线程将拥有resourceB锁,两者都等待的话就出现死锁。
 
上面这个例子发生死锁的概率很小,因为CPU必须从读线程切换到写线程,所以死锁基本上不能发生。
 
有一些设计方法能帮助避免死锁,包括始终按照预定义的顺序获取锁这一策略。
 
7、线程同步小结
 
1、线程同步的目的是为了保护多个线程访问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象有且只有一个锁,一旦已经有线程获取了该对象锁,那么其他线程就无法再访问该对象的同步方法了。
3、静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步代码块,要时刻清醒在哪个对象上同步。
5、编写线程安全的类,就是保证一个线程操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相等待造成的,在实际中发生的概率非常的小。

Java中static关键字用法总结

1.静态方法
静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法 (就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。static方法必须被实现,而不能是抽象的abstract。
2.静态变量
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
3.静态类
通常一个普通类不允许声明为静态的,只有一个内部类才可以。
4.静态代码块
static{后面跟着一段代码
5.final static静态常量
用来修饰成员变量和成员方法,可简单理解为“全局常量”!

Java内存泄露方面


Java 中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java 中的内存泄漏,这些对象不会被GC 所回收,然而它却占用内存。在C++中,内存泄漏的范围更大一些。有些对象被分配了内存空间,然后却不可达,由于C++中没有GC,这些内存将永远收

不回来。在Java 中,这些不可达的对象都由GC 负责回收,因此程序员不需要考虑这部分的内存泄漏。通过分析,可以得知,对于C++,程序员需要自己管理边和顶点,而对于Java 程序员只需要管理边就可以了(不需要管理顶点

的释放)。通过这种方式,Java 提高了编程的效率。

内存泄漏示例

示例1

在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身,那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。

Vector v = new Vector(10);

for (int i = 1; i

{Object o = new Object();

v.add(o);

o = null;

}//

此时,所有的Object 对象都没有被释放,因为变量v 引用这些对象。实际上无用,而还被引用的对象,GC 就无能为力了(事实上GC 认为它还有用),这一点是导致内存泄漏最重要的原因。

(1)如果要释放对象,就必须使其的引用记数为0,只有那些不再被引用的对象才能被释放,这个原理很简单,但是很重要,是导致内存泄漏的基本原因,也是解决内存泄漏方法的宗旨;

(2)程序员无须管理对象空间具体的分配和释放过程,但必须要关注被释放对象的引用记数是否为0;

(3)一个对象可能被其他对象引用的过程的几种:

a.直接赋值,如上例中的A.a = E;

b.通过参数传递,例如public void addObject(Object E);

c.其它一些情况如系统调用等。

容易引起内存泄漏的几大原因

静态集合类

像HashMap、Vector 等静态集合类的使用最容易引起内存泄漏,因为这些静态变量的生命周期与应用程序一致,如示例1,如果该Vector 是静态的,那么它将一直存在,而其中所有的Object对象也不能被释放,因为它们也将一直被该Vector 引用着。

监听器

在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

物理连接

一些物理连接,比如数据库连接和网络连接,除非其显式的关闭了连接,否则是不会自动被GC 回收的。Java 数据库连接一般用DataSource.getConnection()来创建,当不再使用时必须用Close()方法来释放,因为这些连接是独立于JVM的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。

内部类和外部模块等的引用

内部类的引用是比较容易遗忘的一种,而且一旦没释放可能导致一系列的后继类对象没有释放。对于程序员而言,自己的程序很清楚,如果发现内存泄漏,自己对这些对象的引用可以很快定位并解决,但是现在的应用软件

并非一个人实现,模块化的思想在现代软件中非常明显,所以程序员要小心外部模块不经意的引用,例如程序员A 负责A 模块,调用了B 模块的一个方法如:

public void registerMsg(Object b);

这种调用就要非常小心了,传入了一个对象,很可能模块B就保持了对该对象的引用,这时候就需要注意模块B 是否提供相应的操作去除引用。

预防和检测内存漏洞

在了解了引起内存泄漏的一些原因后,应该尽可能地避免和发现内存泄漏。

(1)好的编码习惯。最基本的建议就是尽早释放无用对象的引用,大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域后,自动设置为null。在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组、列、树、图等,这些对象之间有相互引用关系较为复杂。对于这类对象,GC 回收它们一般效率较低。如果程序允许,尽早将不用的引用对象赋为null。另外建议几点:

在确认一个对象无用后,将其所有引用显式的置为null;

当类从Jpanel 或Jdialog 或其它容器类继承的时候,删除该对象之前不妨调用它的removeall()方法;在设一个引用变量为null 值之前,应注意该引用变量指向的对象是否被监听,若有,要首先除去监听器,然后才可以赋空值;当对象是一个Thread 的时候,删除该对象之前不妨调用它的interrupt()方法;内存检测过程中不仅要关注自己编写的类对象,同时也要关注一些基本类型的对象,例如:int[]、String、char[]等等;如果有数据库连接,使用try…finally 结构,在finally 中关闭Statement 对象和连接。

(2)好的测试工具。在开发中不能完全避免内存泄漏,关键要在发现有内存泄漏的时候能用好的测试工具迅速定位问题的所在。市场上已有几种专业检查Java 内存泄漏的工具,它们的基本工作原理大同小异,都是通过监测Java 程序运行时,所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员将根据这些信息判断程序是否有内存泄漏问题。这些工具包括Optimizeit Profiler、JProbe Profiler、JinSight、Rational 公司的Purify 等。



0 0