Java面試

自己經驗有限,篇幅也有限,這里只是記錄一些比較容易混淆或有難度和一些易忘的技術知識點,里面有一些也是面試阿里經常會被問到的問題,但是不保證答案全部正確,有錯誤的地方望大家指正

JVM相關
  1. JVM內存是如何分配的?
    堆:占用內存最大的區(qū)塊,主要存放new出來的對象,線程共享,主要設置大小參數(shù)名是-Xms-Xmx
    棧(以前概括都叫棧,具體說其實是非堆內存):一般是線程私有
    • 寄存器:即是程序計數(shù)器,存放當前正要執(zhí)行的下一條指令地址
    • 本地方法棧(不同jvm的實現(xiàn)可能不同,比如平常所用的sun的實現(xiàn)中方法棧和虛擬機棧是一個):線程私有,存儲比如ObjecthashCode();虛擬機棧中用于存儲局部變量表、動態(tài)鏈接、操作數(shù)、方法出口等信息
    • 方法區(qū):所有線程共享,用于存放加載類信息,比如常量、靜態(tài)常量,需要注意的是1.8以后將靜態(tài)常量放在了堆里
  2. GC
    垃圾回收的算法基礎是標記和復制算法,標記一般是樹形結構,采用根搜索算法,標記可以回收的對象,一般是對象搜索不到根節(jié)點即可以回收,有2次機會,在第一次被標記回收后可以重新被掛靠根節(jié)點(也即是被重新引用,涉及的的方法是finalize()),如果沒有下一次判定對象死亡;基礎的復制算法我舉個例子說明,將一塊內存分成2份,運行時只使用其中一塊,GC時將活的對象復制到另一塊內存,然后清除前一塊所有內存空間,類似于給U盤格式化,這樣比一個一個釋放內存要快得多,相信大家做格式化的時候體會過,現(xiàn)在jvm gc使用的的復制算法是結果改良的,不是平均的分成2份,默認比例好像是1/8,即平常見到的新生代、老年代、持久代等。具體的算法大家看資料文檔吧,這種東西不是說說就能清楚的。
  3. 內存泄漏和內存溢出
    內存泄漏,當一個對象不會被使用但占著內存即會導致內存泄漏,比如
Object o1 = new Object();
Object o2 = new Object();
o1 = o2; // 這時2個對象的引用地址是一樣的,但是o1申請的內存就沒有被使用

內存泄漏積累多了,內存不斷被無用對象占用,新的對象申請不到足夠的空間就會產生內存溢出。

架構相關
  1. springMVC的處理流程
    • 一個http請求
    • 經過一些過濾器或攔截器到達DispatcherServlet將請求轉發(fā)給對應的@Controller@RequestMapping
    • 參數(shù)封裝,請求頭判定等等
    • 調用業(yè)務方法獲得Model
    • 返回ModelAndView查找ViewResolver返回對應的View,可能是需要渲染的jsp,可能是json,可能是文件流等等。
  2. 說說redis里的bitmap
    bitmap一般用于計數(shù)或top計算,比如統(tǒng)計網(wǎng)站當前在線人數(shù),假設用戶id是遞增的整數(shù),當用戶上線時將用戶id存進bitmap,比如id是4,則bitmap就是00001000,id為8的用戶上線,bitmap的值變成10001000,對bitmap做count計算得出是2,而1KB=1024B=8196b且位運算是計算機最快的,這樣做的好處是速度快,還能知道是誰上下線的。同理如果要按月統(tǒng)計某個操作,只需要用每天做key值,然后做并集得到新的bitset計數(shù)就可。
  3. Ioc和AOP分別使用了哪些設計模式?
    工廠模式和代理模式,細一點還有單例、模版、原型,這里說一下代理模式,常用的一般是動態(tài)代理模式,jdk中提供了InvocationHandler接口可以方便實現(xiàn)動態(tài)代理:
public interface IService {
    void service();
}
public class MyService implements IService {
    @Override
    public void service() {
        System.out.println("service...");
    }
}
public class MyProxy implements InvocationHandler {

    private IService service;

    public MyProxy(IService service) {
        this.service = service;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before service");
        Object invoke = method.invoke(service, args);
        System.out.println("after service ");
        return invoke;
    }
}
public class Main {

    public static void main(String[] args) {
        MyService myService = new MyService();
        MyProxy myProxy = new MyProxy(myService);

        // 動態(tài)生成代理對象
        IService instance = (IService) Proxy.newProxyInstance(MyService.class.getClassLoader(), MyService.class.getInterfaces(), myProxy);
        instance.service();// 利用代理對象調用service方法

    }
}
  1. MySQL數(shù)據(jù)庫的鎖
    MySQL常見的有2種引擎,MyISAM采用的是表級鎖,給整表加鎖,速度快,不會死鎖,但是但是鎖競爭激烈,效率低,lock table_name read or write;InnoDB采用的是行級鎖,只鎖行,效率高,但是會死鎖,另外MySQL的行級鎖采用鎖索引實現(xiàn),所以只有通過索引檢索數(shù)據(jù)才能使用行級鎖,否則會使用表級鎖
  2. 冪等性
    冪等性是數(shù)學里的一個概念,我并不是很精通,簡單來說就是N次變換和1次變換的結果應該保持一致,計算機里他是一種Http協(xié)議中提到的性質,注意冪等性本身并不是協(xié)議,沒有辦法通過規(guī)范一致化操作,多用于分布式系統(tǒng),用于保證分布式系統(tǒng)中數(shù)據(jù)的一致性操作,類似于分布式事務,但是分布式事務中間件一般較重且效率有很大虧損,對于要求高性能的分布式場景中,冪等設計可能是唯一的選擇。實現(xiàn)場景簡單舉例來說,假設微信支付后端是分布式的(肯定是的),我發(fā)起了一個支付,如果服務器端已經處理完成但是我的手機沒網(wǎng)了,我會誤以為支付失敗,重新支付,冪等設計在此類場景中一般會這樣設計,在發(fā)起支付操作前會先向服務端申請一個ticket,這個ticket會關聯(lián)此次支付的操作,這個ticket只能增長一次,這樣在我重新發(fā)起支付的時候,服務器就可以正確返回支付成功且保證我只支付了一次。
java基礎
  1. ConcurrentHashMap
    ConcurrentHashMap是線程安全的集合類,功能類似于Hashtable,但是Hashtable雖然也是線程安全的,但是Hashtable只有一把同步鎖,并發(fā)性能不高,ConcurrentHashMap則是利用了鎖分段技術,簡單來說就是,多個類似的HashTable,單獨維護自己的鎖,這樣多線程操作的時候減少了競爭鎖的等待,在多線程應用里是最常用的線程安全集合類。查看源碼可以知道,ConcurrentHashMap內部主要成員是SegmentNode,Segment充當鎖,繼承ReentrantLock,Node相當于一個Map.Entry,其他大部分成員變量都是volatile的,因為happen before的存在,volatile字段的寫入操作先于讀操作,這也是用volatile替換鎖的經典應用場景。
  2. ThreadLocal
    ThreadLocal,利用線程局部變量來實現(xiàn)線程安全的方式,使用時需要小心應對,因為線程局部變量一旦使用完沒有被釋放就會導致內存泄漏。
  3. 有沒有可能兩個不相等的對象有相同的 hashcode?
    hashcode并不是唯一的,只是重復概率非常小而已,但是相等的對象hashcode一定是一樣的。
  4. 編寫多線程程序的時候你需要注意哪些?
    • 盡量使用volatile替換同步鎖
    • 給線程取個name
    • 使用并發(fā)集合而不是讓集合同步
    • 合理創(chuàng)建線程數(shù),一般而言是CPU的核心數(shù)*2+1
    • 給需要同步的代碼同步,而不是圖簡單給整個方法或類加同步
  5. DateFormat的所有實現(xiàn)都不是線程安全的,如果一定要在多線程中使用可以利用ThreadLocal
  6. 對稱加密和非對稱加密
    對稱加密:需要同一把密鑰來解密,速度快,一般用于需要加密大量數(shù)據(jù)時使用,常見用于對稱加密的算法有DES、3DES、RC系、AES等。
    非對稱加密:需要2把密鑰才能解密,分作公鑰和私鑰,如果用公開密鑰對數(shù)據(jù)進行加密,只有用對應的私有密鑰才能解密;如果用私有密鑰對數(shù)據(jù)進行加密,那么只有用對應的公開密鑰才能解密;常見的https協(xié)議里的證書機制就是采用的這種方式,常用于非對稱加密算法的有RSA、ECC、Elgamal。
  7. NIO和普通IO的區(qū)別?
    最主要的區(qū)別在于非阻塞與阻塞,NIO是先寫入緩沖區(qū)在再讀出操作,是非阻塞的,而普通IO操作主要是針對流的,一個線程讀寫流時是不能做其他操作的,就好比如下載文件有些軟件可以斷點續(xù)傳,有些不可以。
問題
  1. 統(tǒng)計log文件里所有出現(xiàn)的單詞以及出現(xiàn)的次數(shù)并且按照次數(shù)排序找出最頻繁的單詞?
    步驟其實很簡單:
    • 讀取文件
    • 排序
      這里直接提供代碼,分別是jdk 1.7和jdk 1.8的2個版本
      1.7:
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;

public class Test {

    /**
     * 根據(jù)map的value進行排序
     * @param map
     * @param <K>
     * @param <V>
     * @return
     */
    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
        List<Map.Entry<K, V>> list = new LinkedList<Map.Entry<K, V>>(map.entrySet());
        // 先將map轉換成List便于使用sort排序
        Collections.sort(list, new Comparator<Map.Entry<K, V>>() {
            public int compare(Map.Entry<K, V> o1, Map.Entry<K, V> o2) {
                return (o2.getValue()).compareTo(o1.getValue());
            }
        });

        Map<K, V> result = new LinkedHashMap<K, V>();
        for (Map.Entry<K, V> entry : list) {
            result.put(entry.getKey(), entry.getValue());
        }
        return result;
    }

    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        String fileName = "ngen.log";
        TreeMap<String, Integer> map = new TreeMap<>();
//      SortedMap<String, Integer> map = new
        try {
            fileInputStream = new FileInputStream(fileName);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
            String line = null;
            // 按行讀取文件分解單詞
            while ((line = bufferedReader.readLine()) != null) {
                String[] ss = line.split(" ");
                for (int i = 0; i < ss.length; i++) {
                    String s = ss[i];
                    if (s != null && s.matches("\\w+")) {
                        // 如果map中有此單詞就將次數(shù)+1
                        // 否則此單詞第一次出現(xiàn)
                        if (map.containsKey(s)) {
                            map.put(s, map.get(s) + 1);
                        } else {
                            map.put(s, 1);
                        }
                    }
                }
            }

            Map<String, Integer> sortedMap = sortByValue(map);
            for (Map.Entry<String, Integer> entry : sortedMap.entrySet()) {
                String key = entry.getKey();
                Integer value = entry.getValue();
                System.out.println(key + ":" + value);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

1.8:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

    /**
     * 根據(jù)map的value進行排序
     * @param map
     * @param <K>
     * @param <V>
     * @return
     */
    public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
        return map.entrySet()
                .stream()
                .sorted(Map.Entry.comparingByValue(Collections.reverseOrder())) // 逆序
                .collect(Collectors.toMap(
                        Map.Entry::getKey,
                        Map.Entry::getValue,
                        (e1, e2) -> e1,
                        LinkedHashMap::new
                ));
    }

    public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        String fileName = "D:/app.log";
        TreeMap<String, Integer> map = new TreeMap<>();
        try {
            fileInputStream = new FileInputStream(fileName);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream));
            bufferedReader.lines()
                    .flatMap(line -> Stream.of(line.split(" ")))
                    .filter(word -> word.matches("\\w+"))
                    .forEach(s -> { // Stream語法不太熟悉,不知道有木有更方便的方法?
                        if (map.containsKey(s)) {
                            map.put(s, map.get(s) + 1);
                        } else {
                            map.put(s, 1);
                        }
                    })
            ;

            Map<String, Integer> sortedMap = sortByValue(map);
            sortedMap.forEach((k, v) -> System.out.println(k + "," + v));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fileInputStream != null) {
                try {
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
  1. 從日志文件中讀取的是字節(jié)還是字符?
    乍一看這問題不要太簡單,但是我挺佩服問這問題的面試官的,這問題向后衍生無論是廣度還是深度都無可挑剔。如何回答這問題要從兩方面出發(fā),首先一點,所有操作系統(tǒng)存放在磁盤的任何數(shù)據(jù)肯定都是字節(jié)的,那么讀出來肯定也是字節(jié)的;第二點,通常日志文件操作寫代碼的時候讀出來的肯定是字符,不然你如何操作呢?只是java提供的方便的I/O操作方法而已,其實里面是將字節(jié)轉成了字符而已。向后延伸就會問比如java的IO操作注意事項、編碼等等問題,還有操作系統(tǒng)底層如何處理等等的問題,這道題很簡單,但是切記不要盲目作答。

  2. 秒殺系統(tǒng)設計
    自己并沒有實際秒殺系統(tǒng)設計經驗,這里從朋友以及網(wǎng)絡總結幾點:

    • 高并發(fā),總的來說肯定是Nginx做負載均衡,后臺做服務集群
    • 秒殺計時前的靜態(tài)頁面使用cdn
    • 秒殺計時不需要做高并發(fā)處理,因為new一個Date返回給前臺,任何語言支持個幾億并發(fā)都是沒問題的
    • 先緩存再查庫,保證低延遲
    • 秒殺系統(tǒng)單獨設計,不要與已有業(yè)務混淆,不然一旦阻塞會全盤崩潰
    • 庫存要保持事務唯一,數(shù)據(jù)庫最好另建表,不要與日常業(yè)務沖突
    • 預估請求處理最大量,當請求過多時攔截并直接返回等待
    • 預防惡意刷單,比如同一個IP只能有一個請求
    • 動態(tài)加載js來激活秒殺按鈕,避免秒殺沒有開始時被惡意操作
    • 并發(fā)請求隊列

本文已在版權印備案,如需轉載請訪問版權印。40142943

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • 小編費力收集:給你想要的面試集合 1.C++或Java中的異常處理機制的簡單原理和應用。 當JAVA程序違反了JA...
    八爺君閱讀 5,239評論 1 114
  • 一. Java基礎部分.................................................
    wy_sure閱讀 4,036評論 0 11
  • (一)Java部分 1、列舉出JAVA中6個比較常用的包【天威誠信面試題】 【參考答案】 java.lang;ja...
    獨云閱讀 7,279評論 0 62
  • 轉自:http://blog.csdn.net/jackfrued/article/details/4492194...
    王帥199207閱讀 8,807評論 3 93
  • 相關概念 面向對象的三個特征 封裝,繼承,多態(tài).這個應該是人人皆知.有時候也會加上抽象. 多態(tài)的好處 允許不同類對...
    東經315度閱讀 2,213評論 0 8

友情鏈接更多精彩內容