1. Since there is a GC mechanism, why are there still memory leaks?
Theoretically, Java has a garbage collection mechanism (GC ) There will be no memory leak problem (this is also an important reason why Java is widely used in server-side programming). However, in actual development, there may be useless but reachable objects that cannot be recycled by GC, thus causing memory leaks.
For example, the objects in hibernate's Session (first-level cache) are persistent, and the garbage collector will not recycle these objects. However, there may be useless garbage objects in these objects. If they are not closed in time (close ) or flushing the first-level cache may cause memory leaks.
The code in the following example will also cause a memory leak.
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); } } }
The above code implements a stack (first in, last out (FILO)) structure. At first glance, there seems to be no obvious problem. It can even pass various unit tests you write.
However, the pop method has a memory leak problem. When we use the pop method to pop an object in the stack, the object will not be treated as garbage collection, even if the program using the stack no longer references these objects. , because obsolete references to these objects are maintained internally on the stack. In languages that support garbage collection, memory leaks are very hidden. This kind of memory leak is actually unconscious object retention.
If an object reference is retained unconsciously, the garbage collector will not process this object, nor will it process other objects referenced by this object. Even if there are only a few such objects, it may As a result, many objects are excluded from garbage collection, which has a significant impact on performance. In extreme cases, Disk Paging (exchanging data between physical memory and the virtual memory of the hard disk) may be caused, or even an OutOfMemoryError may be caused.
2. Why is there a GC mechanism in Java?
·Safety considerations;--for security.
·Reduce memory leaks;--erase memory leak in some degree.
·Reduce programmer work quantity. --Programmers dont worry about memory releasing.
3. Which memory needs to be recycled for Java's GC?
When memory is running, the JVM will have a runtime data area to manage memory.
It mainly includes 5 parts:
Program CounterRegister;
Virtual Machine Stack (VM Stack);
Native Method Stack;
Method Area;
Heap.
The program counter, virtual machine stack, and local method stack are the private memory spaces of each thread. They are born and die with the thread. For example, how much memory is allocated in each stack frame in the stack is basically known when the class structure is determined. Therefore, the memory allocation and recycling of these three areas are determined, and there is no need to consider the issue of memory recycling.
But the method area and the heap are different. Multiple implementation classes of an interface may require different memory. We will only know which objects will be created during the running of the program. The allocation and recycling of this part of memory They are all dynamic, and the GC mainly focuses on this part of memory. All in all, the memory that GC mainly reclaims is the method area and heap in the JVM.
4. When does Java's GC collect garbage?
In interviews, we often encounter this question (in fact, the author has also encountered it): How to judge that an object is dead?
An easy answer to think of is: add a reference counter to an object. Whenever there is a reference to it, the counter value is incremented by 1; when the reference expires, the counter value is decremented by 1. When the counter value is 0, the object will no longer be used and is judged to be dead. Isn't it simple and intuitive?
However, it is a pity. This approach is wrong! Why is it wrong? In fact, using reference counting is indeed a good solution in most cases, and there are many cases in actual applications, but it cannot solve the problem of circular references between objects.
For example, there is a field in object A that points to object B, and there is a field in object B that points to object A. In fact, both of them are no longer used, but the value of the counter can never be 0. , it will not be recycled, and then a memory leak occurs.
What should be the correct approach?
In languages such as Java and C#, the more mainstream method of determining the death of an object is: Reachability Analysis. All generated objects are called "GC Roots" "The subtree of the root.
Start from GC Roots and search downward. The path traveled by the search is called the reference chain. When an object has no reference chain to reach GC Roots, it is said that the object is unreachable. (non-referenceable), that is, it can be recycled by GC.
Whether it is a reference counter or reachability analysis, determining whether an object is alive is related to references! So, how to define a reference to an object?
我们希望给出这样一类描述:当内存空间还够时,能够保存在内存中;如果进行了垃圾回收之后内存空间仍旧非常紧张,则可以抛弃这些对象。所以根据不同的需求,给出如下四种引用,根据引用类型的不同,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接口也允许进行多重条件的测试。
The above is the detailed content of java gc interview questions and answers (questions 1~5). For more information, please follow other related articles on the PHP Chinese website!