天天看点

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

一直以来都要看看看这本书《深入理解JAVA虚拟机》,所以找机会记录下来,以备不时之需,供看客借鉴。2017年10月16日10:16:02

科普一下,内存是计算机分配的一段空间(可以理解为一段用01表示的数据长度)。java 虚拟机运行时的内存如下。

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

        图示说明:白色模块为线程隔离、金色模块为所有线程共享

              program Count Register 是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。

             如果正在执行一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行一个Native方法,这个计数器值为空(Undefined)。

    计数器区域没有OutOfMemory。  

Java Virtual Machine Stacks 生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个帧栈(Stack Frame),用于存储

局部变量表(基本类型、对象引用、returnAddress)、操作数栈、动态链接、方法出口 等信息。每个方法从调用到执行完成的过程,对应一个帧栈在虚拟机栈中的入栈到出栈的过程。

           StackOverflowError情况:线程请求的栈深度大于虚拟机允许的深度。

   OutOfMemory情况:虚拟机栈无法申请足够内存。

        Native Method Stack 为虚拟机中使用到的Native方法服务。在HotSpot中虚拟机栈与本地方法栈已经合二为一。

        StackOverflowError情况:线程请求的栈深度大于虚拟机允许的深度。

OutOfMemory情况:虚拟机栈无法申请足够内存。

         Java Heap是Java 虚拟机管理的内存中最大的一块,在虚拟机启动时创建。Java堆的目的就是存放对象实例,此区域是垃圾回收的主要区域。

HotSpot JVM把年轻代分为了三部分:1个Eden区和2个Survivor区(分别叫from和to)。默认比例为8:1

         Java Heap 的可扩展实现(通过-Xmx和 -Xms控制)

          OutOfMemory情况:在Java 堆中没有内存完成实例分配。

Method Area用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。作为Java堆的逻辑部分,称之为Non-Heap(非堆),“永久代”。

此区域还包括:运行时常量池(运行期间产生的常量)。��栗子:String类的intern()方法;

OutOfMemory情况:在Java 堆中没有内存完成实例分配。

2.5.1 直接内存:通过DirectByteBuffer对象作为这块内存的的引用进行操作。NIO直接操作Native分配堆外内存。

     OutOfMemory情况:物理内存总和大于系统限制等(32位系统最大为4G)。

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

对象在内存中分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。

对象头:自身运行是的数据+类型指针。

存储内容                    

标志位

状态    

对象哈希码、对象分带年龄

01

未锁定

对象访问定位:句柄访问(稳定)、指针访问(快)。可以理解为句柄访问的引用,指针访问的是直接地址。Hotspot采用指针访问对象。

在Debug/Run中设置虚拟机参数:参考Hotspot

Java堆是保持对象实例,不断的创建对象,保证Gc Roots有可达路径来避免清除对象。

代码示例:

出错信息:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

内存泄露总体分析预览:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

实际错误例子:

public static List<Object> list;

在项目中不断在list增加数据,进行操作,

栈存储局部变量表、操作数等,那么只要不断增加数据一直到内存溢出就可以实现。

示例代码:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

实验结果:无论是堆帧太大还是虚拟机栈容量小,都会报Stack OverflowError异常。

Jvm默认大多数情况下帧的大小在1000到2000完全没得问题,正常的递归完全没得问题。---可以通过减少最大的堆和栈容量来解决部门“内存溢出”。

运行时常量池是方法区的一部分。

通过-XX:PermSzie和-XX:MaxPermSzie限制方法区的大小。是我们的永久代大小。

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

<code>package</code> <code>com.paddx.test.memory;</code>

<code>import</code> <code>java.util.ArrayList;</code>

<code>import</code> <code>java.util.List;</code>

<code>public</code> <code>class</code> <code>StringOomMock {</code>

<code>    </code><code>static</code> <code>String  base = </code><code>"string"</code><code>;</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(String[] args) {</code>

<code>        </code><code>List&lt;String&gt; list = </code><code>new</code> <code>ArrayList&lt;String&gt;();</code>

<code>        </code><code>for</code> <code>(</code><code>int</code> <code>i=</code><code>0</code><code>;i&lt; Integer.MAX_VALUE;i++){</code>

<code>            </code><code>String str = base + base;</code>

<code>            </code><code>base = str;</code>

<code>            </code><code>list.add(str.intern());</code>

<code>        </code><code>}</code>

<code>    </code><code>}</code>

<code>}</code>

这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存。我们通过 JDK 1.6、JDK 1.7 和 JDK 1.8 分别运行:

JDK 1.6 的运行结果:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

JDK 1.7的运行结果:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

JDK 1.8的运行结果:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

  

元空间与方法区相似,但是存在与直接内存中.

为什么出现Metaspace:

       1、字符串存在永久代中,容易出现性能问题和内存溢出。

  2、类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。

  3、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。

  4、Oracle 可能会将HotSpot 与 JRockit 合二为一

结果:

1、符号引用(Symbols)转移到了native heap; 例子:com.ycy.test

2、字面量(interned strings)转移到了java heap; 例子: string.intern()

3、类的静态变量(class statics)转移到了java heap 例子:Class元

-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。

  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。

  除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:

  -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集

  -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集

实例代码:

输出结果:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

实际例子:

执行结果:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

默认与Jave heap一样的最大值。

示例代码

错误信息:

第一章 JAVA内部区域与内存溢出异常一、前言二、虚拟机内存划分三、Hotspot虚拟机四、OutOfMemoryError实例

出现症状:一般OOM溢出但是,Dump文件很小,也使用了nio可以回出现这个问题。

   出现内存溢出一般情况

1.内存中加载的数据量过大。

 比如一次性从数据库加载过多的数据。

2.并发数量太高。

 并发数量太高,导致在短时间内创建大量的对象,GC也不及回收。

3.集合类中有无用对象的引用,使用完后没有立即清除。

 集合类中的对象,如果不手动进行清除,GC不是不会对集合中无用的对象进行回收。

4.代码中存在死循环,递归,或者循环次数过多产生大量的对象。

5.方法区内存溢出。

 方法区存放的是Class类型信息,类名,常量池,修饰符,方法描述等信息。

 使用了过多的静态变量。常量池也被大量的占用。

 jvm在“运行期间” 产生了大量的类。导致填满了方法区。比如使用反射,动态代理,字节码生成技术会在运行期间产生大量的类和类型信息。如hibernate,spring第三方框架大量使用了cglib技术产生大量的动态类。

 大量的jsp在编译生成java类时也有可能产生方法区溢出,GC对方法区的回收非常苛刻的,因为对于一个类的回收条件就很严格。

6.启动时JVM内存参数设置过小。