說下物件的創建方法?物件的記憶體布局?物件的訪問定位?
四種不同的方法創建物件
1、用new陳述句創建物件,這是最常用的創建物件的方式;
2、呼叫物件的clone方法,
MyObject obj =new MyObject();
MyObject objs= obj.clone();
使用clone方法克隆一個物件的步驟:
1)被克隆的類要實作Cloneable介面;
2)被克隆的類要重寫clone方法;
1 class Obj implements Cloneable{
2 private Date birth = new Date();
3 public Date getBirth(){
4 return birth;
5 }
6 public void setBirth(){
7 this.birth=birth;
8 }
9 public void changeDate(){
10 this.birth.setMonth(4);
11 }
12 public Object clone(){
13 Obj o = null;//o指向了復制后的新物件
14 try{
15 o=(Obj)super.clone();//實作淺復制
16 }catch(CloneNotSupportedException e){
17 e.printStackTrace();
18 }
19 //實作深復制
20 o.birth = (Date)this.getBirth().clone();
21 return o;
22 }
23 }
24 public class TestRef{
25 public static void main(String[] args){
26 Obj a = new Obj();
27 Obj b = (Obj)a.clone();
28 b.changeDate();
29 System.out.println("a="+a.getBirth());
30 System.out.println("b="+b.getBirth());
31 }
32 }
33 //程式運行結果:
34 //a=Sun Jul 13 23:58:56 CST 2013
35 //b=Mon May 13 23:58:56 CST 2013
那么在編程時,如何選擇使用哪種復制方式呢?首先,檢查類有無非基本型別(即物件)的資料成員,若沒有,則回傳super.clone()即可;若有,確保類中包含的所有非基本型別的成員變數都實作了深復制,
Object o = super.clone();//先執行淺復制
對每一個物件attr執行以下陳述句:
o.attr = this.getAttr().clone();
最后回傳,
需要注意的是,clone方法的保護機制在Oject中clone()是被宣告為protected的,以User類為例,通過宣告為protected,就可以保證只有User類里面才能“克隆”User物件,
引申:淺復制和深復制有什么區別?
淺復制:被復制的物件的所有變數都含有與原來物件相同的值,而所有其他物件的參考仍然指向原來的物件,換而言之,淺復制僅僅復制所考慮的物件,而不復制他參考的物件,
深復制:被復制物件的所有變數都含有與原來物件相同的值,除去那些參考其他物件的變數,那些參考其他物件的變數將指向被復制的新物件,而不再是原有的那些被參考的物件,換而言之,深復制把復制的物件所參考的物件都復制了一遍,
擴展:
原型模式主要用于物件的復制,實作一個介面(實作Cloneable介面),重寫一個方法(重寫Object類中的clone方法),即完成了原型模式,
原型模式中的拷貝分為“淺拷貝”和“深拷貝”:
淺拷貝:對值型別的成員變數進行值的復制,對參考型別的成員變數只復制參考,不復制參考的物件,
深拷貝:對值型別的成員變數進行值的復制,對參考型別的成員變數也進行參考物件的復制,
(Object類中clone方法只會拷貝物件中的基本資料型別的值,對于資料中、容器物件、參考物件等都不會拷貝,這就是淺拷貝,如果要實作深拷貝,必須將原型模式中的陣列、容器物件、參考物件等另行拷貝,)
原型模式的優點:
1)如果創建新的物件比較復雜時,可以利用原型模式簡化物件的創建程序,
2)使用原型模式創建物件比直接new一個物件在性能上要好得多,因為Object類的clone方法是一個本地方法,它直接操作記憶體中的二進制流,特別是復制大物件時,性能的差別非常明顯,
原型模式的適用場景:
因為以上優點,所以在需要重復地創建相似物件時可以考慮使用原型模式,比如需要在一個回圈體內創建物件,假如物件創建程序比較復雜或者回圈次數很多的話,使用原型模式不但可以簡化創建程序,而且可以使系統的整體性能提高很多,
3、運行反射手段,使用Class.forName()
MyObject object =(MyObject)Class.forName("subin.rnd.MyObject").newInstance();
4、運用反序列化手段,呼叫java.io.ObjectInputStream物件的readObject()方法,
什么是記憶體泄漏和記憶體溢位?
記憶體泄漏:指一個不再被程式使用的物件或變數還在記憶體中占有存盤空間,
兩種情況:
1)在堆中申請的空間沒有被釋放;
2)物件已不再被使用但還仍然在記憶體中保留著;
記憶體泄露的典型例子是一個沒有重寫hashCode和equals方法的Key類在HashMap中保存的情況,最后會生成很多重復的物件,所有的記憶體泄漏最后都會拋出OutOfMemoryError例外,
造成記憶體泄漏的原因:
1)靜態集合類
2)各種連接,例如資料庫連接等
3)監聽器
4)變數不合理的作用域
記憶體泄露的解決方案:
1)避免在回圈中創建物件;
2)盡早釋放無用物件的參考;
3)盡量少用靜態變數,因為靜態變數存放在永久代(方法區),永久代基本不參與垃圾回收;
4)使用字串處理,避免使用String,應大量使用StringBuffer,每一個String物件都得獨立占用記憶體一塊區域;
在實際場景中,你怎么查找記憶體泄漏?
可以使用 Jconsole,
記憶體溢位:指程式運行程序中無法申請到足夠的記憶體而導致的記憶體的一種錯誤,
記憶體溢位的幾種情況(OOM例外):
OutOfMemoryError例外:
除了程式計數器外,虛擬機記憶體的其他幾個運行時區域都有發生OutOfMemoryError(OOM)例外的可能,
1、虛擬機堆疊和本地方法堆疊溢位
如果執行緒請求的堆疊深度大與虛擬機所允許的最大深度,將拋出StackOverflowError例外,
如果虛擬機在擴展堆疊時無法申請到足夠的空間,則拋出OutOfMemoryError例外,
2、堆溢位
一般的例外資訊:java.lang.OutOfMemoryError:Java heap spaces,
解決方案:
出現這種例外,一般手段是先通過記憶體影像分析工具(如Eclipse Memory Analyzer)對dump出來的堆轉存快照進行分析,重點是確認記憶體中的物件是否是必要的,先分清是因為記憶體泄漏(Memory Leak)還是記憶體溢位(Memory Overflow),
如果是記憶體泄露,可進一步通過工具查看泄露物件到GC Roots的參考鏈,于是就能找到泄露物件是通過怎樣的路徑與GC Roots相關聯并導致垃圾收集器無法自動回收,
如果不存在泄露,那就應該檢查虛擬機的引數(-Xmx與-Xms)的設定是否適當,
3、方法區溢位
例外資訊:java.lang.OutOfMemoryError:PermGen space,
4、運行時常量池溢位
例外資訊:java.lang.OutOfMemoryError:PermGen space,
如果要向運行時常量池中添加內容,最簡單的做法就是使用String.intern()這個Native方法,該方法的作用是:如果池中已經包含一個等于此String的字串,則回傳代表池中這個字串的String物件;否則,將此String物件包含的字串添加到常量池中,并且回傳此String物件的參考,由于常量池分配在方法區內,我們可以通過-XX:PermSize和-XX:MaxPermSize限制方法區的大小,從而間接限制其中常量池的容量,
導致記憶體溢位的原因:
1)記憶體中加載的資料量過大,如一次從資料庫取出過多資料;
2)集合類中有對物件的參考,使用完后未清空,使得JVM不能回收;
3)代碼中存在死回圈或回圈產生過多重復的物件物體;
4)啟動引數記憶體值設定的大小,
記憶體溢位的解決方法:
第一步,修改JVM啟動引數,直接增加記憶體,(-Xms,-Xmx引數一定不要忘記加,一般要將-Xms和-Xmx選項設定為相同,以避免在每次GC后調整堆的大小;建議堆的最大值設定為可用的記憶體的最大值的80%),
第二步,檢查錯誤日志,查看“OutOfMemory”錯誤前是否有其他例外或錯誤,
第三步,對代碼進行走查和分析,找出可能發生記憶體溢位的位置,
第四步,使用記憶體查看工具動態查看記憶體使用情況(Jconsole),
如何減少GC出現的次數?(Java記憶體管理)
1)物件不用時最好顯式置為NULL
一般而言,為NULL的物件都會被作為垃圾處理,所以將不同的物件顯式地設為NULL,有利于GC收集器判定垃圾,從而提高了GC的效率,
2)盡量少用System.gc()
此函式建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數,
3)盡量少用靜態變數
靜態變數屬于全域變數,不會被GC回收,他們會一直占用記憶體,
4)盡量使用StringBuffer,而不用String來累加字串,
由于String是固定長度的字串,累加String物件時,并非在一個String物件中擴增,而是重新創建新的String物件,如str5=str1+str2+str3+str4,這條陳述句執行程序中會產生多個垃圾物件,因為對次作“+”操作時都必須產生新String物件,但這些過渡物件對系統來說沒有實際意義的,只會增加更多的垃圾,避免這種情況可以改用StringBuffer來累加字串,因StringBuffer是可變長的,它在原有基礎上進行擴增,不會產生中間物件,
5)分散物件創建或洗掉的時間
集中在短時間內大量創建新物件,特別是大物件,會導致突然需要大量記憶體,JVM在面臨這種情況時,只能進行主GC,已回收記憶體或整合記憶體碎片,從而增加主GC的頻率,
集中洗掉物件,道理也是一樣的,它使得突然出現了大量的垃圾物件,空閑空間必然減少,從而大大增加了下一次創建新物件時強制主GC的機會,
6)盡量少用finalize函式,因為它會加大GC的作業量,因此盡量少用finalize方式回收資源,
7)如果需要使用經常用到的照片,可以使用軟參考型別,它可以盡可能將圖片保存在記憶體中,供程式呼叫,而不引起OutOfMemory,
8)能用基本型別如int,long,就不用Integer,Long物件
基本型別變數占用的記憶體資源比相應包裝類物件占用的少得多,如果沒有必要,最好使用基本變數,
9)增大-Xmx的值,
陣列多大放在JVM老年代?永久帶物件如何GC?如果想不被GC怎么辦?如果想在GC中生存一次怎么辦?
虛擬機提供了一個-XX:PretenureSizeThreshold引數(通常是3MB),令大于這個設定值的物件直接在老年代分配,這樣做的目的是避免在Eden區及兩個Survivor區之間發生大量的記憶體復制,
垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(FullGC),如果仔細查看垃圾回收器的輸出資訊,就會發現永久代也是被回收的,這就是為什么正確的永久代大小對避免FullGC是非常重要的,
讓物件實作finalize()方法,一次物件的自我拯救,
JVM常見的啟動引數有哪些?
-Xms:設定堆的最小值,
-Xmx:設定堆的最大值,
-Xmn:設定新生代的大小,
-Xss:設定每個執行緒的堆疊大小,
-XX:NewSize:設定新生代的初始值
-XX:MaxNewSize:設定新生代的最大值
-XX:PermSize:設定永久代的初始值
-XX:MaxPermSize:設定永久代的最大值
-XX:SurvivorRatio:年輕代中Eden區與Survivor區的大小比值
-XX:PretenureSizeThreshold:零大于這個設定值的物件直接在老年代分配,
說下幾種常用的記憶體除錯工具:jps、jump、jhat、jstack、jconsole、jstat
Java記憶體泄露的問題調查方法:jmap,jstack的使用等等,
jps:查看虛擬機行程的狀況,如行程ID,
jmap:用于生成堆轉儲快照檔案(某一時刻的),
jhat:對于生成的堆轉儲快照檔案進行分析,
jstack:用來生成執行緒快照(某一時刻的),生成執行緒快照的主要目的是定位執行緒長時停頓的原因(如死鎖,死回圈,等待I/O等),通過查看各個執行緒的呼叫堆疊,就可以知道沒有回應的執行緒在后臺做了什么或者等待什么資源,
jstat:虛擬機統計資訊監視工具,如顯示垃圾收集的情況,記憶體使用得情況,
Jconosole:主要是記憶體監控和執行緒監控,記憶體監控:可以顯示記憶體的使用情況,執行緒監控:遇到執行緒停頓時,可以使用這個功能,
描述Java類加載器的作業原理及其組織結構
Java類加載器的作用就是在運行時加載類,
Java類加載器基于三個機制:委托性、可見性和單一性,
1、委托機制是指雙親委派模型,當一個類加載和初始化的時候,類僅在有需要加載的時候被加載,假設你有一個應用需要的類叫作abc.class,首先加載這個類的請求由Application類加載器委托給它的父類加載器Extension類加載器,然后再委托給Bootstrap類加載器,Bootstrap類加載器會先看看rt.jar中有沒有這個類,因為并沒有這個類,所以這個請求又回到Extension類加載器,它會查看jre/lib/ext目錄下有沒有這個類,如果這個類被Extension類加載器找到了,那么它將被加載,而Application類加載器不會加載這個類;而如果這個類沒有被Extension類加載器找到,那么再由Application類加載器從classpath中尋找,如果沒找到,就會拋出例外,
雙親委派模型機制的優點就是能夠提高軟體系統的安全性,因為在此機制下,用戶自定義的類加載器不可能加載本應該由父類加載器加載的可靠類,從而防止不可靠的惡意代碼代替由父類加載器加載的可靠代碼,如java.lang.Object類總是由根類加載器加載的,其他任何用戶自定義的類加載器都不可能加載含有惡意代碼的java.lang.Object類,
2、可見性原理是子類的加載器可以看見所有的父類加載器加載的類,而父類加載器看不到子類加載器加載的類,
3、單一性原理是指僅加載一個類一次,這是由委托機制確保子類加載器不會再次加載父類加載器加載過的類,
Java的類加載器有三個,對應Java的三種類:
Bootstrap Loader //負責加載系統類(指的是內置類,像String)
ExtClassLoader //負責加載擴展類(就是繼承類和實作類)
AppClassLoader //負責加載應用類(程式員自定義的類)
Java提供了顯式加載類的API:Class.forName(classname),
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/24684.html
標籤:其他
上一篇:樹---序列化二叉樹
