집 >Java >Java인터뷰 질문들 >java gc 인터뷰 질문 및 답변(질문 1~5)
1. GC 메커니즘이 있는데 왜 여전히 메모리 누수가 발생합니까?
이론적으로 Java 가비지 수집 메커니즘(GC)으로 인해 메모리 누수 문제가 없습니다. 이는 Java가 서버측 프로그래밍에서 널리 사용되는 중요한 이유이기도 합니다. 그러나 실제 개발에서는 쓸모는 없지만 도달 가능한 객체가 있어 GC로 재활용할 수 없어 메모리 누수가 발생할 수 있습니다.
예를 들어, 최대 절전 모드의 세션(1차 캐시)에 있는 객체는 지속적이고 가비지 수집기는 이러한 객체를 재활용하지 않습니다. 그러나 이러한 객체에 쓸모없는 가비지 객체가 있을 수 있습니다. 첫 번째 수준 캐시가 제때 닫히지 않거나(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 메서드를 사용하여 스택의 개체를 팝하면 프로그램이 더 이상 스택을 사용하지 않더라도 개체가 가비지 수집되지 않습니다. 이러한 객체에 대한 더 이상 사용되지 않는 참조는 스택에서 내부적으로 유지되기 때문에 이러한 객체를 참조합니다. 가비지 컬렉션을 지원하는 언어에서는 메모리 누수가 매우 숨겨져 있습니다. 이러한 종류의 메모리 누수는 실제로 무의식적인 객체 보유입니다.
객체 참조가 무의식적으로 유지되면 가비지 수집기는 이 객체를 처리하지 않으며 해당 객체가 몇 개만 있어도 이 객체가 참조하는 다른 객체를 처리하지 않습니다. 극단적인 경우 디스크 페이징(물리적 메모리와 하드 디스크의 가상 메모리 간 데이터 교환)이 발생하거나 OutOfMemoryError가 발생할 수도 있습니다.
2. Java에 GC 메커니즘이 있는 이유는 무엇인가요?
·보안 고려 사항.
·메모리 누수를 줄입니다. -- 어느 정도 메모리 누수를 지웁니다. #·프로그래머 작업량을 줄입니다. --프로그래머는 메모리 해제에 대해 걱정하지 않습니다.
3. Java의 GC를 위해 어떤 메모리를 재활용해야 합니까?메모리가 실행 중일 때 JVM에는 메모리를 관리하기 위한 런타임 데이터 영역이 있습니다.
주로 5개 부분으로 구성됩니다.Program CounterRegister
가상 머신 스택(VM 스택); 🎜🎜#
네이티브 메서드 스택 메서드 영역 힙. 프로그램 카운터, 가상 머신 스택, 로컬 메서드 스택은 스레드와 함께 태어나고 죽는 각 스레드의 전용 메모리 공간입니다. 예를 들어 스택의 각 스택 프레임에 얼마나 많은 메모리가 할당되는지는 기본적으로 클래스 구조가 결정될 때 알 수 있으므로 이 세 영역의 메모리 할당 및 재활용이 결정되며 메모리 문제를 고려할 필요가 없습니다. 재활용. 그러나 메소드 영역과 힙은 서로 다릅니다. 인터페이스의 여러 구현 클래스에는 서로 다른 메모리가 필요할 수 있습니다. 우리는 실행 중에 생성될 객체와 이 메모리 부분의 할당만 알 수 있습니다. 프로그램과 재활용은 모두 동적이며 GC는 주로 메모리의 이 부분에 중점을 둡니다. 전체적으로 GC가 주로 회수하는 메모리는 JVM의 메소드 영역과 힙입니다.4. Java의 GC는 언제 가비지를 수집합니까?
우리는 인터뷰에서 종종 이런 질문을 접하게 됩니다. (사실 저도 이런 질문을 접한 적이 있습니다.) 피험자가 죽었다고 판단하는 방법은 무엇입니까?
생각나는 쉬운 대답은 객체에 참조 카운터를 추가하는 것입니다. 참조가 있을 때마다 카운터 값은 1씩 증가하고, 참조가 만료되면 카운터 값은 1씩 감소합니다. 카운터 값이 0이면 해당 개체는 더 이상 사용되지 않고 죽은 것으로 판단됩니다. 간단하고 직관적이지 않나요?
그런데 아쉽네요. 이 접근 방식은 잘못되었습니다! 왜 틀린가요? 실제로 참조 카운팅을 사용하는 것은 대부분의 경우 좋은 해결책이고 실제 응용에서도 많은 경우가 있지만 객체 간 순환 참조 문제를 해결할 수는 없습니다. 예를 들어 객체 A에는 객체 B를 가리키는 필드가 있고, 객체 B에는 객체 A를 가리키는 필드가 있습니다. 사실 둘 다 더 이상 사용되지 않지만, 카운터의 값은 절대 될 수 없습니다. 0이면 재활용되지 않으며 메모리 누수가 발생합니다.올바른 접근 방식은 무엇입니까?
Java 및 C#과 같은 언어에서 객체의 소멸을 결정하는 보다 주류적인 방법은 도달성 분석입니다. 생성된 모든 객체를 " 루트의 하위 트리"라고 합니다. GC 루트'.
GC Roots에서 시작하여 아래쪽으로 검색합니다. 검색이 이동한 경로를 참조 체인이라고 합니다. 개체에 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!