1. GC メカニズムがあるのに、なぜ依然としてメモリ リークが発生するのでしょうか?
理論的には、Java にはガベージ コレクション メカニズム (GC) があります。 ) メモリ リークの問題は発生しません (これは、Java がサーバーサイド プログラミングで広く使用されている重要な理由でもあります)。ただし、実際の開発では、GC でリサイクルできない、役に立たないが到達可能なオブジェクトが存在し、メモリ リークが発生する可能性があります。
たとえば、Hibernate のセッション (1 次キャッシュ) 内のオブジェクトは永続的であり、ガベージ コレクターはこれらのオブジェクトをリサイクルしません。ただし、これらのオブジェクトには不要なガベージ オブジェクトが存在する可能性があります。閉じられていない場合は、時間が経つと (close )、1 次キャッシュをフラッシュするとメモリ リークが発生する可能性があります。
次の例のコードでもメモリ リークが発生します。
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 メソッドを使用してオブジェクトをスタックにポップすると、スタックを使用するプログラムがなくなっても、オブジェクトはガベージ コレクションとして扱われません。は、これらのオブジェクトを参照します。これは、これらのオブジェクトへの古い参照が内部的にスタックに保持されているためです。ガベージ コレクションをサポートする言語では、メモリ リークは非常に隠されていますが、この種のメモリ リークは実際には無意識のオブジェクト保持です。
オブジェクト参照が無意識に保持されている場合、ガベージ コレクターはこのオブジェクトを処理せず、このオブジェクトによって参照される他のオブジェクトも処理しません。そのようなオブジェクトが少数しか存在しない場合でも、結果として、多くのオブジェクトがガベージ コレクションから除外されるため、パフォーマンスに重大な影響があり、極端な場合には、ディスク ページング (物理メモリとハード ディスクの仮想メモリ間でのデータ交換) が発生したり、OutOfMemoryError が発生したりする可能性があります。
2. Java に GC メカニズムがあるのはなぜですか?
·安全性への配慮; -- セキュリティのため。
·メモリ リークの削減; -- メモリ リークをある程度消去します。
·プログラマの作業量を削減します。 --プログラマーはメモリの解放について心配する必要はありません。
3. Java の GC のためにどのメモリをリサイクルする必要がありますか?
メモリが実行されている場合、JVM にはメモリを管理するためのランタイム データ領域があります。
主に 5 つの部分で構成されています:
プログラム CounterRegister;
仮想マシン スタック (VM スタック);
ネイティブ メソッド スタック;
メソッド領域;
ヒープ。
プログラム カウンタ、仮想マシン スタック、およびローカル メソッド スタックは、各スレッドのプライベート メモリ空間であり、スレッドとともに生まれ、消滅します。例えば、スタック内の各スタックフレームにどのくらいのメモリが割り当てられているかは、基本的にクラス構成が決まれば分かるので、これら3つの領域のメモリ割り当てや再利用も決まり、メモリの問題を考慮する必要はありません。リサイクル。
しかし、メソッド領域とヒープは異なります。インターフェイスの複数の実装クラスは、異なるメモリを必要とする場合があります。どのオブジェクトが作成されるかは、プログラムの実行中にのみわかります。この部分の割り当てとリサイクルメモリのこれらはすべて動的であり、GC は主にメモリのこの部分に焦点を当てます。全体として、GC が主に再利用するメモリは、JVM のメソッド領域とヒープです。
4. Java の GC はいつガベージを収集しますか?
インタビューでは、よくこのような質問に遭遇します(実際、著者も遭遇しました):物体が死んだことをどのように判断するか?
考えられる簡単な答えは、オブジェクトに参照カウンタを追加することです。参照があると常にカウンタ値が 1 増加し、参照が期限切れになるとカウンタ値が 1 減少します。カウンタ値が 0 の場合、オブジェクトは使用されなくなり、死んでいると判断されます。シンプルで直感的ではないでしょうか?
しかし、残念です。このアプローチは間違っています!なぜ間違っているのでしょうか?実際、参照カウントを使用することはほとんどの場合に優れた解決策であり、実際のアプリケーションでも多くのケースがありますが、オブジェクト間の循環参照の問題は解決できません。
たとえば、オブジェクト A にはオブジェクト B を指すフィールドがあり、オブジェクト B にはオブジェクト A を指すフィールドがあります。実際、どちらも現在は使用されていませんが、値カウンタの値が 0 になることはありません。リサイクルされず、メモリ リークが発生します。
正しいアプローチは何でしょうか?
Java や C# などの言語では、オブジェクトの死を判断するためのより主流の方法は、到達可能性分析です。生成されたすべてのオブジェクトは、「GC ルート」「ルートのサブツリー」と呼ばれます。 。
GC ルートから開始して、下方向に検索します。検索によって移動されるパスは参照チェーンと呼ばれます。オブジェクトに GC ルートに到達する参照チェーンがない場合、オブジェクトは到達不能であると言われます。(非-参照可能)、つまり、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 中国語 Web サイトの他の関連記事を参照してください。