遇到面試題總結

1、描述Java 關鍵字volatile?

  用volatile修飾的變量,線程在每次使用變量的時候,都會讀取變量修改後的最的值。

public class VolatileTest extends Thread {
    
    volatile boolean flag = false;
    int i = 0;
    
    public void run() {
        while (!flag) {
            i++;
        }
    }
    
    public static void main(String[] args) throws Exception {
        VolatileTest vt = new VolatileTest();
        vt.start();
        Thread.sleep(2000);
        vt.flag = true;
        System.out.println("stope" + vt.i);
    }
}

在flag前面加上volatile關鍵字,強制線程每次讀取該值的時候都去“主內存”中取值

2、Java MANIFEST.MF文件的用途

可以用的上的有:

    1. Main-Class 指定程序的入口,這樣可以直接用java -jar xxx.jar來運行程序。

    2. Class-Path 指定jar包的依賴關係,class loader會依據這個路徑來搜索class。

我平時用mf文件一般說來都是讓jar成爲可以直接運行這一目的。雖然目的這麼簡單,但是每次打包還是很容易出現各種各樣的問題讓人頭痛。mf總是沒有被應用,或者是出錯了。

首先給出一個簡單而標準的例子:

Manifest-Version: 1.0
Main-Class: ui.JFrameMain
Class-Path: jtds-1.2.jar c3p0-0.9.1.2.jar 
esri_mo20.jar esri_mo20res.jar jsde83_sdk.jar

解釋一下:

Manifest-Version mf文件版本號,這行照抄

Main-Class 包含main函數的類,程序的入口

Class-Path 用到的一些包吧 

3、Lock和synchronized的區別和使用

 1)Lock不是Java語言內置的,synchronized是Java語言的關鍵字,因此是內置特性。Lock是一個類,通過這個類可以實現同步訪問;

  2)Lock和synchronized有一點非常大的不同,採用synchronized不需要用戶去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的佔用;而Lock則必須要用戶去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。

4、JAVA多線程實現的四種方式

     1、繼承Thread類創建線程

     2、實現Runnable接口創建線程

     3、實現Callable接口通過FutureTask包裝器來創建Thread線程

    4、使用ExecutorService、Callable、Future實現有返回結果的線程

5、Java native關鍵字

 一. 什麼是Native Method
   簡單地講,一個Native Method就是一個java調用非java代碼的接口。一個Native Method是這樣一個java的方法:該方法的實現由非java語言實現,比如C。這個特徵並非java所特有,很多其它的編程語言都有這一機制,比如在C++中,你可以用extern "C"告知C++編譯器去調用一個C的函數。
   "A native method is a java method whose implementation is provided by non-java code."
   在定義一個native method時,並不提供實現體(有些像定義一個java interface),因爲其實現體是由非java語言在外面實現的。

 

6、stack,deque,queue對比

1.stack堆棧,沒有迭代器,支持push()方法。後進先出,top()返回最頂端的元素,pop()剔除最頂元素,後進先出(LIFO);

2.deque雙端隊列,支持迭代器,有push_back()方法,跟vector差不多,比vector多了個pop_front,push_front方法;

3.queue隊列,先進先出,不支持迭代器。隊列通常(但並非一定)以 FIFO(先進先出)的方式排序各個元素。Queue使用時要儘量避免Collection的add()和remove()方法而是要使用offer()來加入元素,使用poll()來獲取並移出元素。它們的優點是通過返回值可以判斷成功與否,add()和remove()方法在失敗的時候會拋出異常。 如果要使用前端而不移出該元素,使用element()或者peek()方法。

7、雙向鏈表的java實現

package structure;

import java.util.Arrays;
import java.util.Scanner;
import static net.mindview.util.Print.*;

/**
 * 雙向鏈表的操作
 * @author Tom Tao
 *
 */
class Node {
    public int value;
    public Node(int n) {
        this.value = n;
    }
    Node pre;
    Node next;
}

public class ReLinkedList {
    public static Node head;
    public static int nodeNum;
    
    /*初始化*/
    public static void init(int n[]) {
        head = new Node(n[0]);
        Node temp = null;
        head.pre = null;
        Node p = head;
        nodeNum ++;
        for(int i = 1 ; n[i] != -1 ; i ++) {
            nodeNum ++;
            temp = new Node(n[i]);
            p.next = temp;
            temp.pre = p;
            p = temp;
        }
        p.next = null;
    }
    
    /*打印*/
    public static void printList(Node h) {
        while(h != null) {
            printnb(h.value + " ");
            h = h.next;
        }
    }
    
    /*插入*/
    public static void insert(int index, int v) {
        Node ne = new Node(v);
        if(index == 0) {
            ne.next = head;
            ne.pre = null;
            head = ne;
            return;
        }
        Node temp = head;
        Node t = temp;
        for(int i = 0 ; i < index ; i ++) {
            t = temp;
            temp = temp.next;
        }
        if(index == nodeNum) {
            t.next = ne;
            ne.pre = t;
            ne.next = null;
            return;
        }
        ne.next = temp;
        ne.pre = t;
        temp.pre = ne;
        t.next = ne;
    }
    
    /*刪除*/
    public static void delete(int index) {
        if(index == 0){
            head = head.next;
            head.pre = null;
            return;
        }
        Node temp = head;
        Node t = temp;
        for(int i = 0 ; i < index ; i ++) {
            t = temp;
            temp = temp.next;
        }
        if(index == nodeNum) {
            t.next = null;
            return;
        }
        t.next = temp.next;
        temp.next.pre = t;
    }
    
    /*查找*/
    public static int find(int index) {
        Node temp = head;
        for(int i = 0 ; i < index ; i ++) {
            temp = temp.next;
        }
        return temp.value;
    }
    
    /*test*/
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        print("please input the nums to init:");
        int[] n = new int[100];
        Arrays.fill(n, -1);
        int val;
        int count = 0;
        while((val = sc.nextInt()) >= 0) {
            n[count++] = val;
        }
        init(n);
        while(true){
            printList(head);
            print("\nplease input the index and num to insert");
            int index = sc.nextInt();
            val = sc.nextInt();
            insert(index, val);
            printList(head);
            print("\nplease input the index to delete");
            index = sc.nextInt();
            delete(index);
            printList(head);
            print("\nplease input the index to find");
            index = sc.nextInt();
            print(find(index));
            print("----------------------------------------------------");
        }
    }
}

8、java內存泄漏與處理

內存溢出 out of memory,是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;

內存泄露 memory leak,是指程序在申請內存後,無法釋放已申請的內存空間,一次內存泄露危害可以忽略,但內存泄露堆積後果很嚴重,無論多少內存,遲早會被佔光。

memory leak會最終會導致out of memory!

以發生的方式來分類,內存泄漏可以分爲4類:

  1. 常發性內存泄漏。發生內存泄漏的代碼會被多次執行到,每次被執行的時候都會導致一塊內存泄漏。
  2. 偶發性內存泄漏。發生內存泄漏的代碼只有在某些特定環境或操作過程下才會發生。常發性和偶發性是相對的。對於特定的環境,偶發性的也許就變成了常發性的。所以測試環境和測試方法對檢測內存泄漏至關重要。
  3. 一次性內存泄漏。發生內存泄漏的代碼只會被執行一次,或者由於算法上的缺陷,導致總會有一塊僅且一塊內存發生泄漏。比如,在類的構造函數中分配內存,在析構函數中卻沒有釋放該內存,所以內存泄漏只會發生一次。
  4. 隱式內存泄漏。程序在運行過程中不停的分配內存,但是直到結束的時候才釋放內存。嚴格的說這裏並沒有發生內存泄漏,因爲最終程序釋放了所有申請的內存。但是對於一個服務器程序,需要運行幾天,幾周甚至幾個月,不及時釋放內存也可能導致最終耗盡系統的所有內存。所以,我們稱這類內存泄漏爲隱式內存泄漏。

從用戶使用程序的角度來看,內存泄漏本身不會產生什麼危害,作爲一般的用戶,根本感覺不到內存泄漏的存在。真正有危害的是內存泄漏的堆積,這會最終消耗盡系統所有的內存。從這個角度來說,一次性內存泄漏並沒有什麼危害,因爲它不會堆積,而隱式內存泄漏危害性則非常大,因爲較之於常發性和偶發性內存泄漏它更難被檢測到

一、Java內存回收機制
不論哪種語言的內存分配方式,都需要返回所分配內存的真實地址,也就是返回一個指針到內存塊的首地址。Java中對象是採用new或者反射的方法創建的,這些對象的創建都是在堆(Heap)中分配的,所有對象的回收都是由Java虛擬機通過垃圾回收機制完成的。GC爲了能夠正確釋放對象,會監控每個對象的運行狀況,對他們的申請、引用、被引用、賦值等狀況進行監控,Java會使用有向圖的方法進行管理內存,實時監控對象是否可以達到,如果不可到達,則就將其回收,

二、Java內存泄露引起原因
內存泄露是指無用對象(不再使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而造成的內存空間的浪費稱爲內存泄露。內存泄露有時不嚴重且不易察覺,這樣開發者就不知道存在內存泄露,但有時也會很嚴重,會提示你Out of memory。

那麼,Java內存泄露根本原因是什麼呢?長生命週期的對象持有短生命週期對象的引用就很可能發生內存泄露,儘管短生命週期對象已經不再需要,但是因爲長生命週期對象持有它的引用而導致不能被回收,這就是java中內存泄露的發生場景。具體主要有如下幾大類:
1、靜態集合類引起內存泄露:
像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變量的生命週期和應用程序一致,他們所引用的所有的對象Object也不能被釋放,因爲他們也將一直被Vector等引用着。

Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}//
複製代碼
在這個例子中,循環申請Object 對象,並將所申請的對象放入一個Vector 中,如果僅僅釋放引用本身(o=null),那麼Vector 仍然引用該對象,所以這個對象對GC 來說是不可回收的。因此,如果對象加入到Vector 後,還必須從Vector 中刪除,最簡單的方法就是將Vector對象設置爲null。----在循環外面添加v=null,這樣就可以將vector裏面的內存進行回收了。

案例2
public static void main(String[] args) {
if (true) {
ArrayList

在局部變量的作用域之外,al這個引用變成了空指針,但是new ArrayList< String >()這個對象仍可能存在,它的實際地址值也就是這個對象在內存中的地址還是保存在棧裏面的,gc無法把它給回收

public static void main(String[] args) {
if (true) {
ArrayList

9、單列

考慮性能問題。synchronized關鍵字修飾getInstance()方法,會導致所有調用getInstance()方法的線程都要競爭同一把鎖,即使在單例對象已經生成好的情況下。這裏使用double check的方式。另外,還需要注意instance在初始化過程中,可能已經不爲NULL,這樣有可能會導致程序崩潰,因此這裏加了個臨時變量t,確保初始化完成之後再賦值給instance。

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null){
Singleton t = new Singleton();
instance = t;
}
}
}
return instance;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章