2019-12-19

基礎

數(shù)據(jù)結構與算法基礎

說一下幾種常見的排序算法和分別的復雜度。

冒泡排序?

?? ?具體的原理就是未排好,自上而下的比較,小的數(shù)就往上冒,大的數(shù)就往下沉,按理來說冒泡排序總共的次數(shù)最多為n

選擇排序

?? ?每一趟從待排序的數(shù)據(jù)元素中選出最?。ɑ蜃畲螅┑囊粋€元素,順序放在已排好序的數(shù)列的最后,直到全部待排序的數(shù)據(jù)元素排完。 選擇排序是不穩(wěn)定的排序方法。

用Java寫一個冒泡排序算法

?//主方法

??public static void main(String[] args) {

????int[] arr = { 3, 5, 7, 1, 8, 11, 9}; //定義數(shù)組

????bubbleSort (arr); //開始排序

??}

??//排序方法

??public static void bubbleSort(int[] arrys) {

????//定義臨時變量 temp

????int temp = 0;

????//用j為下標,遍歷數(shù)組

????for (int j = 0; j < arrys.length; j++) {

??????//對于每一個數(shù)組元素,從0到還未來排序的最大下標,總是把最大的數(shù)字放在后面

??????for(int k = 0;k < arrys.length-j-1; k++){

????????if(arrys[k] > arrys[k+1]){ //判斷當前數(shù)字與后面數(shù)字的大小

??????????temp = arrys[k];

??????????arrys[k] = arrys[k+1];

??????????arrys[k+1] = temp; //用temp變量進行換值

????????}

??????}

???}

????bubblePrint(arrys); //打印

??}

??//打印方法

??public static void bubblePrint(int[] before){

????for(int i = 0;i < before.length; i++){ //遍歷

??????System.out.print(before[i] + ""); //打印,以空格隔開

????}

????System.out.println();//換行

??}

描述一下鏈式存儲結構。

提起鏈式存儲結構,其與數(shù)組是兩個非?;A的數(shù)據(jù)結構,每當提到鏈式存儲結構時,一般情況下我們都會將其與數(shù)組放到一塊兒來比較。

對于數(shù)組與鏈表,從結構上來看,數(shù)組是需要一塊連續(xù)的內存空間來存儲數(shù)據(jù),對內存的要求非常高,比如說我們申請一個100M大小的數(shù)組,而如果我們的內存可用空間大于100M,但是沒有連續(xù)的100M可用空間,那即便是我們的內存空間充足,在申請空間時也會申請失敗。

而對于鏈表來說,對內存空間的要求就不會有那么高,它并不需要一塊連續(xù)的內存空間,只要內存空間充足,即使內存空間存在碎片,只要碎片的大小足夠存儲一個鏈表節(jié)點的數(shù)據(jù),該碎片的空間都有可能被分配,鏈表通過指針或者引用的方式將一組零散的空間串聯(lián)起來使用。所以如果一個鏈表需要100M的空間,內存空間充足,即使沒有一個連續(xù)的空間大于100M,也不會影響鏈表的空間分配。

數(shù)組連續(xù)的存儲空間

鏈表分散的空間串聯(lián)

如何遍歷一棵二叉樹?

?前序遞歸遍歷算法:訪問根結點-->遞歸遍歷根結點的左子樹-->遞歸遍歷根結點的右子樹

?中序遞歸遍歷算法:遞歸遍歷根結點的左子樹-->訪問根結點-->遞歸遍歷根結點的右子樹

?后序遞歸遍歷算法:遞歸遍歷根結點的左子樹-->遞歸遍歷根結點的右子樹-->訪問根結點

? ? public static void main(String[] args) {

????????System.out.print("前序:");

????????Traversal.preOrder();

????????Traversal.preOrderRecursion(Traversal.createBinTree());

????????System.out.print("中序:");

????????Traversal.inOrder();

????????Traversal.inOrderRecursion(Traversal.createBinTree());

????????System.out.print("后序:");

????????Traversal.postOrder();

????????Traversal.postOrderRecursion(Traversal.createBinTree());

????}

}

/**

* 節(jié)點數(shù)據(jù)結構

*

* @author bin.zhang

* @version 2017年8月30日 上午11:49:38

*/

class BinTreeNode {

????BinTreeNode() {

????}

????BinTreeNode(char data, int flag, BinTreeNode lchild, BinTreeNode rchild) {

????????this.data = data;

????????this.flag = flag;

????????this.lchild = lchild;

????????this.rchild = rchild;

????}

????char data;

????int flag;

????BinTreeNode lchild, rchild;

}

class Traversal {

????/**

?????* 創(chuàng)建一棵二叉樹

?????*

?????* @return 根節(jié)點

?????* @author bin.zhang

?????*/

????public static BinTreeNode createBinTree() {

????????BinTreeNode R3 = new BinTreeNode('F', 0, null, null);

????????BinTreeNode L2 = new BinTreeNode('D', 0, null, null);

????????BinTreeNode R2 = new BinTreeNode('E', 0, null, R3);

????????BinTreeNode L1 = new BinTreeNode('B', 0, L2, R2);

????????BinTreeNode R1 = new BinTreeNode('C', 0, null, null);

????????BinTreeNode T = new BinTreeNode('A', 0, L1, R1);

????????return T;

????}

????// 前序

????public static void preOrder() {

????????BinTreeNode p = createBinTree();

????????Stack<BinTreeNode> stack = new Stack<BinTreeNode>();

????????while (p != null || !stack.empty()) {

????????????if (p != null) {

????????????????System.out.print(p.data);

????????????????stack.push(p);

????????????????p = p.lchild;

????????????} else {

????????????????p = stack.pop();

????????????????p = p.rchild;

????????????}

????????}

????????System.out.println();

????}

????// 前序遞歸

????public static void preOrderRecursion(BinTreeNode top) {

????????if (top != null) {

????????????System.out.println(top.data);

????????????preOrderRecursion(top.lchild);

????????????preOrderRecursion(top.rchild);

????????}

????}

????// 中序

????public static void inOrder() {

????????BinTreeNode p = createBinTree();

????????Stack<BinTreeNode> stack = new Stack<BinTreeNode>();

????????while (p != null || !stack.empty()) {

????????????if (p != null) {

????????????????stack.push(p);

????????????????p = p.lchild;

????????????} else {

????????????????p = stack.pop();

????????????????System.out.print(p.data);

????????????????p = p.rchild;

????????????}

????????}

????????System.out.println();

????}

????// 中序遞歸

????public static void inOrderRecursion(BinTreeNode top) {

????????if (top != null) {

????????????inOrderRecursion(top.lchild);

????????????System.out.println(top.data);

????????????inOrderRecursion(top.rchild);

????????}

????}

????// 后序

????public static void postOrder() {

????????BinTreeNode p = createBinTree();

????????Stack<BinTreeNode> stack = new Stack<BinTreeNode>(); // 初始化棧

????????int mark = 1; // 轉向標志

????????while (p != null || !stack.empty()) { // 遍歷

????????????if (p != null && mark != 0) {

????????????????stack.push(p);

????????????????p = p.lchild;

????????????}// 轉向左子樹

????????????else {

????????????????p = stack.pop();

????????????????p.flag++; // 退棧

????????????????if (p.flag == 1) {

????????????????????stack.push(p);

????????????????????p = p.rchild;

????????????????????mark = 1;

????????????????} // 轉向右子樹

????????????????else if (p.flag == 2 && !stack.empty()) { // 輸出結點

????????????????????System.out.print(p.data);

????????????????????mark = 0;

????????????????} else if (p.flag == 2 && stack.empty()) { // 輸出根結點并退出

????????????????????System.out.print(p.data);

????????????????????break;

????????????????}

????????????} // if-else

????????} // while

????????System.out.println();

????}

????// 后序遞歸

????public static void postOrderRecursion(BinTreeNode top) {

????????if (top != null) {

????????????postOrderRecursion(top.lchild);

????????????postOrderRecursion(top.rchild);

????????????System.out.println(top.data);

????????}

????}

倒排一個LinkedList。

??templist.addFirst(m);?

public static void main(String[] args) {

????reverse();

}

public static void reverse() {

????LinkedList<Integer> list = new LinkedList<>();

????LinkedList<Integer> templist = new LinkedList<>();

????int i = 0;

????while (i < 6) {

????????list.add(i);

????????i++;

????}

? ? ? ?System.out.println(list);

????Iterator<Integer> it = list.iterator();

????int m;

????while (it.hasNext() && i >= 0) {

????????m = it.next();

????????templist.addFirst(m);

????????i--;

????}

????list = templist;

????System.out.println(list);

}

運行結果為:

[0, 1, 2, 3, 4, 5]

[5, 4, 3, 2, 1, 0]

用Java寫一個遞歸遍歷目錄下面的所有文件。

private static int depth=1;

public static void find(String pathName,int depth) throws IOException {

????int filecount=0;

????//獲取pathName的File對象

????File dirFile = new File(pathName);

????//判斷該文件或目錄是否存在,不存在時在控制臺輸出提醒

????if (!dirFile.exists()) {

????????System.out.println("do not exit");

????????return ;

????}

????//判斷如果不是一個目錄,就判斷是不是一個文件,時文件則輸出文件路徑

????if (!dirFile.isDirectory()) {

????????if (dirFile.isFile()) {

????????????System.out.println(dirFile.getCanonicalFile());

????????}

????????return ;

????}

????for (int j = 0; j < depth; j++) {

????????System.out.print("??");

????}

????System.out.print("|--");

????System.out.println(dirFile.getName());

????//獲取此目錄下的所有文件名與目錄名

????String[] fileList = dirFile.list();

????int currentDepth=depth+1;

????for (int i = 0; i < fileList.length; i++) {

????????//遍歷文件目錄

????????String string = fileList[i];

????????//File("documentName","fileName")是File的另一個構造器

????????File file = new File(dirFile.getPath(),string);

????????String name = file.getName();

????????//如果是一個目錄,搜索深度depth++,輸出目錄名后,進行遞歸

????????if (file.isDirectory()) {

????????????//遞歸

????????????find(file.getCanonicalPath(),currentDepth);

????????}else{

????????????//如果是文件,則直接輸出文件名

????????????for (int j = 0; j < currentDepth; j++) {

????????????????System.out.print("???");

????????????}

????????????System.out.print("|--");

????????????System.out.println(name);

? ? ? ? }

????}

}

public static void main(String[] args) throws IOException{

????find("D:", depth);

}

二、Java基礎

接口與抽象類的區(qū)別?

接口(interface)和抽象類(abstract class)是支持抽象類定義的兩種機制。

接口是公開的,不能有私有的方法或變量,接口中的所有方法都沒有具體的實現(xiàn),通過關鍵字interface修飾。

抽象類是可以有私有方法或私有變量的,通過把類或者類中的方法聲明為abstract來表示一個類是抽象類,被聲明為抽象的方法不能有具體的實現(xiàn)。

相同點:

(1)都不能被實例化?即不能使用new關鍵字來實例化對象;

(2)接口的實現(xiàn)類或抽象類的子類都只有實現(xiàn)了接口或抽象類中的方法后才能實例化。

不同點:

(1)接口只有方法,不能有具體的實現(xiàn),而抽象類可以有方法與實現(xiàn),方法可在抽象類中實現(xiàn)。

(2)實現(xiàn)接口的關鍵字為implements,繼承抽象類的關鍵字為extends。一個類可以實現(xiàn)多個接口,但一個類只能繼承一個抽象類。所以,使用接口可以間接地實現(xiàn)多重繼承。

(3)接口強調特定功能的實現(xiàn),而抽象類強調所屬關系。

(4)接口成員變量默認為public static final,必須賦初值,不能被修改;其所有的成員方法都是public、abstract的。抽象類中成員變量默認default,可在子類中被重新定義,也可被重新賦值;抽象方法被abstract修飾,不能被private、static、synchronized和native等修飾,必須以分號結尾,不帶花括號。

(5)接口被用于常用的功能,便于日后維護和添加刪除,而抽象類更傾向于充當公共類的角色,不適用于日后重新對立面的代碼修改。功能需要累積時用抽象類,不需要累積時用接口。

Java中的異常有哪幾類?分別怎么使用?

從根本上講所有的異常都屬于Throwable的子類,從大的方面講分為Error(錯誤)和Exception(異常)。

Error是程序無法處理的異常,當發(fā)生Error時程序線程會終止運行。

我們一般意義上講的異常就是指的Exception,這也是面試官常問的問題。

下面就簡單說一下關于Exception(以下都簡稱異常)的一點理解。

異常分為運行時異常(RuntimeException,又叫非檢查時異常)和非運行時異常(又叫檢查異常)。

下面列舉一下常見的運行時異常:

????NullPointerException - 試圖訪問一空對象的變量、方法或空數(shù)組的元素

????ArrayIndexOutOfBoundsException - 數(shù)組越界訪問

????NoClassDefFoundException - JAVA運行時系統(tǒng)找不到所引用的類

????ArithmeticException - 算術運算中,被0除或模除

????ArrayStoreException - 數(shù)據(jù)存儲異常,寫數(shù)組操作時,對象或數(shù)據(jù)類型不兼容

????ClassCastException - 類型轉換異常

????IllegalArgumentException - 方法的參數(shù)無效

????IllegalThreadStateException - 試圖非法改變線程狀態(tài),比方說試圖啟動一已經運行的線程

????NumberFormatException - 數(shù)據(jù)格式異常,試圖把一字符串非法轉換成數(shù)值(或相反)

????SecurityException - 如果Applet試圖執(zhí)行一被WWW瀏覽器安全設置所禁止的操作

????IncompatibleClassChangeException - 如改變了某一類定義,卻沒有重新編譯其他引用了這個類的對象。

????如某一成員變量的聲明被從靜態(tài)改變?yōu)榉庆o態(tài),但其他引用了這個變量的類卻沒有重新編譯,或者相反。如刪除了類聲明中的某一域或方法,但沒有重新編譯那些引用了這個域或方法的類

????OutOfMemoryException - 內存不足,通常發(fā)生于創(chuàng)建對象之時

????IncompatibleTypeException - 試圖實例化一個接口,Java運行時系統(tǒng)將拋出這個異常

????UnsatisfiedLinkException - 如果所需調用的方法是C函數(shù),但Java運行時系統(tǒng)卻無法連接這個函數(shù)

????InternalException - 系統(tǒng)內部故障所導致的異常情況,可能是因為Java運行時系統(tǒng)本身的原因。如果發(fā)現(xiàn)一可重現(xiàn)的InternalException,

? ? 前三種異常是我在開發(fā)中經常性遇到的問題,對于運行時異常通過它的別名(非檢查時異常)我們可以知道這些異常不是我們寫代碼的時候可以檢查到的,而是程序在運行的時候可能會發(fā)生的,也就是說這是在業(yè)務邏輯上可能會出現(xiàn)的問題。

對于這類異常就需要我們在開發(fā)的時候盡量的把業(yè)務邏輯可能會出現(xiàn)的問題考慮清楚,進行異常捕捉。

常用的方法包括:

try{}catch(Exception e){}finally{}和throws兩種辦法。

try{}catch(Exception e){}finally{}是在方法中對異常進行捕獲,catch可以寫多個,Java運行時系統(tǒng)從上到下分別對每個catch語句處理的例外類型進行檢測,

直到找到類型相匹配的catch語句為止。(具體的就不過多贅述)

throws是出現(xiàn)在方法頭部,個人理解算是在最外層拋出異常。關于對運行時異常的處理。

非運行時異常中我們常遇到的可能有IOException、SQLException等,

這類異常就是我們編寫的代碼、sql語句的問題了,這就需要我們回到代碼中仔細檢查了。

常用的集合類有哪些?比如List如何排序?

集合的兩個頂級接口分別為:Collection和Map

Collection下有兩個比較常用的接口分別是List(列表)和Set(集),

其中List可以存儲重復元素,元素是有序的(存取順序一致),可以通過List腳標來獲取指定元素;

而Set不可以有重復元素,元素是無序的。

List接口中,比較常用的類有三個:ArrayList、Vactor、LinkedList。

ArrayList:線程不安全的,對元素的查詢速度快。

Vector:線程安全的,多了一種取出元素的方式:枚舉(Enumeration),但已被ArrayList取代。

LinkedList:鏈表結構,對元素的增刪速度很快。

Set接口中,比較常用的類有兩個:HashSet、TreeSet:

HashSet:要保證元素唯一性,需要覆蓋掉Object中的equals和hashCode方法(因為底層是通過這兩個方法來判斷兩個元素是否是同一個)。

TreeSet:以二叉樹的結構對元素進行存儲,可以對元素進行排序。

??????排序的兩種方式:

?????????????1、元素自身具備比較功能,元素實現(xiàn)Comparable接口,覆蓋compareTo方法。

?????????????2、建立一個比較器對象,該對象實現(xiàn)Comparator接口,覆蓋compare方法,并將該對象作為參數(shù)傳給TreeSet的構造函數(shù)(可以用匿名內部類)。

Map接口其特點是:元素是成對出現(xiàn)的,以鍵和值的形式體現(xiàn)出來,鍵要保證唯一性:常用類有:HashMap,Hashtable,TreeMap。

HashMap:線程不安全等的,允許存放null鍵null值。

Hashtable:線程安全的,不允許存放null鍵null值。

TreeMap:可以對鍵進行排序(要實現(xiàn)排序方法同TreeSet)。

Collection和Map兩個接口對元素操作的區(qū)別:

存入元素:

Collection接口下的實現(xiàn)類通過add方法來完成,而Map下是通過put方法來完成。

取出元素:

Collection接口下:List接口有兩種方式:1、get(腳標);2、通過Iterator迭代方式獲取元素;而Vactor多了一種枚舉(Enumeration)的方式。Set接口通過迭代的方式獲取元素。

Map接口下:先通地keySet獲取鍵的系列,然后通過該系列使用Iterator迭代方式獲取元素值。

List集合序列排序的兩種方法

Comparable自然規(guī)則排序

在自定義類里面實現(xiàn)Comparable接口,并重寫抽象方法compareTo(Student o);

Collections.sort(集合);

Comparator專門規(guī)則排序(l臨時排序)

新建一個實現(xiàn)了Comparator接口的類,并重寫抽象方法compare(Student o1, Student o2);

Collections.sort(集合,實現(xiàn)了Comparator接口的類的實例化對象);

ArrayList和LinkedList內部的實現(xiàn)大致是怎樣的?他們之間的區(qū)別和優(yōu)缺點?

?? ?ArrayList:底層數(shù)組,線程不安全的,對元素的查詢速度快。

?? ?LinkedList:鏈表結構,對元素的增刪速度很快。

內存溢出是怎么回事?請舉一個例子?

1、OutOfMemoryError: PermGen space

Permanent Generation space 這個區(qū)域主要用來保存加來的Class的一些信息,在程序運行期間屬于永久占用的,Java的GC不會對他進行釋放,所以如果啟動的程序加載的信息比較大,超出了這個空間的大小,就會發(fā)生溢出錯誤;

解決的辦法無非就是增加空間分配了——增加java虛擬機中的XX:PermSize和XX:MaxPermSize參數(shù)的大小,其中XX:PermSize是初始永久保存區(qū)域大小,XX:MaxPermSize是最大永久保存區(qū)域大小。

2、OutOfMemoryError:Java heap space

heap 是Java內存中的堆區(qū),主要用來存放對象,當對象太多超出了空間大小,GC又來不及釋放的時候,就會發(fā)生溢出錯誤。

Java中對象的創(chuàng)建是可控的,但是對象的回收是由GC自動的,一般來說,當已存在對象沒有引用(即不可達)的時候,GC就會定時的來回收對象,釋放空間。但是因為程序的設計問題,導致對象可達但是又沒有用(即前文提到的內存泄露),當這種情況越來越多的時候,問題就來了。

針對這個問題,我們需要做一下兩點:

1、檢查程序,減少大量重復創(chuàng)建對象的死循環(huán),減少內存泄露。

2、增加Java虛擬機中Xms(初始堆大小)和Xmx(最大堆大?。﹨?shù)的大小。

3、StackOverFlowError

stack是Java內存中的??臻g,主要用來存放方法中的變量,參數(shù)等臨時性的數(shù)據(jù)的,發(fā)生溢出一般是因為分配空間太小,或是執(zhí)行的方法遞歸層數(shù)太多創(chuàng)建了占用了太多棧幀導致溢出。

針對這個問題,除了修改配置參數(shù)-Xss參數(shù)增加線程棧大小之外,優(yōu)化程序是尤其重要。

==和equals的區(qū)別?

?? ?==用于判別兩字符串在內存中的地址是否相同

?? ?equals用于判別兩字符串的內容是否相同

hashCode方法的作用?

減少set集合添加元素的速度

哈希算法也稱為散列算法,是將數(shù)據(jù)依特定算法直接指定到一個地址上。hashCode方法實際上返回的就是對象存儲的物理地址(實際可能并不是)。???

當集合要添加新的元素時,先調用這個元素的hashCode方法,就能定位到它應該放置的物理位置。

如果這個位置上沒有元素,它就可以直接存儲在這個位置上,不用再進行任何比較;

如果這個位置上已經有元素了,

就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址。

這樣一來實際調用equals方法的次數(shù)就大大降低了,幾乎只需要一兩次。

所以,Java對于eqauls方法和hashCode方法是這樣規(guī)定的:

1、如果兩個對象相同,那么它們的hashCode值一定要相同;

2、如果兩個對象的hashCode相同,它們并不一定相同,上面說的對象相同指的是用eqauls方法比較。

NIO是什么?適用于何種場景?

NIO最核心的三個組件

Channel通道

Buffer緩沖區(qū)

Selector選擇器

NIO是為了彌補IO操作的不足而誕生的,NIO的一些新特性有:

非阻塞I/O,選擇器,緩沖以及管道。

管道(Channel),緩沖(Buffer) ,選擇器( Selector)是其主要特征。

概念解釋:

Channel——管道實際上就像傳統(tǒng)IO中的流,到任何目的地(或來自任何地方)的所有數(shù)據(jù)都必須通過一個 Channel 對象。

一個 Buffer 實質上是一個容器對象。

Selector——選擇器用于監(jiān)聽多個管道的事件,使用傳統(tǒng)的阻塞IO時我們可以方便的知道什么時候可以進行讀寫,而使用非阻塞通道,我們需要一些方法來知道什么時候通道準備好了,選擇器正是為這個需要而誕生的。

NIO和傳統(tǒng)的IO有什么區(qū)別呢?

1,IO是面向流的,NIO是面向塊(緩沖區(qū))的。

IO面向流的操作一次一個字節(jié)地處理數(shù)據(jù)。一個輸入流產生一個字節(jié)的數(shù)據(jù),一個輸出流消費一個字節(jié)的數(shù)據(jù)。導致了數(shù)據(jù)的讀取和寫入效率不佳;

NIO面向塊的操作在一步中產生或者消費一個數(shù)據(jù)塊。按塊處理數(shù)據(jù)比按(流式的)字節(jié)處理數(shù)據(jù)要快得多,同時數(shù)據(jù)讀取到一個它稍后處理的緩沖區(qū),需要時可在緩沖區(qū)中前后移動。這就增加了處理過程中的靈活性。通俗來說,NIO采取了“預讀”的方式,當你讀取某一部分數(shù)據(jù)時,他就會猜測你下一步可能會讀取的數(shù)據(jù)而預先緩沖下來。

2,IO是阻塞的,NIO是非阻塞的。

對于傳統(tǒng)的IO,當一個線程調用read() 或 write()時,該線程被阻塞,直到有一些數(shù)據(jù)被讀取,或數(shù)據(jù)完全寫入。該線程在此期間不能再干任何事情了。

而對于NIO,使用一個線程發(fā)送讀取數(shù)據(jù)請求,沒有得到響應之前,線程是空閑的,此時線程可以去執(zhí)行別的任務,而不是像IO中那樣只能等待響應完成。

NIO和IO適用場景

NIO是為彌補傳統(tǒng)IO的不足而誕生的,但是尺有所短寸有所長,NIO也有缺點,因為NIO是面向緩沖區(qū)的操作,每一次的數(shù)據(jù)處理都是對緩沖區(qū)進行的,那么就會有一個問題,在數(shù)據(jù)處理之前必須要判斷緩沖區(qū)的數(shù)據(jù)是否完整或者已經讀取完畢,如果沒有,假設數(shù)據(jù)只讀取了一部分,那么對不完整的數(shù)據(jù)處理沒有任何意義。所以每次數(shù)據(jù)處理之前都要檢測緩沖區(qū)數(shù)據(jù)。

那么NIO和IO各適用的場景是什么呢?

如果需要管理同時打開的成千上萬個連接,這些連接每次只是發(fā)送少量的數(shù)據(jù),例如聊天服務器,這時候用NIO處理數(shù)據(jù)可能是個很好的選擇。

而如果只有少量的連接,而這些連接每次要發(fā)送大量的數(shù)據(jù),這時候傳統(tǒng)的IO更合適。

使用哪種處理數(shù)據(jù),需要在數(shù)據(jù)的響應等待時間和檢查緩沖區(qū)數(shù)據(jù)的時間上作比較來權衡選擇。

通俗解釋,最后,對于NIO和傳統(tǒng)IO,有一個網友講的生動的例子:

以前的流總是堵塞的,一個線程只要對它進行操作,其它操作就會被堵塞,也就相當于水管沒有閥門,你伸手接水的時候,不管水到了沒有,你就都只能耗在接水(流)上。

nio的Channel的加入,相當于增加了水龍頭(有閥門),雖然一個時刻也只能接一個水管的水,但依賴輪換策略,在水量不大的時候,各個水管里流出來的水,都可以得到妥

善接納,這個關鍵之處就是增加了一個接水工,也就是Selector,他負責協(xié)調,也就是看哪根水管有水了的話,在當前水管的水接到一定程度的時候,就切換一下:臨時關上當

前水龍頭,試著打開另一個水龍頭(看看有沒有水)。

當其他人需要用水的時候,不是直接去接水,而是事前提了一個水桶給接水工,這個水桶就是Buffer。也就是,其他人雖然也可能要等,但不會在現(xiàn)場等,而是回家等,可以做

其它事去,水接滿了,接水工會通知他們。

這其實也是非常接近當前社會分工細化的現(xiàn)實,也是統(tǒng)分利用現(xiàn)有資源達到并發(fā)效果的一種很經濟的手段,而不是動不動就來個并行處理,雖然那樣是最簡單的,但也是最浪費

資源的方式。

HashMap實現(xiàn)原理,如何保證HashMap的線程安全?

HashMap 重復允許有空鍵和空值,不允許有null鍵和null值

使用 java.util.Hashtable 類,此類是線程安全的。

使用 java.util.concurrent.ConcurrentHashMap,此類是線程安全的。

使用 java.util.Collections.synchronizedMap() 方法包裝 HashMap object,得到線程安全的Map,并在此Map上進行操作。

Map<Object, Object> objectObjectMap = Collections.singletonMap("aa","aa");

為什么用HashMap

HashMap是一個散列桶(數(shù)組和鏈表),它存儲的內容是鍵值對(key-value)映射

HashMap采用了數(shù)組和鏈表的數(shù)據(jù)結構,能在查詢和修改方便繼承了數(shù)組的線性查找和鏈表的尋址修改

HashMap是非synchronized,所以HashMap很快

HashMap可以接受null鍵和值,而Hashtable則不能(原因就是equlas()方法需要對象,因為HashMap是后出的API經過處理才可以)

HashMap的工作原理是什么

HashMap是基于hashing的原理,我們使用put(key,value)存儲對象到HashMap中,使用get(key)從HashMap中獲取對象。

當我們給put()方法傳遞鍵和值時,我們先對鍵調用hashCode()方法,計算并返回的hashCode是用于找到Map數(shù)組的bucket位置來儲存Node 對象。這里關鍵點在于指出,HashMap是在bucket中儲存鍵對象和值對象,作為Map.Node 。

JVM內存結構,為什么需要GC?

JVM主要包括四個部分:JVM結構、內存分配、垃圾回收算法、垃圾收集器。

1.類加載器(ClassLoader):在JVM啟動時或者在類運行時將需要的class加載到JVM中。

2.執(zhí)行引擎:負責執(zhí)行class文件中包含的字節(jié)碼指令;

3.內存區(qū)(也叫運行時數(shù)據(jù)區(qū)):是在JVM運行的時候操作所分配的內存區(qū)。運行時內存區(qū)主要可以劃分為5個區(qū)域,

方法區(qū)(Method Area):

????用于存儲類結構信息的地方,包括常量池、靜態(tài)變量、構造函數(shù)等。雖然JVM規(guī)范把方法區(qū)描述為堆的一個邏輯部分, 但它卻有個別名non-heap(非堆),所以大家不要搞混淆了。方法區(qū)還包含一個運行時常量池。

java堆(Heap):

????存儲java實例或者對象的地方。這塊是GC的主要區(qū)域(后面解釋)。從存儲的內容我們可以很容易知道,方法區(qū)和堆是被所有java線程共享的。

java棧(Stack):

????java棧總是和線程關聯(lián)在一起,每當創(chuàng)建一個線程時,JVM就會為這個線程創(chuàng)建一個對應的java棧。在這個java棧中又會包含多個棧幀,每運行一個方法就創(chuàng)建一個棧幀,用于存儲局部變量表、操作棧、方法返回值等。每一個方法從調用直至執(zhí)行完成的過程,就對應一個棧幀在java棧中入棧到出棧的過程。所以java棧是線程私有的。

程序計數(shù)器(PCRegister):

????用于保存當前線程執(zhí)行的內存地址。由于JVM程序是多線程執(zhí)行的(線程輪流切換),所以為了保證線程切換回來后,還能恢復到原先狀態(tài),就需要一個獨立的計數(shù)器,記錄之前中斷的地方,可見程序計數(shù)器也是線程私有的。

本地方法棧(Native Method Stack):

????和java棧的作用差不多,只不過是為JVM使用到的native方法服務的。

4.本地方法接口:

????主要是調用C或C++實現(xiàn)的本地方法及返回結果。

內存分配

我覺得了解垃圾回收之前,得先了解JVM是怎么分配內存的,然后識別哪些內存是垃圾需要回收,最后才是用什么方式回收。

Java的內存分配原理與C/C++不同,C/C++每次申請內存時都要malloc進行系統(tǒng)調用,而系統(tǒng)調用發(fā)生在內核空間,每次都要中斷進行切換,這需要一定的開銷,而Java虛擬機是先一次性分配一塊較大的空間,然后每次new時都在該空間上進行分配和釋放,減少了系統(tǒng)調用的次數(shù),節(jié)省了一定的開銷,這有點類似于內存池的概念;二是有了這塊空間過后,如何進行分配和回收就跟GC機制有關了。

java一般內存申請有兩種:靜態(tài)內存和動態(tài)內存。很容易理解,編譯時就能夠確定的內存就是靜態(tài)內存,即內存是固定的,系統(tǒng)一次性分配,比如int類型變量;動態(tài)內存分配就是在程序執(zhí)行時才知道要分配的存儲空間大小,比如java對象的內存空間。根據(jù)上面我們知道,java棧、程序計數(shù)器、本地方法棧都是線程私有的,線程生就生,線程滅就滅,棧中的棧幀隨著方法的結束也會撤銷,內存自然就跟著回收了。所以這幾個區(qū)域的內存分配與回收是確定的,我們不需要管的。但是java堆和方法區(qū)則不一樣,我們只有在程序運行期間才知道會創(chuàng)建哪些對象,所以這部分內存的分配和回收都是動態(tài)的。一般我們所說的垃圾回收也是針對的這一部分。

總之Stack的內存管理是順序分配的,而且定長,不存在內存回收問題;而Heap 則是為java對象的實例隨機分配內存,不定長度,所以存在內存分配和回收的問題;

垃圾檢測、回收算法

垃圾收集器一般必須完成兩件事:檢測出垃圾;回收垃圾。怎么檢測出垃圾?

一般有以下幾種方法:

引用計數(shù)法:給一個對象添加引用計數(shù)器,每當有個地方引用它,計數(shù)器就加1;引用失效就減1。

好了,問題來了,如果我有兩個對象A和B,互相引用,除此之外,沒有其他任何對象引用它們,實際上這兩個對象已經無法訪問,即是我們說的垃圾對象。但是互相引用,計數(shù)不為0,導致無法回收,所以還有另一種方法:

可達性分析算法:以根集對象為起始點進行搜索,如果有對象不可達的話,即是垃圾對象。這里的根集一般包括java棧中引用的對象、方法區(qū)常良池中引用的對象

本地方法中引用的對象等。

NIO模型,select/epoll的區(qū)別,多路復用的原理

select,poll,epoll都是IO多路復用的機制。

?? ?I/O多路復用就通過一種機制,可以監(jiān)視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現(xiàn)會負責把數(shù)據(jù)從內核拷貝到用戶空間

Java中一個字符占多少個字節(jié),擴展再問int, long, double占多少字節(jié)

boolean 1 byte 1 char 2 int 4 float 4 long 8 double 8

一個字符兩個字節(jié),int 4 , long double 8

創(chuàng)建一個類的實例都有哪些辦法?

new ,反射。反序列化,Object().clone()方法

final/finally/finalize的區(qū)別?

final是類,變量,方法的修飾。類被修飾后不能再被繼承。變量和方法被修飾不能再被修改

finally是try...catch后執(zhí)行的finally,

finalize,在Object中的方法名,在此對象被回收前會執(zhí)行這個方法。(當使用文件流時,如果對象被回收,沒有關閉流,在底層就會實現(xiàn)內存泄露)

Session/Cookie的區(qū)別?

Session存在服務器端。

Cookie存在客戶端(瀏覽器上)。

HTTP是一種無狀態(tài)的協(xié)議,為了分辨鏈接是誰發(fā)起的,就需要我們自己去解決這個問題。不然有些情況下即使是同一個網站我們每打開一個頁面也都要登錄一下。而Session和Cookie就是為解決這個問題而提出來的兩個機制。

應用場景

日常登錄一個網站,今天輸入用戶名密碼登錄了,第二天再打開很多情況下就直接打開了。這個時候用到的一個機制就是cookie。

session的一個場景是購物車,添加了商品之后客戶端處可以知道添加了哪些商品,而服務器端如何判別呢,所以也需要存儲一些信息,這里就用到了session。

Cookie

通俗講,Cookie是訪問某些網站以后在本地存儲的一些網站相關的信息,下次再訪問的時候減少一些步驟。另外一個更準確的說法是:Cookies是服務器在本地機器上存儲的小段文本并隨每一個請求發(fā)送至同一個服務器,是一種在客戶端保持狀態(tài)的方案。

Cookie的主要內容包括:名字,值,過期時間,路徑和域。使用Fiddler抓包就可以看見,比方說我們打開百度的某個網站可以看到Headers包括Cookie,

可以看見是key, value的形式,也就是我們說的對應著的名字,值。過期時間是可以設置的,如果不設置,則瀏覽器關掉就消失了,是存儲在內存當中的,否則就是按照我們設置的時間來存儲在硬盤上的,當過期后自動清除,

Session

Session是存在服務器的一種用來存放用戶數(shù)據(jù)的類HashTable結構。

當瀏覽器 第一次發(fā)送請求時,服務器自動生成了一個HashTable和一個Session ID用來唯一標識這個HashTable,并將其通過響應發(fā)送到瀏覽器。

當瀏覽器第二次發(fā)送請求,

會將前一次服務器響應中的Session ID放在請求中一并發(fā)送到服務器上,服務器從請求中提取出Session ID,并和保存的所有Session ID進行對比,找到這個用戶對應的HashTable。

一般這個值會有一個時間限制,超時后毀掉這個值,默認是20分鐘。

Session的實現(xiàn)方式和Cookie有一定關系。試想一下,建立一個連接就生成一個session id,那么打開幾個頁面就好幾個了,這顯然不是我們想要的,那么該怎么區(qū)分呢?這里就用到了Cookie,我們可以把session id存在Cookie中,然后每次訪問的時候將Session id帶過去就可以識別了,是不是很方便~

區(qū)別

通過上面的簡單敘述,很容易看出來最明顯的不同是一個在客戶端一個在服務端。因為Cookie存在客戶端所以用戶可以看見,所以也可以編輯偽造,不是十分安全。

Session過多的時候會消耗服務器資源,所以大型網站會有專門的Session服務器,而Cookie存在客戶端所以沒什么問題。

域的支持范圍不一樣,比方說a.com的Cookie在a.com下都能用,而www.a.com的Session在api.a.com下都不能用,解決這個問題的辦法是JSONP或者跨域資源共享。

String/StringBuffer/StringBuilder的區(qū)別,擴展再問他們的實現(xiàn)?

String:字符串常量

String是不可變對象,在每次對String類進行改變的時候其實都等于生成了一個新的String對象,然后指向新的String對象,

所以經常改變內容的字符串最好不要用String類型,因為每次聲稱對象都會對系統(tǒng)性能產生影響.

StringBuffer類:

StringBuffer是可變字符串,在每次對StringBuffer對象進行改變時,會對StringBuffer對象本身進行操作,而不是生成新的對象,

再改變對象引用.所以,在字符串對象經常改變的情況下推薦使用StringBuffer類.String實現(xiàn)了equals()方法和hashCode()方法,

而StringBuffer沒有實現(xiàn).StringBuffer對字符串拼接效率較高.StringBuffer 中的方法大都采用了 synchronized 關鍵字進行修飾,

因此是線程安全的,而 StringBuilder 沒有這個修飾,可以被認為是線程不安全的。

StringBuilder類:

StringBuilder是JDK5.0以后提供的類,它和StringBuffer類等價,區(qū)別在于StringBuffer類是線程安全的,而StringBuilder是單線程的,

不提供同步,理論上效率更高.在單線程程序下,StringBuilder效率更快,因為它不需要加鎖,不具備多線程安全,而StringBuffer則每次都需要判斷鎖,效率相對更低

StringBuffer StringBuilder 類繼承自AbstractStringBuilder抽象類,實現(xiàn)Serializable序列化接口和CharSequence接口。

AbstractStringBuilder抽象類實現(xiàn)Appendabel,CharSequence接口。

Servlet的生命周期?

Servlet運行在Servlet容器中,其生命周期由容器來管理。

Servlet的生命周期通過javax.servlet.Servlet接口中的init()、service()和destroy()方法來表示

Servlet的生命周期包含了下面4個階段:

1.加載和實例化

2.初始化

3.請求處理

4.服務終止

Web服務器在與客戶端交互時Servlet的工作過程是:

1.在客戶端對web服務器發(fā)出請求

2.web服務器接收到請求后將其發(fā)送給Servlet

3.Servlet容器為此產生一個實例對象并調用ServletAPI中相應的方法來對客戶端HTTP請求進行處理,然后將處理的響應結果返回給WEB服務器.

4.web服務器將從Servlet實例對象中收到的響應結構發(fā)送回客戶端.

servlet的生命周期:

1.加載和實例化

  Servlet容器負責加載和實例化Servlet。當Servlet容器啟動時,或者在容器檢測到需要這個Servlet來響應第一個請求時,創(chuàng)建Servlet實例。當Servlet容器啟動后,它必須要知道所需的Servlet類在什么位置,Servlet容器可以從本地文件系統(tǒng)、遠程文件系統(tǒng)或者其他的網絡服務中通過類加載器加載Servlet類,成功加載后,容器創(chuàng)建Servlet的實例。因為容器是通過Java的反射API來創(chuàng)建Servlet實例,調用的是Servlet的默認構造方法(即不帶參數(shù)的構造方法),所以我們在編寫Servlet類的時候,不應該提供帶參數(shù)的構造方法。

2.初始化

  在Servlet實例化之后,容器將調用Servlet的init()方法初始化這個對象。初始化的目的是為了讓Servlet對象在處理客戶端請求前完成一些初始化的工作,如建立數(shù)據(jù)庫的連接,獲取配置信息等。對于每一個Servlet實例,init()方法只被調用一次。在初始化期間,Servlet實例可以使用容器為它準備的ServletConfig對象從Web應用程序的配置信息(在web.xml中配置)中獲取初始化的參數(shù)信息。在初始化期間,如果發(fā)生錯誤,Servlet實例可以拋出ServletException異常或者UnavailableException異常來通知容器。ServletException異常用于指明一般的初始化失敗,例如沒有找到初始化參數(shù);而UnavailableException異常用于通知容器該Servlet實例不可用。例如,數(shù)據(jù)庫服務器沒有啟動,數(shù)據(jù)庫連接無法建立,Servlet就可以拋出UnavailableException異常向容器指出它暫時或永久不可用。

I.如何配置Servlet的初始化參數(shù)?

???在web.xml中該Servlet的定義標記中,比如:

????<servlet>

?????????<servlet-name>TimeServlet</servlet-name>

?????????<servlet-class>com.allanlxf.servlet.basic.TimeServlet</servlet-class>

????????<init-param>

????????????<param-name>user</param-name>

????????????<param-value>username</param-value>

???????</init-param>

???????<init-param>

???????????<param-name>blog</param-name>

???????????<param-value>http://。。。</param-value>

???????</init-param>

????</servlet>

配置了兩個初始化參數(shù)user和blog它們的值分別為username和http://。。。, 這樣以后要修改用戶名和博客的地址不需要修改Servlet代碼,只需修改配置文件即可。

II.如何讀取Servlet的初始化參數(shù)?

???????ServletConfig中定義了如下的方法用來讀取初始化參數(shù)的信息:

?public String getInitParameter(String name)

??????????參數(shù):初始化參數(shù)的名稱。

??????????返回:初始化參數(shù)的值,如果沒有配置,返回null。

III.init(ServletConfig)方法執(zhí)行次數(shù)

???????在Servlet的生命周期中,該方法執(zhí)行一次。

IV.init(ServletConfig)方法與線程

?????該方法執(zhí)行在單線程的環(huán)境下,因此開發(fā)者不用考慮線程安全的問題。

V.init(ServletConfig)方法與異常

??該方法在執(zhí)行過程中可以拋出ServletException來通知Web服務器Servlet實例初始化失敗。一旦ServletException拋出,

??Web服務器不會將客戶端請求交給該Servlet實例來處理,而是報告初始化失敗異常信息給客戶端,該Servlet實例將被從內存中銷毀。如果在來新的請求,

??Web服務器會創(chuàng)建新的Servlet實例,并執(zhí)行新實例的初始化操作

3.請求處理

  Servlet容器調用Servlet的service()方法對請求進行處理。要注意的是,在service()方法調用之前,init()方法必須成功執(zhí)行。在service()方法中,Servlet實例通過ServletRequest對象得到客戶端的相關信息和請求信息,在對請求進行處理后,調用ServletResponse對象的方法設置響應信息。在service()方法執(zhí)行期間,如果發(fā)生錯誤,Servlet實例可以拋出ServletException異?;蛘遀navailableException異常。如果UnavailableException異常指示了該實例永久不可用,Servlet容器將調用實例的destroy()方法,釋放該實例。此后對該實例的任何請求,都將收到容器發(fā)送的HTTP 404(請求的資源不可用)響應

。如果UnavailableException異常指示了該實例暫時不可用,那么在暫時不可用的時間段內,對該實例的任何請求,都將收到容器發(fā)送的HTTP 503(服務器暫時忙,不能處理請求)響應。

I. service()方法的職責

?????service()方法為Servlet的核心方法,客戶端的業(yè)務邏輯應該在該方法內執(zhí)行,典型的服務方法的開發(fā)流程為:

????解析客戶端請求-〉執(zhí)行業(yè)務邏輯-〉輸出響應頁面到客戶端

II.service()方法與線程

????為了提高效率,Servlet規(guī)范要求一個Servlet實例必須能夠同時服務于多個客戶端請求,即service()方法運行在多線程的環(huán)境下,

????Servlet開發(fā)者必須保證該方法的線程安全性。

III.service()方法與異常

????service()方法在執(zhí)行的過程中可以拋出ServletException和IOException。其中ServletException可以在處理客戶端請求的過程中拋出,

????比如請求的資源不可用、數(shù)據(jù)庫不可用等。一旦該異常拋出,容器必須回收請求對象,并報告客戶端該異常信息。IOException表示輸入輸出的錯誤,

????編程者不必關心該異常,直接由容器報告給客戶端即可。

編程注意事項說明:

1) 當Server Thread線程執(zhí)行Servlet實例的init()方法時,所有的Client Service Thread線程都不能執(zhí)行該實例的service()方法,更沒有線程能夠執(zhí)行該實例的destroy()方法,因此Servlet的init()方法是工作在單線程的環(huán)境下,開發(fā)者不必考慮任何線程安全的問題。

2) 當服務器接收到來自客戶端的多個請求時,服務器會在單獨的Client Service Thread線程中執(zhí)行Servlet實例的service()方法服務于每個客戶端。此時會有多個線程同時執(zhí)行同一個Servlet實例的service()方法,因此必須考慮線程安全的問題。

3) 請大家注意,雖然service()方法運行在多線程的環(huán)境下,并不一定要同步該方法。而是要看這個方法在執(zhí)行過程中訪問的資源類型及對資源的訪問方式。分析如下:

????i. 如果service()方法沒有訪問Servlet的成員變量也沒有訪問全局的資源比如靜態(tài)變量、文件、數(shù)據(jù)庫連接等,而是只使用了當前線程自己的資源,比如非指向全局資源的臨時變量、request和response對象等。該方法本身就是線程安全的,不必進行任何的同步控制。

??????ii. 如果service()方法訪問了Servlet的成員變量,但是對該變量的操作是只讀操作,該方法本身就是線程安全的,不必進行任何的同步控制。

??????iii. 如果service()方法訪問了Servlet的成員變量,并且對該變量的操作既有讀又有寫,通常需要加上同步控制語句。

??????iv. 如果service()方法訪問了全局的靜態(tài)變量,如果同一時刻系統(tǒng)中也可能有其它線程訪問該靜態(tài)變量,如果既有讀也有寫的操作,通常需要加上同步控制語句。

??????v. 如果service()方法訪問了全局的資源,比如文件、數(shù)據(jù)庫連接等,通常需要加上同步控制語句。

4.服務終止

  當容器檢測到一個Servlet實例應該從服務中被移除的時候,容器就會調用實例的destroy()方法,以便讓該實例可以釋放它所使用的資源,保存數(shù)據(jù)到持久存儲設備中。當需要釋放內存或者容器關閉時,容器就會調用Servlet實例的destroy()方法。在destroy()方法調用之后,容器會釋放這個Servlet實例,該實例隨后會被Java的垃圾收集器所回收。如果再次需要這個Servlet處理請求,Servlet容器會創(chuàng)建一個新的Servlet實例。  在整個Servlet的生命周期過程中,創(chuàng)建Servlet實例、調用實例的init()和destroy()方法都只進行一次,當初始化完成后,Servlet容器會將該實例保存在內存中,通過調用它的service()方法,為接收到的請求服務。

如何用Java分配一段連續(xù)的1G的內存空間?需要注意些什么?

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*1024*1024);

Java有自己的內存回收機制,但為什么還存在內存泄露的問題呢?

JAVA是支持垃圾回收機制的,在這樣的一個背景下,內存泄露又被稱為“無意識的對象保持”。如果一個對象引用被無意識地保留下來,那么垃圾回收器不僅不會處理這個對象,而且也不處理被這個對象引用的其它對象?!皟却嫘孤丁本褪莾却嬷心承﹥却娌豢杀换厥?。內存對象明明已經不需要的時候,還仍然保留著這塊內存和它的訪問方式(引用)

舉個例子:

如果對一個棧(Stack類)先是進行入棧操作,之后再進行出棧操作,那么彈出來的對象將不會被當做垃圾回收,即使使用棧的客戶程序不再引用這些對象,因為棧內部存在著對這些已彈出對象的引用,這是Stack類自己管理內存的機制所決定的。

什么是java序列化,如何實現(xiàn)java序列化?(寫一個實例)?

序列化:

可以將一個對象保存到一個文件,所以可以通過流的方式在網絡上傳輸,可以將文件的內容讀取,轉化為一個對象。

處理對象流的機制,所謂對象流也就是將對象的內容進行流化。可以對流化后的對象進行讀寫操作,也可將流化后的對象傳輸于網絡之間。

序列化是為了解決在對對象流進行讀寫操作時所引發(fā)的問題。

序列化的實現(xiàn):

將需要被序列化的類實現(xiàn)Serializable 接口,該接口沒有需要實現(xiàn)的方法,implements Serializable 只是為了標注該對象是可被序列化的,

然后使用一個輸出流(如:FileOutputStream)來構造一個ObjectOutputStream(對象流)對象,

接著,使用ObjectOutputStream 對象的writeObject(Object obj)方法就可以將參數(shù)為obj 的對象寫出(即保存其狀態(tài)),要恢復的話則用輸入流。

import java.io.Serializable;

public class Person implements Serializable {

????//本類可以序列化

????private String name ;

????private int age ;

????public Person(String name,int age){

????????this.name = name ; this.age = age ;

????}

????public String toString(){

????????return "姓名:" + this.name + ",年齡" + this.age ;

????}

}

String s = new String("abc");創(chuàng)建了幾個 String Object?

2

1.在虛擬機棧中為String類型的s分配內存

2.在堆中為分配一塊內存用來保存"abc"

3.將堆中指向"abc"的地址賦值給s

String temp="abc";

String s=new String(temp)

三、JVM

JVM堆的基本結構。

JVM中內存

JVM中內存通常劃分為兩個部分,分別為堆內存與棧內存,棧內存主要用執(zhí)行線程方法存放本地臨時變量與線程中方法執(zhí)行時候需要的引用對象地址。JVM所有的對象信息都存放在堆內存中,相比棧內存,堆內存可以大的多,所以JVM一直通過對堆內存劃分不同的功能區(qū)塊實現(xiàn)對堆內存中對象管理。

堆內存不夠最常見的錯誤就是OOM(OutOfMemoryError)

棧內存溢出最常見的錯誤就是StackOverflowError,程序有遞歸調用時候最容易發(fā)生

在JDK7以及其前期的JDK版本中,堆內存通常被分為三塊區(qū)域

Nursery內存(younggeneration)、長時內存(old generation)、永久內存(Permanent Generation forVM Matedata),

顯示如下圖:

Eden????伊甸園

Survivor???幸存者

Old Generation for older obiects老一輩的老年人

Permanent Generation for VM metadata永久生成虛擬機元數(shù)據(jù)

其中最上一層是Nursery內存,一個對象被創(chuàng)建以后首先被放到Nursery中的Eden內存中,如果存活期超兩個Survivor之后就會被轉移到長時內存中(OldGeneration)中永久內存中存放著對象的方法、變量等元數(shù)據(jù)信息。通過如果永久內存不夠,我們就會得到如下錯誤:java.lang.OutOfMemoryError: PermGen,而在JDK8中情況發(fā)生了明顯的變化,就是一般情況下你都不會得到這個錯誤,原因在于JDK8中把存放元數(shù)據(jù)中的永久內存從堆內存中移到了本地內存(nativememory)中,JDK8中JVM堆內存結構就變成了如下:

這樣永久內存就不再占用堆內存,它可以通過自動增長來避免JDK7以及前期版本中常見的永久內存錯誤java.lang.OutOfMemoryError:PermGen),也許這個就是你的DK升級到JDK8的理由之一吧。當然JDK8也提供了一個新的設置Matespace內存大小的參數(shù),通過這個參數(shù)可以設置Matespace內存大小,這樣我們可以根據(jù)自己項目的實際情況,避免過度浪費本地內存,達到有效利用。

-XX:MaxMetaspaceSize=128m 設置最大的元內存空間128兆

注意:如果不設置JVM將會根據(jù)一定的策略自動增加本地元內存空間如果你設置的元內存空間過小,你的應用程序可能得到以下錯誤:

java.lang.OutOfMemoryError: Metadata space

JVM的垃圾算法有哪幾種?CMS垃圾回收的基本流程?

常用的垃圾回收算法有如下四種:

標記-清除、復制、標記-整理和分代收集。

標記-清除算法

從算法的名稱上可以看出,這個算法分為兩部分,標記和清除。

首先標記出所有需要被回收的對象,然后在標記完成后統(tǒng)一回收掉所有被標記的對象。

這個算法簡單,但是有兩個缺點:一是標記和清除的效率不是很高;二是標記和清除后會產生很多的內存碎片,導致可用的內存空間不連續(xù),當分配大對象的時候,沒有足夠的空間時不得不提前觸發(fā)一次垃圾回收。

復制算法

這個算法將可用的內存空間分為大小相等的兩塊,每次只是用其中的一塊,當這一塊被用完的時候,就將還存活的對象復制到另一塊中,然后把原已使用過的那一塊內存空間一次回收掉。這個算法常用于新生代的垃圾回收。

復制算法解決了標記-清除算法的效率問題,以空間換時間,但是當存活對象非常多的時候,復制操作效率將會變低,而且每次只能使用一半的內存空間,利用率不高。

標記-整理算法

這個算法分為三部分:一是標記出所有需要被回收的對象;二是把所有存活的對象都向一端移動;三是把所有存活對象邊界以外的內存空間都回收掉。

標記-整理算法解決了復制算法多復制效率低、空間利用率低的問題,同時也解決了內存碎片的問題。

分代收集算法

根據(jù)對象生存周期的不同將內存空間劃分為不同的塊,然后對不同的塊使用不同的回收算法。一般把Java堆分為新生代和老年代,新生代中對象的存活周期短,只有少量存活的對象,所以可以使用復制算法,而老年代中對象存活時間長,而且對象比較多,所以可以采用標記-清除和標記-整理算法。

CMS:Concurrent Mark Sweep。并發(fā)標記掃描

看名字就知道,CMS是一款并發(fā)、使用標記-清除算法的gc。

CMS是針對老年代進行回收的GC。?

CMS有什么用?

CMS以獲取最小停頓時間為目的。在一些對響應時間有很高要求的應用或網站中,用戶程序不能有長時間的停頓,CMS 可以用于此場景。?

CMS如何執(zhí)行?

總體來說CMS的執(zhí)行過程可以分為以下幾個階段: ?

? ? ? ? ? 1 初始標記(STW)

? ? ? ? ? 2 并發(fā)標記

? ? ? ? ? 3 并發(fā)預清理

? ? ? ? ? 4 重標記(STW)

? ? ? ? ? 5 并發(fā)清理

? ? ? ? ? 6 重置

CMS缺點?

1.CMS回收器采用的基礎算法是Mark-Sweep。所有CMS不會整理、壓縮堆空間

2.CMS需要更多的CPU資源。

3.CMS需要更大的堆空間。

4.CMS回收器減少了回收的停頓時間,但是降低了堆空間的利用率。

JVM有哪些常用啟動參數(shù)可以調整,描述幾個?

-Xms:設置jvm內存的初始大小

-Xmx:設置jvm內存的最大值

-Xmn:設置新域的大?。ㄟ@個似乎只對jdk1.4來說是有效的,后來就廢棄了)

-Xss:設置每個線程的堆棧大小(也就是說,在相同物理內存下,減小這個值能生成更多的線程)

-XX:NewRatio:設置新域與舊域之比,如-XX:NewRatio=4就表示新域與舊域之比為1:4

-XX:NewSize:設置新域的初始值

-XX:MaxNewSize:設置新域的最大值

-XX:MaxPermSize:設置永久域的最大值

-XX:SurvivorRatio=n:設置新域中Eden區(qū)與兩個Survivor區(qū)的比值。(Eden區(qū)主要是用來存放新生的對象,而兩個Survivor區(qū)則用來存放每次垃圾回收后存活下來的對象)

JVM啟動參數(shù)使用中常見的錯誤:

java.lang.OutOfMemoryError相信很多開發(fā)人員都用到過,這個主要就是JVM參數(shù)沒有配好引起的,但是這種錯誤又分兩種:java.lang.OutOfMemoryError:Javaheapspace和java.lang.OutOfMemoryError:PermGenspace,其中前者是有關堆內存的內存溢出,可以同過配置-Xms和-Xmx參數(shù)來設置,而后者是有關永久域的內存溢出,可以通過配置-XX:MaxPermSize來設置。

如何查看JVM的內存使用情況?

jinfo:可以輸出并修改運行時的java 進程的opts。

jps:與unix上的ps類似,用來顯示本地的java進程,可以查看本地運行著幾個java程序,并顯示他們的進程號。

jstat:一個極強的監(jiān)視VM內存工具??梢杂脕肀O(jiān)視VM內存內的各種堆和非堆的大小及其內存使用量。

jmap:打印出某個java進程(使用pid)內存內的所有'對象'的情況(如:產生那些對象,及其數(shù)量)。

jconsole:一個java GUI監(jiān)視工具,可以以圖表化的形式顯示各種數(shù)據(jù)。并可通過遠程連接監(jiān)視遠程的服務器VM。

詳細:在使用這些工具前,先用JPS命令獲取當前的每個JVM進程號,然后選擇要查看的JVM。

jstat工具特別強大,有眾多的可選項,詳細查看堆內各個部分的使用量,以及加載類的數(shù)量。使用時,需加上查看進程的進程id,和所選參數(shù)。以下詳細介紹各個參數(shù)的意義。

jstat -class pid:顯示加載class的數(shù)量,及所占空間等信息。

jstat -compiler pid:顯示VM實時編譯的數(shù)量等信息。

jstat -gc pid:可以顯示gc的信息,查看gc的次數(shù),及時間。其中最后五項,分別是young gc的次數(shù),young gc的時間,full gc的次數(shù),full gc的時間,gc的總時間。

jstat -gccapacity:可以顯示,VM內存中三代(young,old,perm)對象的使用和占用大小,如:PGCMN顯示的是最小perm的內存使用量,PGCMX顯示的是perm的內存最大使用量,PGC是當前新生成的perm內存占用量,PC是但前perm內存占用量。其他的可以根據(jù)這個類推, OC是old內純的占用量。

jstat -gcnew pid:new對象的信息。

jstat -gcnewcapacity pid:new對象的信息及其占用量。

jstat -gcold pid:old對象的信息。

jstat -gcoldcapacity pid:old對象的信息及其占用量。

jstat -gcpermcapacity pid: perm對象的信息及其占用量。

jstat -util pid:統(tǒng)計gc信息統(tǒng)計。

jstat -printcompilation pid:當前VM執(zhí)行的信息。

除了以上一個參數(shù)外,還可以同時加上 兩個數(shù)字,如:jstat -printcompilation3024 2506是每250毫秒打印一次,一共打印6次,還可以加上-h3每三行顯示一下標題。

jmap是一個可以輸出所有內存中對象的工具,甚至可以將VM 中的heap,以二進制輸出成文本。

命令:jmap -dump:format=b,file=heap.bin <pid>

file:保存路徑及文件名

pid:進程編號

jmap -histo:live??pid| less :堆中活動的對象以及大小

jmap -heap pid : 查看堆的使用狀況信息

jinfo:的用處比較簡單,就是能輸出并修改運行時的java進程的運行參數(shù)。用法是jinfo -opt pid 如:查看2788的MaxPerm大小可以用 jinfo -flag MaxPermSize 2788。

jconsole是一個用java寫的GUI程序,用來監(jiān)控VM,并可監(jiān)控遠程的VM,非常易用,而且功能非常強。使用方法:命令行里打 jconsole,選則進程就可以了。

JConsole中關于內存分區(qū)的說明。

Eden Space (heap): 內存最初從這個線程池分配給大部分對象。

Survivor Space (heap):用于保存在eden space內存池中經過垃圾回收后沒有被回收的對象。

Tenured Generation (heap):用于保持已經在 survivor space內存池中存在了一段時間的對象。

Permanent Generation (non-heap): 保存虛擬機自己的靜態(tài)(refective)數(shù)據(jù),例如類(class)和方法(method)對象。Java虛擬機共享這些類數(shù)據(jù)。這個區(qū)域被分割為只讀的和只寫的,

Code Cache (non-heap):HotSpot Java虛擬機包括一個用于編譯和保存本地代碼(native code)的內存,叫做“代碼緩存區(qū)”(code cache)

jstack ( 查看jvm線程運行狀態(tài),是否有死鎖現(xiàn)象等等信息) : jstack pid : thread dump

jstat -gcutil??pid??1000 100??: 1000ms統(tǒng)計一次gc情況統(tǒng)計100次;

Java程序是否會內存溢出,內存泄露情況發(fā)生?舉幾個例子。

OutOfMemoryError: PermGen space

Permanent Generation space 這個區(qū)域主要用來保存加來的Class的一些信息,在程序運行期間屬于永久占用的,Java的GC不會對他進行釋放,所以如果啟動的程序加載的信息比較大,超出了這個空間的大小,就會發(fā)生溢出錯誤;

解決的辦法無非就是增加空間分配了——增加java虛擬機中的XX:PermSize和XX:MaxPermSize參數(shù)的大小,其中XX:PermSize是初始永久保存區(qū)域大小,XX:MaxPermSize是最大永久保存區(qū)域大小。

2、OutOfMemoryError:Java heap space

heap 是Java內存中的堆區(qū),主要用來存放對象,當對象太多超出了空間大小,GC又來不及釋放的時候,就會發(fā)生溢出錯誤。

Java中對象的創(chuàng)建是可控的,但是對象的回收是由GC自動的,一般來說,當已存在對象沒有引用(即不可達)的時候,GC就會定時的來回收對象,釋放空間。但是因為程序的設計問題,導致對象可達但是又沒有用(即前文提到的內存泄露),當這種情況越來越多的時候,問題就來了。

針對這個問題,我們需要做一下兩點:

1、檢查程序,減少大量重復創(chuàng)建對象的死循環(huán),減少內存泄露。

2、增加Java虛擬機中Xms(初始堆大?。┖蚗mx(最大堆大?。﹨?shù)的大小。

3、StackOverFlowError

stack是Java內存中的棧空間,主要用來存放方法中的變量,參數(shù)等臨時性的數(shù)據(jù)的,發(fā)生溢出一般是因為分配空間太小,或是執(zhí)行的方法遞歸層數(shù)太多創(chuàng)建了占用了太多棧幀導致溢出。

針對這個問題,除了修改配置參數(shù)-Xss參數(shù)增加線程棧大小之外,優(yōu)化程序是尤其重要。

你常用的JVM配置和調優(yōu)參數(shù)都有哪些?分別什么作用?

堆配置

-Xms:初始堆大小

-Xms:最大堆大小

-XX:NewSize=n:設置年輕代大小

-XX:NewRatio=n:設置年輕代和年老代的比值。如:為3表示年輕代和年老代比值為1:3,年輕代占整個年輕代年老代和的1/4

-XX:SurvivorRatio=n:年輕代中Eden區(qū)與兩個Survivor區(qū)的比值。注意Survivor區(qū)有兩個。如3表示Eden: 3 Survivor:2,一個Survivor區(qū)占整個年輕代的1/5

-XX:MaxPermSize=n:設置持久代大小

1、一般初始堆和最大堆設置一樣,因為:現(xiàn)在內存不是什么稀缺的資源,但是如果不一樣,從初始堆到最大堆的過程會有一定的性能開銷,所以一般設置為初始堆和最大堆一樣。64位系統(tǒng)理論上可以設置為無限大,但是一般設置為4G,因為如果再大,JVM進行垃圾回收出現(xiàn)的暫停時間會比較長,這樣全GC過長,影響JVM對外提供服務,所以不能太大。一般設置為4G。

2、-XX:NewRaio和-XX:SurvivorRatio這兩個參數(shù),都是設置年輕代和年老代的大小的,設置一個即可,第一是設置年輕代的大小,第二個是設置比值,理論上設置一個既可以滿足需求

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

相關閱讀更多精彩內容

  • JAVA面試題 1、作用域public,private,protected,以及不寫時的區(qū)別答:區(qū)別如下:作用域 ...
    JA尐白閱讀 1,250評論 1 0
  • 所有知識點已整理成app app下載地址 J2EE 部分: 1.Switch能否用string做參數(shù)? 在 Jav...
    侯蛋蛋_閱讀 2,700評論 1 4
  • 第二部分 自動內存管理機制 第二章 java內存異常與內存溢出異常 運行數(shù)據(jù)區(qū)域 程序計數(shù)器:當前線程所執(zhí)行的字節(jié)...
    小明oh閱讀 1,275評論 0 2
  • 小編費力收集:給你想要的面試集合 1.C++或Java中的異常處理機制的簡單原理和應用。 當JAVA程序違反了JA...
    八爺君閱讀 5,172評論 1 114
  • JAVA相關基礎知識 1、面向對象的特征有哪些方面 1.抽象: 抽象就是忽略一個主題中與當前目標無關的那些方面,以...
    yangkg閱讀 737評論 0 1

友情鏈接更多精彩內容