Heim >Java >JavaInterview Fragen >Fragen und Antworten zum Java GC-Interview (Fragen 1 bis 5)

Fragen und Antworten zum Java GC-Interview (Fragen 1 bis 5)

(*-*)浩
(*-*)浩Original
2019-11-16 16:06:594832Durchsuche

Fragen und Antworten zum Java GC-Interview (Fragen 1 bis 5)

1. Da es einen GC-Mechanismus gibt, warum gibt es immer noch Speicherlecks?

Theoretisch verfügt Java über einen Garbage-Collection-Mechanismus (GC). ) Es wird kein Speicherleckproblem geben (dies ist auch ein wichtiger Grund, warum Java in der serverseitigen Programmierung weit verbreitet ist). In der tatsächlichen Entwicklung gibt es jedoch möglicherweise nutzlose, aber erreichbare Objekte, die von GC nicht recycelt werden können, was zu Speicherverlusten führt.

Zum Beispiel sind die Objekte in der Sitzung des Ruhezustands (Cache der ersten Ebene) persistent und der Garbage Collector recycelt diese Objekte möglicherweise nicht, wenn sie nicht geschlossen sind rechtzeitig (close) oder das Leeren des Caches der ersten Ebene kann zu Speicherverlusten führen.

Der Code im Beispiel unten kann auch einen Speicherverlust verursachen.

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);
        }
    }
}

Der obige Code implementiert eine Stack-Struktur (FILO). Auf den ersten Blick scheint es kein offensichtliches Problem zu geben. Er kann sogar verschiedene von Ihnen geschriebene Unit-Tests bestehen.

Die Pop-Methode weist jedoch ein Speicherverlustproblem auf. Wenn wir die Pop-Methode verwenden, um ein Objekt im Stapel zu platzieren, wird das Objekt nicht durch Müll gesammelt, selbst wenn das Programm, das den Stapel verwendet, nicht mehr auf diese verweist Objekte, da der Stapel veraltete Verweise auf diese Objekte verwaltet. In Sprachen, die die Speicherbereinigung unterstützen, sind Speicherlecks sehr versteckt. Bei dieser Art von Speicherleck handelt es sich tatsächlich um eine unbewusste Objektaufbewahrung.

Wenn eine Objektreferenz unbewusst beibehalten wird, verarbeitet der Garbage Collector weder dieses Objekt noch andere Objekte, auf die von diesem Objekt verwiesen wird. Selbst wenn nur wenige solcher Objekte vorhanden sind, kann dies dazu führen, dass Viele Objekte werden von der Garbage Collection ausgeschlossen, was erhebliche Auswirkungen auf die Leistung hat. In extremen Fällen kann es zu Disk Paging (Datenaustausch zwischen physischem Speicher und dem virtuellen Speicher der Festplatte) oder sogar zu einem OutOfMemoryError kommen.

2. Warum gibt es in Java einen GC-Mechanismus?

·Sicherheitsüberlegungen; - aus Sicherheitsgründen.

·Speicherverluste reduzieren - Speicherverluste bis zu einem gewissen Grad beseitigen.

·Reduzieren Sie die Arbeitsmenge des Programmierers. --Programmierer machen sich keine Sorgen über die Speicherfreigabe.

3. Welcher Speicher muss für Javas GC recycelt werden?

Wenn der Speicher ausgeführt wird, verfügt die JVM über einen Laufzeitdatenbereich zur Speicherverwaltung.

Es besteht hauptsächlich aus 5 Teilen:

Programm CounterRegister;

Native Method Stack ;

Methodenbereich

Heap.

Der Programmzähler, der Stapel der virtuellen Maschine und der lokale Methodenstapel sind die privaten Speicherbereiche jedes Threads. Sie werden mit dem Thread geboren und sterben. Beispielsweise ist bei der Bestimmung der Klassenstruktur grundsätzlich bekannt, wie viel Speicher in jedem Stapelrahmen zugewiesen ist. Daher werden die Speicherzuweisung und das Recycling dieser drei Bereiche bestimmt, und es besteht keine Notwendigkeit, das Problem des Speichers zu berücksichtigen Recycling.

Aber der Methodenbereich und der Heap sind unterschiedlich. Wir wissen nur, welche Objekte während der Ausführung des Programms erstellt werden des Gedächtnisses Sie sind alle dynamisch, und der GC konzentriert sich hauptsächlich auf diesen Teil des Gedächtnisses. Alles in allem ist der Speicher, den GC hauptsächlich zurückfordert, der Methodenbereich und der Heap in der JVM.

4. Wann sammelt Javas GC Müll?

Auf diese Frage stoße ich oft in Interviews (tatsächlich bin ich auch darauf gestoßen): Wie kann ich beurteilen, dass ein Objekt tot ist?

Eine leicht zu denkende Antwort ist: Fügen Sie einem Objekt einen Referenzzähler hinzu. Immer wenn eine Referenz darauf vorhanden ist, wird der Zählerwert um 1 erhöht; wenn die Referenz abläuft, wird der Zählerwert um 1 dekrementiert. Wenn der Zählerwert 0 ist, wird das Objekt nicht mehr verwendet und als tot eingestuft. Ist es nicht einfach und intuitiv?

Allerdings ist es schade. Dieser Ansatz ist falsch! Warum ist es falsch? Tatsächlich ist die Verwendung der Referenzzählung in den meisten Fällen eine gute Lösung, und in tatsächlichen Anwendungen gibt es viele Fälle, sie kann jedoch das Problem der Zirkelverweise zwischen Objekten nicht lösen.

Zum Beispiel gibt es in Objekt A ein Feld, das auf Objekt B zeigt, und in Objekt B gibt es ein Feld, das auf Objekt A zeigt. Tatsächlich werden beide nicht mehr verwendet, sondern der Wert Der Zähler kann niemals 0 sein. Er wird nicht recycelt und es kommt zu einem Speicherverlust.

Was sollte der richtige Ansatz sein?

In Sprachen wie Java und C# ist die gängigere Methode zur Bestimmung des Todes eines Objekts: Erreichbarkeitsanalyse. Alle generierten Objekte werden als „GC Roots“ bezeichnet .

Suche abwärts, ausgehend von den GC-Wurzeln. Der von der Suche zurückgelegte Pfad wird als Referenzkette bezeichnet, um die GC-Wurzeln zu erreichen. referenzierbar), das heißt, es kann von GC recycelt werden.

Ob es sich um einen Referenzzähler oder eine Erreichbarkeitsanalyse handelt, die Bestimmung, ob ein Objekt lebendig ist, hängt von Referenzen ab! Wie definiert man also einen Verweis auf ein Objekt?

我们希望给出这样一类描述:当内存空间还够时,能够保存在内存中;如果进行了垃圾回收之后内存空间仍旧非常紧张,则可以抛弃这些对象。所以根据不同的需求,给出如下四种引用,根据引用类型的不同,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接口也允许进行多重条件的测试。

Das obige ist der detaillierte Inhalt vonFragen und Antworten zum Java GC-Interview (Fragen 1 bis 5). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn

In Verbindung stehende Artikel

Mehr sehen