1、既然有GC機制,為什麼還會有記憶體洩漏的情況?
理論上Java因為有垃圾回收機制(GC )不會有記憶體外洩問題(這也是Java被廣泛使用於伺服器端程式設計的重要原因)。然而在實際開發中,可能會存在無用但可達的對象,這些對像不能被GC回收,因此也會導致記憶體外洩的發生。
例如hibernate的Session(一級快取)中的物件屬於持久態,垃圾回收器是不會回收這些物件的,然而這些物件中可能存在無用的垃圾對象,如果不及時關閉(close )或清空(flush)一級快取就可能導致記憶體外洩。
下面範例中的程式碼也會導致記憶體外洩。
import java.util.Arrays; import java.util.EmptyStackException; public class MyStack<T> { private T[] elements; private int size = 0; private static final int INIT_CAPACITY = 16; public MyStack() { elements = (T[]) new Object[INIT_CAPACITY]; } public void push(T elem) { ensureCapacity(); elements[size++] = elem; } public T pop() { if (size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if (elements.length == size) { elements = Arrays.copyOf(elements,2 * size + 1); } } }
上面的程式碼實現了一個堆疊(先進後出(FILO))結構,乍看之下似乎沒有什麼明顯的問題,它甚至可以通過你編寫的各種單元測試。
然而其中的pop方法卻存在記憶體洩漏的問題,當我們用pop方法彈出堆疊中的物件時,該物件不會被當作垃圾回收,即使使用堆疊的程式不再引用這些對象,因為堆疊內部維護著對這些物件的過期參考(obsolete reference)。在支援垃圾回收的語言中,記憶體外洩是很隱密的,這種記憶體洩漏其實就是無意識的物件保持。
如果一個物件引用被無意識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該物件所引用的其他對象,即使這樣的物件只有少數幾個,也可能會導致許多的物件被排除在垃圾回收之外,從而對效能造成重大影響,極端情況下會引發Disk Paging(實體記憶體與硬碟的虛擬記憶體交換資料),甚至造成OutOfMemoryError。
2、Java中為什麼會有GC機制呢?
·安全性考量;--for security.
·減少記憶體外洩;--erase memory leak in some degree.
##·減少程式設計師工作量。 --Programmers dont worry about memory releasing.3、哪些對於Java的GC記憶體需要回收?
記憶體運行時JVM會有一個運行時資料區來管理記憶體。它主要包括5大部分:
程式計數器(Program CounterRegister);虛擬機器堆疊(VM Stack);本機方法堆疊(Native Method Stack);方法區(Method Area);堆疊(Heap)。 而其中程式計數器、虛擬機器堆疊、本地方法堆疊是每個執行緒私有的記憶體空間,隨執行緒而生,隨執行緒而亡。例如堆疊中每一個堆疊幀中分配多少記憶體基本上在類別結構確定是哪個時就已知了,因此這3個區域的記憶體分配和回收都是確定的,無需考慮記憶體回收的問題。 但方法區和堆就不同了,一個介面的多個實作類別所需的記憶體可能不一樣,我們只有在程式運行期間才會知道會建立哪些對象,這部分記憶體的分配和回收都是動態的,GC主要關注的是這部分記憶體。總而言之,GC主要進行回收的記憶體是JVM中的方法區和堆。4、Java的GC什麼時候回收垃圾?
在面試中常常會碰到這樣一個問題(事實上筆者也碰到過):如何判斷一個物件已經死去?
很容易想到的一個答案是:對一個物件新增引用計數器。每當有地方引用它時,計數器值加1;當引用失效時,計數器值減1.而當計數器的值為0時這個物件就不會再被使用,判斷為已死。是不是簡單又直覺。 然而,很遺憾。這種做法是錯的!為什麼是錯的呢?事實上,用引用計數法確實在大部分情況下是一個不錯的解決方案,而在實際的應用中也有不少案例,但它卻無法解決物件之間的循環引用問題。 例如物件A中有一個欄位指向了物件B,而物件B中也有一個欄位指向了物件A,而事實上他們兩個都不再使用,但計數器的值永遠都不可能為0 ,也就不會被回收,然後就發生了記憶體外洩。正確的做法應該是怎麼樣?
在Java,C#等語言中,比較主流的判定一個物件已死的方法是:可達性分析(Reachability Analysis).所有產生的物件都是一個稱為"GC Roots "的根的子樹。 從GC Roots開始向下搜索,搜索所經過的路徑稱為引用鏈(Reference Chain),當一個物件到GC Roots沒有任何引用鏈可以到達時,就稱這個物件是不可達的(不可引用的),也就是可以被GC回收了。 無論是引用計數器或可達性分析,判定物件是否存活都與引用有關!那麼,如何定義物件的引用呢?我们希望给出这样一类描述:当内存空间还够时,能够保存在内存中;如果进行了垃圾回收之后内存空间仍旧非常紧张,则可以抛弃这些对象。所以根据不同的需求,给出如下四种引用,根据引用类型的不同,GC回收时也会有不同的操作:
强引用(Strong Reference):Object obj=new Object();只要强引用还存在,GC永远不会回收掉被引用的对象。
软引用(Soft Reference):描述一些还有用但非必需的对象。在系统将会发生内存溢出之前,会把这些对象列入回收范围进行二次回收(即系统将会发生内存溢出了,才会对他们进行回收)
弱引用(Weak Reference):程度比软引用还要弱一些。这些对象只能生存到下次GC之前。当GC工作时,无论内存是否足够都会将其回收(即只要进行GC,就会对他们进行回收。)
虚引用(Phantom Reference):一个对象是否存在虚引用,完全不会对其生存时间构成影响。关于方法区中需要回收的是一些废弃的常量和无用的类。
1.废弃的常量的回收。这里看引用计数就可以了。没有对象引用该常量就可以放心的回收了。
2.无用的类的回收。什么是无用的类呢?
A.该类所有的实例都已经被回收。也就是Java堆中不存在该类的任何实例;
B加载该类的ClassLoader已经被回收;
C.该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
总而言之:对于堆中的对象,主要用可达性分析判断一个对象是否还存在引用,如果该对象没有任何引用就应该被回收。而根据我们实际对引用的不同需求,又分成了4种引用,每种引用的回收机制也是不同的。
对于方法区中的常量和类,当一个常量没有任何对象引用它,它就可以被回收了。而对于类,如果可以判定它为无用类,就可以被回收了。
5、通过10个示例来初步认识Java8中的lambda表达式
用lambda表达式实现Runnable
// Java 8 之前: new Thread(new Runnable(){ @Override public void run(){ System.out.println("Before Java8, too much code for too little to do"); }}).start(); //Java 8 方式: new Thread(()->System.out.println("In Java8, Lambda expression rocks !!")).start();
输出:
too much code,for too little to do Lambda expression rocks!!
这个例子向我们展示了Java 8 lambda表达式的语法。你可以使用lambda写出如下代码:
(params) -> expression (params) -> statement (params) -> { statements }
例如,如果你的方法不对参数进行修改、重写,只是在控制台打印点东西的话,那么可以这样写:
() -> System.out.println("Hello Lambda Expressions");
如果你的方法接收两个参数,那么可以写成如下这样:
(int even, int odd) -> even + odd
顺便提一句,通常都会把lambda表达式内部变量的名字起得短一些。这样能使代码更简短,放在同一行。所以,在上述代码中,变量名选用a、b或者x、y会比even、odd要好。
使用Java 8 lambda表达式进行事件处理
如果你用过Swing API编程,你就会记得怎样写事件监听代码。这又是一个旧版本简单匿名类的经典用例,但现在可以不这样了。你可以用lambda表达式写出更好的事件监听代码,如下所示:
// Java 8 之前: JButton show = new JButton("Show"); show.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Event handling without lambda expression is boring"); } }); // Java 8 方式: show.addActionListener((e) -> { System.out.println("Light, Camera, Action !! Lambda expressions Rocks"); });
使用Java 8 lambda表达式进行事件处理 使用lambda表达式对列表进行迭代
如果你使过几年Java,你就知道针对集合类,最常见的操作就是进行迭代,并将业务逻辑应用于各个元素,例如处理订单、交易和事件的列表。
由于Java是命令式语言,Java 8之前的所有循环代码都是顺序的,即可以对其元素进行并行化处理。如果你想做并行过滤,就需要自己写代码,这并不是那么容易。
通过引入lambda表达式和默认方法,将做什么和怎么做的问题分开了,这意味着Java集合现在知道怎样做迭代,并可以在API层面对集合元素进行并行处理。
下面的例子里,我将介绍如何在使用lambda或不使用lambda表达式的情况下迭代列表。你可以看到列表现在有了一个forEach()方法,它可以迭代所有对象,并将你的lambda代码应用在其中。
// Java 8 之前: List features = Arrays.asList("Lambdas", "Default Method", "Stream API","Date and Time API"); for (String feature : features) { System.out.println(feature); } // Java 8 之后: List features = Arrays.asList("Lambdas", "Default Method", "Stream API","Date and Time API"); features.forEach(n -> System.out.println(n)); // 使用 Java 8 的方法引用更方便,方法引用由::双冒号操作符标示, // 看起来像 C++的作用域解析运算符 features.forEach(System.out::println);
输出:
Lambdas Default Method Stream API Date and Time API
列表循环的最后一个例子展示了如何在Java 8中使用方法引用(method reference)。你可以看到C++里面的双冒号、范围解析操作符现在在Java 8中用来表示方法引用。
使用lambda表达式和函数式接口Predicate
除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫做java.util.function。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用java.util.function.Predicate函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。下面是Java 8 Predicate的例子,展示了过滤集合数据的多种常用方法。Predicate接口非常适用于做过滤。
public static void main(String[]args){ List languages=Arrays.asList("Java", "Scala","C++", "Haskell", "Lisp"); System.out.println("Languages which starts with J :"); filter(languages, (str)->str.startsWith("J")); System.out.println("Languages which ends with a "); filter(languages, (str)->str.endsWith("a")); System.out.println("Print all languages :"); filter(languages, (str)->true); System.out.println("Print no language : "); filter(languages, (str)->false); System.out.println("Print language whose length greater than 4:"); filter(languages, (str)->str.length()>4); } public static void filter(List names, Predicate condition){ for(String name:names){ if(condition.test(name)){ System.out.println(name+" "); } } } // filter 更好的办法--filter 方法改进 public static void filter(List names, Predicate condition) { names.stream().filter((name)->(condition.test(name))).forEach((name)-> {System.out.println(name + " "); }); }
可以看到,Stream API的过滤方法也接受一个Predicate,这意味着可以将我们定制的filter()方法替换成写在里面的内联代码,这就是lambda表达式的魔力。另外,Predicate接口也允许进行多重条件的测试。
以上是java gc 面試題目及答案(1~5題)的詳細內容。更多資訊請關注PHP中文網其他相關文章!