Java memory leak and overflow

          The main purpose of using the JVM to allocate and manage memory is to prevent the two major problems of memory leaks and overflows, which are also the easiest to ignore when we first learn;

          concept:

           Memory leak: The allocated memory cannot be recovered

           Memory overflow: The system is running out of memory

1. Memory leak

          Generally speaking, there are two cases of memory leaks. The first case is in C/C++, when the memory allocated in the heap is not released, all the ways to access this memory are deleted. (such as pointer reassignment); another situation is that when the memory object is obviously no longer needed, the memory and its access method (reference) are still retained. The first case has been well resolved in Java due to the introduction of the garbage collection mechanism. Therefore, the memory leak in Java mainly refers to the second case. 

         There are three main reasons for memory leaks in Java

         ① Object references that have not been eliminated: The so-called expired references refer to references that will never be eliminated

  eg: A stack increases at the beginning, increases to a certain length, and then shrinks again. At this time, objects popped from the stack will not be collected as garbage, even if the stack program no longer references these objects, they will not Will be recycled; because the stack maintains expired references to these objects, a memory leak occurs in this case; these details are most often ignored when we write programs;

 

For example, if a long-lived object holds a reference to a short-lived object, memory leaks are likely to occur. Although the short-lived object is no longer needed, it cannot be recycled because the long-lived object holds its reference.

 

          Solution: Once the object expires, we just clear these references. The advantage of clearing the references in time is that when we call these expired objects again, the program will throw an exception instead of executing it incorrectly; when declaring the object Before referencing, clarify the effective scope of the memory object. Effective memory objects within a function should be declared as local variables, those with the same life cycle as the class instance should be declared as instance variables... and so on. Remember to manually null out the reference to the memory object when it is no longer needed.

          Of course, we should pay attention not to be overly careful. For each object reference, when we don't need it, we should immediately clear it. This is not necessary. Clearing object references should be a special case, not a norm; generally speaking, as long as the class manages its own memory, we should be alert to the problem of memory leaks;

         ② Another common source of memory leaks is the cache:

eg: You put the object reference in the cache, and then you forget it, so that it remains in the cache for a long time when it is no longer useful;

         Solution: One: when there is a reference to the key of an item outside the cache, the item is meaningful. WeakHashMap is used to represent the cache, which will be automatically eliminated when the object expires; the other: the life cycle of the cache item It is not easy to determine whether it makes sense. Over time, the items in it become less and less valuable. In this case, the cache needs to be cleaned from time to time to get some useless items. The LinkedHashMap class can use its removeEldestEntry method to It is easy to clean up these expired items as new entries are added to the cache; for more complex caches, java.lang.ref must be used directly

          ③ The most common sources of memory leaks are listeners and other callbacks

eg: When you implement an API, and the client registers callbacks in this API, but does not explicitly cancel the registration, if you do not take some special actions at this time, they may accumulate;

          Solution: The best way to ensure callbacks are immediately garbage collected is to save their weak references, for example, as keys in a WeakHashMap;

2. Memory overflow 

       As we all know about the JVM, we know that in the Java Virtual Machine Specification, the JVM is divided into several partitions. First, it is divided into a thread shared area and a thread non-shared area:

        Thread shared area: divided into heap area, method area and runtime constant pool, in which Java heap area is divided into new generation and old generation, and new generation is divided into Eden space, From Survivor space, To Survivor space; two we The method area is only logically independent, and is physically a part of the heap area. There is a special runtime memory area in the method area, which many developers call the permanent generation; and the runtime constant pool is the method area. a part;

        Thread non-shared area: A type of memory area that is not allowed to be shared by all threads, but only allowed to be accessed by the independent thread to which it belongs, including PC registers (PC counters), Java stack areas (stored object instances), local method stacks ( for supporting native methods)

       Our JVM is generally partitioned this way. Closer to home, our memory overflow will revolve around these partitions;

①Stack overflow (StackOverFlowError)

       The stack overflow throws java.lang.StackOverflowErroran error, which occurs because the depth of the stack exceeds the maximum depth allowed by the virtual machine when the method is running.

       This exception is usually caused by a program error, such as a dead recursive function may cause this situation:

package jvm;

/**
 * Created by Taoyongpan on 2017/8/6.
 */
public class stack01 {
    // dead recursion
    public void test(){
        test();
    }

    public static void main(String[] args){
        stack01 s = new stack01();
        s.test();
    }
}
 This program is a recursive call to the test() method, throwing the following exception,

 

 

Exception in thread "main" java.lang.StackOverflowError
	at jvm.stack01.test(stack01.java:9)
 ②Heap overflow (OutOfMemoryError: java heap space),

 

        堆内存溢出的时候,虚拟机会抛出java.lang.OutOfMemoryReeor:java heap space,出现这种情况的时候,我们需要根据内存溢出的时候产生的dump文件具体分析(需要增加-XX:+HeapDumpOnOutOfMemoryErrorjvm启动参数)。出现此种问题的时候有可能是内存泄露,也有可能是内存溢出了。

        如果是内存泄漏了,我们要找出泄露的对象是怎么被GCROOT引用起来,然后根据引用链来具体分析泄漏的原因。

         如果出现你内存溢出的问题,这往往是程序本身需要的内存大雨了我们虚拟机配置的内存,这种情况下,我们可以通过调大-Xmx来解决问题:

持久带溢出(OutOfMemoryError: PermGen space)

       我们知道Hotspot jvm通过持久带实现了Java虚拟机规范中的方法区,而运行时的常量池就是保存在方法区中的,因此持久带溢出有可能是运行时常量池溢出,也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。当持久带溢出的时候抛出java.lang.OutOfMemoryError: PermGen space
我在工作可能在如下几种场景下出现此问题。

  1. 使用一些应用服务器的热部署的时候,我们就会遇到热部署几次以后发现内存溢出了,这种情况就是因为每次热部署的后,原来的class没有被卸载掉。
  2. 如果应用程序本身比较大,涉及的类库比较多,但是我们分配给持久带的内存(通过-XX:PermSize和-XX:MaxPermSize来设置)比较小的时候也可能出现此种问题。
  3. 一些第三方框架,比如spring,hibernate都通过字节码生成技术(比如CGLib)来实现一些增强的功能,这种情况可能需要更大的方法区来存储动态生成的Class文件。

我们知道Java中字符串常量是放在常量池中的,String.intern()这个方法运行的时候,会检查常量池中是否存和本字符串相等的对象,如果存在直接返回对常量池中对象的引用,不存在的话,先把此字符串加入常量池,然后再返回字符串的引用。那么我们就可以通过String.intern方法来模拟一下运行时常量区的溢出.下面我们通过如下的代码来模拟此种情况:

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.*;
import java.lang.*;
public class OOMTest{
 
         public static void main(String... args){
                 List<String> list = new ArrayList<String>();
                 while ( true ){
                         list.add(UUID.randomUUID().toString().intern());
                 }
         }
 
}

我们通过如下的命令运行上面代码:

java -verbose:gc -Xmn5M -Xms10M -Xmx10M -XX:MaxPermSize=1M -XX:+PrintGC OOMTest

运行后的输入如下图所示:

1
2
3
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
         at java.lang.String.intern(Native Method)
         at OOMTest.main(OOMTest.java: 8 )

通过上面的代码,我们成功模拟了运行时常量池溢出的情况,从输出中的PermGen space可以看出确实是持久带发生了溢出,这也验证了,我们前面说的Hotspot jvm通过持久带来实现方法区的说法。

④OutOfMemoryError:unable to create native thread

        最后我们在来看看java.lang.OutOfMemoryError:unable to create natvie thread这种错误。 出现这种情况的时候,一般是下面两种情况导致的:

  1. 程序创建的线程数超过了操作系统的限制。对于Linux系统,我们可以通过ulimit -u来查看此限制。
  2. 给虚拟机分配的内存过大,导致创建线程的时候需要的native内存太少。我们都知道操作系统对每个进程的内存是有限制的,我们启动Jvm,相当于启动了一个进程,假如我们一个进程占用了4G的内存,那么通过下面的公式计算出来的剩余内存就是建立线程栈的时候可以用的内存。 线程栈总可用内存=4G-(-Xmx的值)- (-XX:MaxPermSize的值)- 程序计数器占用的内存 通过上面的公式我们可以看出,-Xmx 和 MaxPermSize的值越大,那么留给线程栈可用的空间就越小,在-Xss参数配置的栈容量不变的情况下,可以创建的线程数也就越小。因此如果是因为这种情况导致的unable to create native thread,那么要么我们增大进程所占用的总内存,或者减少-Xmx或者-Xss来达到创建更多线程的目的。

内存溢出部分出自http://www.importnew.com/14604.html。

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=327041723&siteId=291194637