Maison > Java > JavaQuestions d'entretien > Questions et réponses d'entretien Java GC (questions 1 à 5)

Questions et réponses d'entretien Java GC (questions 1 à 5)

(*-*)浩
Libérer: 2019-11-16 16:06:59
original
4837 Les gens l'ont consulté

Questions et réponses d'entretien Java GC (questions 1 à 5)

1. Puisqu'il existe un mécanisme GC, pourquoi y a-t-il encore des fuites de mémoire

Théoriquement, Java a un mécanisme de récupération de place (GC) ? ) Il n'y aura pas de problème de fuite de mémoire (c'est aussi une raison importante pour laquelle Java est largement utilisé dans la programmation côté serveur). Cependant, dans le développement réel, il peut y avoir des objets inutiles mais accessibles qui ne peuvent pas être recyclés par GC, provoquant ainsi des fuites de mémoire.

Par exemple, les objets de la session d'hibernate (cache de premier niveau) sont persistants et le garbage collector ne recyclera pas ces objets. Cependant, il peut y avoir des objets inutiles dans ces objets s'ils ne sont pas fermés. à temps (close ) ou vider le cache de premier niveau peut provoquer des fuites de mémoire.

Le code de l'exemple ci-dessous peut également provoquer une fuite de mémoire.

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);
        }
    }
}
Copier après la connexion

Le code ci-dessus implémente une structure de pile (FILO) À première vue, il ne semble y avoir aucun problème évident. Il peut même réussir divers tests unitaires que vous écrivez.

Cependant, la méthode pop a un problème de fuite de mémoire. Lorsque nous utilisons la méthode pop pour faire apparaître un objet dans la pile, l'objet ne sera pas récupéré, même si le programme utilisant la pile ne les référence plus. objects , car la pile conserve des références obsolètes à ces objets. Dans les langages qui prennent en charge le garbage collection, les fuites de mémoire sont très cachées. Ce type de fuite de mémoire est en fait une rétention d'objet inconsciente.

Si une référence d'objet est conservée inconsciemment, le ramasse-miettes ne traitera pas cet objet, ni les autres objets référencés par cet objet, même s'il n'y a que quelques objets de ce type, il peut en résulter. de nombreux objets sont exclus du garbage collection, ce qui a un impact significatif sur les performances, dans des cas extrêmes, cela peut provoquer un Disk Paging (échange de données entre la mémoire physique et la mémoire virtuelle du disque dur), voire provoquer une OutOfMemoryError.

2. Pourquoi y a-t-il un mécanisme GC en Java ?

·Considérations de sécurité ; --pour la sécurité.

·Réduire les fuites de mémoire ; --effacer les fuites de mémoire dans une certaine mesure.

·Réduire la quantité de travail du programmeur. --Les programmeurs ne s'inquiètent pas de la libération de mémoire.

3. Quelle mémoire doit être recyclée pour le GC de Java ?

Lorsque la mémoire est en cours d'exécution, la JVM disposera d'une zone de données d'exécution pour gérer la mémoire.

Il comprend principalement 5 parties :

Program CounterRegister ;

Pile de machines virtuelles (VM Stack) ;

Pile de méthodes natives ; ;

Zone de méthode

Tas.

Le compteur de programme, la pile de machine virtuelle et la pile de méthodes locales sont les espaces mémoire privés de chaque thread. Ils naissent et meurent avec le thread. Par exemple, la quantité de mémoire allouée dans chaque cadre de pile de la pile est fondamentalement connue lorsque la structure de classe est déterminée. Par conséquent, l'allocation de mémoire et le recyclage de ces trois zones sont déterminés, et il n'est pas nécessaire de considérer la question de la mémoire. recyclage.

Mais la zone de méthode et le tas sont différents. Plusieurs classes d'implémentation d'une interface peuvent nécessiter une mémoire différente. Nous ne saurons quels objets seront créés lors de l'exécution du programme. de mémoire Ils sont tous dynamiques, et le GC se concentre principalement sur cette partie de la mémoire. Dans l’ensemble, la mémoire récupérée principalement par GC est la zone de méthode et le tas de la JVM.

4. Quand le GC de Java collecte-t-il les déchets ?

Je rencontre souvent cette question dans les interviews (en fait, je l'ai aussi rencontrée) : Comment juger qu'un objet est mort ?

Une réponse simple à laquelle penser est : ajouter un compteur de référence à un objet. Chaque fois qu'il y a une référence à celui-ci, la valeur du compteur est incrémentée de 1 ; lorsque la référence expire, la valeur du compteur est décrémentée de 1. Lorsque la valeur du compteur est 0, l'objet ne sera plus utilisé et est jugé mort. N'est-ce pas simple et intuitif ?

Cependant, c'est dommage. Cette approche est fausse ! Pourquoi est-ce faux ? En fait, utiliser le comptage de références est effectivement une bonne solution dans la plupart des cas, et il existe de nombreux cas dans les applications réelles, mais cela ne peut pas résoudre le problème des références circulaires entre objets.

Par exemple, il y a un champ dans l'objet A qui pointe vers l'objet B, et il y a un champ dans l'objet B qui pointe vers l'objet A. En fait, les deux ne sont plus utilisés, mais la valeur du compteur ne peut jamais être 0. , il ne sera pas recyclé, et alors une fuite mémoire se produit.

Quelle devrait être la bonne approche ?

Dans des langages tels que Java et C#, la méthode la plus courante pour déterminer la mort d'un objet est : l'analyse d'accessibilité. Tous les objets générés sont appelés « racines GC » « le sous-arbre de la racine. .

Recherche vers le bas à partir des Racines GC. Le chemin parcouru par la recherche est appelé chaîne de référence. Lorsqu'un objet n'a pas de chaîne de référence pour atteindre les Racines GC, on dit que l'objet est inaccessible (non-accessible). référençable), c'est-à-dire qu'il peut être recyclé par GC.

Qu'il s'agisse d'un compteur de références ou d'une analyse d'accessibilité, déterminer si un objet est vivant est lié aux références ! Alors, comment définir une référence à un objet ?

我们希望给出这样一类描述:当内存空间还够时,能够保存在内存中;如果进行了垃圾回收之后内存空间仍旧非常紧张,则可以抛弃这些对象。所以根据不同的需求,给出如下四种引用,根据引用类型的不同,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();
Copier après la connexion

输出:

too much code,for too little to do
Lambda expression rocks!!
Copier après la connexion

这个例子向我们展示了Java 8 lambda表达式的语法。你可以使用lambda写出如下代码:

(params) -> expression (params) -> statement
(params) -> { statements }
Copier après la connexion

例如,如果你的方法不对参数进行修改、重写,只是在控制台打印点东西的话,那么可以这样写:

() -> System.out.println("Hello Lambda Expressions");
Copier après la connexion

如果你的方法接收两个参数,那么可以写成如下这样:

(int even, int odd) -> even + odd
Copier après la connexion

顺便提一句,通常都会把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");
});
Copier après la connexion

使用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);
Copier après la connexion

输出:

Lambdas Default Method Stream API
Date and Time API
Copier après la connexion

列表循环的最后一个例子展示了如何在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 + " ");
    });
}
Copier après la connexion

可以看到,Stream API的过滤方法也接受一个Predicate,这意味着可以将我们定制的filter()方法替换成写在里面的内联代码,这就是lambda表达式的魔力。另外,Predicate接口也允许进行多重条件的测试。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal