订阅博客
收藏博客
微博分享
QQ空间分享

绝世邪神,BAT面试必问题系列:深化详解JVM 内存区域及内存溢出剖析,小池

频道:娱乐消息 标签:什么是走读服从tiny 时间:2019年05月14日 浏览:169次 评论:0条

前语

在JVM的管控下,Java程序员不再需求办理内存的分配与开释,这和在C和C++的国际是彻底不一样的。所以,在JVM的协助下,Java程序员很少会重视内存走漏和内存溢出的问题。可是,一旦JVM发作这些状况的时分,假设你不清楚JVM内存的内存办理机制是很难定位与处理问题的。

一、JVM 内存区域

Java虚拟机在运转时,会把内存空间分为若干个区域,依据《Java虚拟机标准(Java SE 7 版)》的规则,Java虚拟机所办理的内存区域分为如下部分:办法区、堆内存、虚拟机栈、本地办法栈、程序计数器。

1、办法区

办法区首要用于存储虚拟机加载的类信息、常量、静态变量,以及编译器编译后的代码等数据。在jdk1.7及其之前,积木办法区是堆的一个“逻辑部分”(一片接连的堆空间),但为了与堆做区别,办法区还有个名字叫“非堆”,也有人雀蜂雷公鞭用“永久代”(HotSpot对办法区的完成办法)来表明办法区。

从jdk1.7现已开端预备“去永久代”的规划,jdk1.7的HotSpot中,现已把本来放在办法区中的静态变量、字符串常量池等移到堆内存中,(常量池除字符串常量池还有class常量池等),这儿仅仅把字符串常量池移到堆内存中;在jdk1.8中,办法区现已不存在,原办法区中存储的类信息、编译后的代码数据等现已移动到了元空间(MetaSpace)中,元空间并没有处于堆内存上,而是直接占用的本地内存(NativeMemory)。依据网上的材料结合自己的了解对jdk1.3~1.6、jdk1.7、jdk1.8中办法区的变迁画了张图如下(如有不合理的当地期望读者指出):

去永久代的原因有:

(1)字符串存在永久代中,简略呈现功能问题和内存溢出。

(2)类及办法的信息等比较难确认其巨细,因而关于永久代的巨细指定比较困难,太小简略呈现永久代溢出,太大则简略导致老时代溢出。

(3)永久代会为 GC 带来不必要的复杂度,而且收回功率偏低。

2、堆内存

堆内存首要用于寄存目标和数组,它是J绝世邪神,BAT面试必问题系列:深化详解JVM 内存区域及内存溢出剖析,小池VM办理的内存中最大的一块区域,堆内存和办法区都被一切线程同享,在虚拟机启动时创立。在废物搜集的层面上浏阳河酒来看,因为现在搜集器基本上都选用分代搜集算法,因而堆还可以分为新生代(YoungGeneration)和老时代(OldGeneration),新生代还可以分为 Eden、From Survivor、To Survivor。

3、程序计数器

程序计数器是一块十分小的内存空间,可以看做是当时线程履行字节码的行号指示器,每个线程都有一个独立的程序计数器,因而程序计数器是线程私有的一块空间,此外,程序计数器是梯子Java虚拟机规则的仅有不会发作内存溢出的区域。

4、虚拟机栈

虚拟机栈也是每个线程私有的一块内存空间,它描绘的是办法的内存模型,直接看下图所示:

虚拟机会为樊建荣每个线程分配一个虚拟机栈,每个虚拟机栈中都有若干个栈帧,每个栈帧中存储了局部变量表、操作数栈、动态链接、回来地址等。一个栈帧就对应 Java 代码中的一个办法,当线程履行到一个办法时,就代表这个办法对应的栈帧现已进入虚拟机栈而且处于栈顶的方位,每一个 Java 办法从被调用到履行完毕,就对应了一个栈帧从入栈到出栈的进程。

5、本地办法栈

本地办法栈与虚拟机栈的区别是,虚拟机栈履行的是 Java 办法,本地办法栈履行的是本地办法(Native Method),其他基本上共同,在 HotSpot 中直接把本地办法栈和虚拟机栈合二为一,这儿暂时不做过多叙说。

6、元空间

上面提到,jdk1.8 中,现已不存在永久代(办法区),代替它的一块空间叫做 “ 元空间 ”,和永久代相似,都是 JVM 标准对办法区的完成,可是元空间并不在虚拟机中,而是运用本地内存,元空间的巨细仅受本地内存约束,但可以经过 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 来指定元空间的巨细。

二、JVM 内存溢出

1、堆内存溢出

堆内存中首要寄存目标、数组等,只需不断地创立这些目标,而且确保 GC Roots 到目标之间有可达途径来防止废物搜集收回机制铲除这些目标,当这些目标所占空间超越最大堆容量时,就会发生 OutOfMemoryError 的反常。堆内存反常示例如下:

/*** 设置最大堆最小堆:-Xms20m -Xmx20m* 运转时,不断在堆中创立OOMObject类的实例目标,且while履行完毕之前,GC Roots(代码中的oomObjectList)到目标(每一个OOMObject目标)之间有可达途径,废物搜集器就无法收回它们,终究导致内存溢出。*/public class HeapOOM {static class OOMObject {}public static void main(String[] args) {List oomObjectList = new ArrayList<>();while (true) 普通之路歌词{oomObjectList.add(new OOMObject());}}}

运转后会报反常,在仓库信息中可以绝世邪神,BAT面试必问题系列:深化详解JVM 内存区域及内存溢出剖析,小池看到:

java.lang.OutOfMemoryError: Java heap space 的信息,阐明在堆内存空间发生内存溢出重生学校之商女的反常。

新发生的目标开始分配在新生代,新生代满后会进行一次 Minor GC,假设 Minor GC 后空间缺乏会把该目标和新生代满意条件的目标放入老时代,老时代空间缺乏时会进行 Full 绝世邪神,BAT面试必问题系列:深化详解JVM 内存区域及内存溢出剖析,小池GC末世之漆黑召唤师,之后假设空间还缺乏以寄存新目标则抛出 OutOfMemoryError 反常。

常见原因:内存中加载的数据过多如一次从数据库中取出过多数据;调集对目标引证过多且运用完后没有清空;代码中存在死循环或循环发生过多重复目标;堆内存分配不合理;网络连接问题、数据库问题等。

2、虚拟机栈/本地办法栈溢出

(1)StackOverflowError:当线程恳求的栈的深度大于虚拟机所答应的最大深度,则抛出StackOverflowError,简略了解便是虚拟机栈中的栈帧数量过多(一个线程嵌套调用的办法数量过多)时,就会抛出StackOverflowError反常。

最常见的场景便是办法无限递归调用,如下:

/*** 设置每个线程的财运亨通栈巨细:-Xss256k* 运转时,不断调用doSomething()办法,main线程不断创立栈帧并入栈,导致栈的深度越来越大,终究导致栈溢出。*/public class StackSOF {private int stackLeng荒岛余生2th=1;public void doSomething(){stackLength++;doSomething();}public st绝世邪神,BAT面试必问题系列:深化详解JVM 内存区域及内存溢出剖析,小池atic void main(String[] args) {StackSOF stackSOF=new StackSOF();try {stackSOF.doSomething();}catch (Throwabl闲适花e e){//留意捕获的是ThrowableSystem.out.println("栈深度:"+stackSOF.stackLength);throw e;}}}

上述代码履行后抛出:

Exception in thread "Thread-0" java.lang.StackOverflowError 的反常。

(2)OutOfMemoryError:假设虚拟机在扩展栈时无法请求到满足的内存空间,则抛出 OutOfMemoryError。

咱们可以这样了解,虚拟机中可以供栈占用的空间≈可用物理内存 - 最大堆内存 - 最大办法区内存,比方一台机器内存为 4G,体系和其他使用占用 2G,虚拟机可用的物理内存为 2G,最大堆内存为 1G,最大办法区内存为 512M,那可供栈占有的内存大约便是 512M,假设咱们设置每个线程栈的巨细为 1M,那虚拟机中最多可以创立 512个线程,超越 512个线程再创立就没有空间可以给栈了,就报 OutOfMemoryError 反常了。

栈上可以发生 OutOfMemoryError 的示例如下:

/*** 设置每个线程的栈巨细:-Xss2m* 运转时,不断创立新的线程(且每个线程继续履行),每个线程对一个一个栈,终究没有剩余的空间来为新的线程分配,导致OutOfMemoryError*/public class StackOOM {private static in绝世邪神,BAT面试必问题系列:深化详解JVM 内存区域及内存溢出剖析,小池t threadNum = 0;public void doSomething() {try {Thread.sleep(100000000);} catch (InterruptedException e) {e.printStackTrace();}}public static void main(String[] args) {final StackOOM stackOOM = new StackOOM();try {while (true) {threadNum++;Thread thread = new Thread(new Runnable() {@Overridepublic void run() {stackOOM.doSomething();}});thread.start();}} catch (Throwable e) {System.out.println("现在活动线程数量:" + threadNum);throw e;}}}

上述代码运转后会报反常

在仓库信息中可以看到java.lang.OutOfMemoryError: unable to create new native thread的信息,无法创立新的线程,阐明是在扩展栈的时分发生的内存溢出反常。

总结:在线程较少的时分,某个线程恳求深度过大,会报 StackOv欧美丝袜erflow 反常,处理这种问题可以恰当加大栈的深度(添加栈空间巨细),也便是把 -Xss 的值设置大一些,但一般状况下是代码问题的可能性较大;在虚拟机发生线程时,无法为该线程请求栈空间了。

会报 OutOfMemoryError 反常,处理这种问题可以恰当减小栈天津小客车摇号成果查询的深度,也便是把 -Xss 的值设置小一些,每个线程占用的空间小了,总空间必定就能包容更多的线程,可是操作体系对一个进程的线程数有约束,经验值在 3000~5000 左右。

在 jdk1.5 之前 -Xss 默许是 256k,jdk1.5 之后默许是 1M,这个选项对体系硬性仍是蛮大的,设置时要依据实际状况,慎重操作。

3、办法区溢出

前面提到,办法区首要用于存储虚拟机加载的类信息、常量、静态变量,以及编译器编译后的代码等数据,所以办法区溢出的原因便是没有满足azis怎样直了的内存来寄存这些数据。

因为在 jdk1.6 之前字符串常量池是存在于办法区中的,所以依据 jdk1.6 之前看逼的虚拟机,可以经过不断发生绝世邪神,BAT面试必问题系列:深化详解JVM 内存区域及内存溢出剖析,小池不共同的字符串(一起要确保和 GC Roots 之间确保有可达途径)来模仿办法区的 OutOfMemoryError 反常;但办法区还存储加载的类信息,所以依据 jdk1.7五台山天气预报 的虚拟机,可以经过动态不断创立很多的类来模仿办法区溢出。

/*** 设置办法区最大、最小空间:-XX:PermSize=10m -XX:MaxPermSize=10m* 运转时,经过cg丽江景点lib不断创立JavaMethodAreaOOM的子类,办法区中类信息越来越多,终究没有可以为新的类分配的内存导致内存溢出*/public class JavaMethodAreaOOM {public static void main(final String[] arg钱塘甬真重高s){try {while (true){Enhancer enhancer=new Enhancer();enhancer.setSuperclass(JavaMethodAreaOOM.class);enhancer.setUseCache(false);enhancer.setCallback(new MethodInterceptor() {@Overridepublic Object interc人猿泰山ept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {return methodProxy.invokeSuper(o,objects);}});enhancer.create();}}catch (Throwable t){t.printStackTrace();}}}

上述代码运转后会报:

java.lang.OutOfMemoryError: PermGen space 的反常,阐明是在办法区呈现了内存溢出的过错。

4、本机直接内存溢出

本机直接内存(DirectMemory)并不是虚拟机运转时数据区的一部分,也不是 Java 虚拟机标准中界说的内存区域,但 Java 中用到 NIO 相关操作时(比方 ByteBuffer 的 allocteDirect 办法请求的是本机直接内存),也可能会呈现内存溢出的反常绝世邪神,BAT面试必问题系列:深化详解JVM 内存区域及内存溢出剖析,小池。

总结

JVM内存区域区分,便于它可以愈加高效的办理本身的内存。当程序中呈现这种因为JVM形成的内存溢出的状况的时分,需求依据不同的状况做不同的剖析与处理。