美文网首页深入理解java虚拟机程序员
深入JAVA虚拟机2_4OutOfMemoryError异常

深入JAVA虚拟机2_4OutOfMemoryError异常

作者: JoeyTsai | 来源:发表于2018-03-06 08:48 被阅读0次

layout: post

title: 深入JAVA虚拟机2_4OutOfMemoryError异常

categories: JVM JAVA

description: 深入JAVA虚拟机2_4OutOfMemoryError异常

keywords: JVM JAVA

注意:KOTLIN跟JAVA运行结果可能会不同,参照样例
https://www.jianshu.com/p/d9f90f3ee936


2_4_1. JAVA堆溢出测试

测试思路

java堆用于存储对象实例,只要不断创建对象,并保证
GC Roots到对象之间有可达路径来避免垃圾回收机制清
除这些对象,那么在对象数量到达最大堆的容量限制后
就会产生内存溢出异常

code2_3虚拟机参数

//限制java堆的大小为20mb[-Xms为堆的最小值,-Xmx为
堆的最大值]
//XX:+HeapDumpOnOutOfMemoryError可让虚拟机在出
现内存溢出异常时Dump出当前内存堆转储快照以便事后
进行分析
参数:  -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

code2_3 代码

package num2

/**
 * Created by Joey_Tsai on 2018/3/5.
 */
class HeapOOM{
    companion object {
        class OOMObject{

        }
    }
}

fun main(args : Array<String>){
    val list : MutableList<HeapOOM.Companion.OOMObject> = ArrayList<HeapOOM.Companion.OOMObject>();
    var i = 0;
    while (true){
        list.add(HeapOOM.Companion.OOMObject());

        println(++i);
    }
}

内存泄漏 与 内存溢出

内存泄漏:编写的程序没有正确的释放内存
内存溢出:内存不足,无法正常给程序分配内存,导致内
存不足的原因有很多,内存泄漏只是其中的一种

解决方法

使用工具检查GC Roots查看引用链上的对象是否都有存
活的意义,若确实没有出现泄露的情况,应该调整堆内
存参数(-Xmx 和 Xms)的最大值和最小值

p.s

程序运行参数图
程序运行结果图

github链接(使用kotlin实现)
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_3.kt



                          分    割    线


2_4_2. 虚拟机栈和本地方法栈溢出

1.虚拟机栈和本地方法栈OOM测试

测试思路

HotSpot虚拟机中不分虚拟机栈和本地方法栈,故-Xoss
参数(设置本地方法栈大小)存在但实际上是无效的,栈容
量由-Xss参数设定,关于虚拟机栈和本地方法栈java虚拟
机描述了两种异常:
1.如果线程请求的栈深度大于虚拟机所允许的最大深度,
  将抛出StackOverflowError异常
2.如果虚拟机在扩展栈时无法申请到足够的内存空间,则
  抛出OutOfMemoryError异常

code2_4虚拟机参数

参数 :   -Xss128k

code2_4代码

package num2

/**
 * Created by Joey_Tsai on 2018/3/5.
 * VM:-Xss128k
 */
public class JavaVMStackSOF{
    public var stackLength : Int = 1
    public fun stackLeak() : Unit{
        stackLength++;
        stackLeak()
    }
}
fun main(args:Array<String>){
    val javaVMStackSOF : JavaVMStackSOF = JavaVMStackSOF()
   try {

       javaVMStackSOF.stackLeak()

   }catch (e : Throwable ){
       println("stack length : ${javaVMStackSOF.stackLength}")
       throw e
   }
}

java虚拟机栈 与 java堆 与 方法区

1.java虚拟机栈:线程私有,生命周期与线程相同,每个方
法在执行的过程中都会创建一个栈帧(Stack Frame)用于
存储局部变量表,操作数栈,动态链接,方法出口等信
息。每一个方法执行完成的过程,就对应一个栈帧在虚
拟机栈中入栈出栈的过程。

2.java堆:所有线程共享的一块内存区域,用于存放对象
实例,垃圾收集器管理的主要区域,很多时候也被称作
"GC堆"(Garbage Collected Heap)

3.方法区:所有线程共享的内存区域,它用于存储已被虚拟
机加载的类信息,常量,静态变量,即时编译器编译后
的代码等数据

实验结果

在单线程下,无论由于栈帧太大还是虚拟机栈容量太
小,当内存无法分配时虚拟机抛出的都是
StackOverflowError异常

p.s


程序运行参数图
程序运行结果图

github链接(使用kotlin实现)
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_4.kt



2.多线程导致内存溢出异常

测试思路

如果测试时不限单线程,通过不断创建线程的方式倒是
可以产生内存溢出异常,这种情况下,为每个线程的栈
分配的内存越大,反而越容易产生内存溢出异常

使用工具

JProfiler用于查看线程使用情况
Idea安装JProfiler

code2_5虚拟机参数

参数:     -Xss2M

code2_5代码

package num2

/**
 * Created by Joey_Tsai on 2018/3/6.
 *VM: -Xss200M
 */
class JavaVMStackOOM{
    private fun dontStop() : Unit{
        while (true){

        }
    }
    public fun stackLeakByThread():Unit{
        while (true){
            val thread : Thread = Thread(Runnable(){
                @Override
                fun run(){
                    dontStop()
                }
            });
            thread.start()
        }
    }
}

fun main(args : Array<String>){
    val oom = JavaVMStackOOM()
    oom.stackLeakByThread()
}

操作系统内存分配

譬如,在32位的windows系统中给每个线程分配的内存
限制为2g,虚拟机提供了参数来控制java堆和方法区这
两部分内存的最大值,剩余的内存为2GB减去Xmx(最大
堆容量),再减去MaxPermSize(最大方法区容量),程序
计数器消耗内存很小,可以忽略。

p.s


程序运行参数图

github链接(使用kotlin实现)
https://github.com/joeytsai03/JVMStudy/blob/master/src/num2/code2_5.kt



                          分    割    线


2_4_3 方法区和运行时常量池溢出

1.运行时常量池导致的内存溢出异常

测试思路

运行时常量池是方法区的一部分,jdk7开始逐步去除永久
代,String.intern()是一个Native方法,在jdk1.6及之前的版
本中,由于常量池分配在永久代中,我们可以通过
-XX:PermSize与 -XX:MaxPermSize限制方法区大小,从
而限制常量池容量大小

code2_6虚拟机参数

VM:-XX:PermSize=10M -XX:MaxPermSize=10M

code2_6代码

package num2

/**
 * Created by Joey_Tsai on 2018/3/6.
 * VM:-XX:PermSize=10M -XX:MaxPermSize=10M
 */
class RuntimeConstantPoolOOM2_6{

}
fun main(args : Array<String>){
    //使用List保持着常量池引用,避免Full GC回收常量池行为
    val list : MutableList<String>?=ArrayList<String>();
    //10MB的permSize在integer范围内足够产生OOM了
    var i : Int = 0
    while (true){
        list?.add(i++.toString().intern())
        println(i)
    }
}

实验结果

运行时常量池溢出,在"OutOfMemoryError"后面跟着的提示信息
是"PermGen space",说明运行时常量池属于方法区(HotSpot虚拟机中的永
久代)的一部分,在jdk1.7中则不会得到相同结果,while将一直循环下去。

2.String.intern返回引用测试

code2_7代码

package num2

/**
 * Created by Joey_Tsai on 2018/3/6.
 */
public class RuntimeConstantPoolOOM2_7{
    
}

fun main(args: Array<String>) {
    val str1 : String = StringBuilder("计算机").append("软件").toString()
    println(str1.intern() == str1)

    val str2 : String = StringBuilder("ja").append("va").toString()
    println(str2.intern() == str2)
}

JDK1.6中的intern() 与 JDK1.7中的intern()

在JDK1.6中,intern()会把首次遇到的字符串实例复制在永久代中,返回的
也是永久代中这个字符串实例的引用,而StringBuilder创建的字符串实例
在java堆上,所以必然不是同一个引用,将返回false。而JDK1.7中的
intern()实现不会再复制实例,只是在常量池中记录首次出现的引用,因此
intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对
于str2返回false是因为'java'这个字符串在执行StringBuilder.toString()之前
已经出现过,字符串常量池已经有它的引用,不符合首次出现原则,而'计
算机软件'这个字符串则是首次出现返回true。  

相关文章

网友评论

    本文标题:深入JAVA虚拟机2_4OutOfMemoryError异常

    本文链接:https://www.haomeiwen.com/subject/covhfftx.html