Java虚拟机--运行时数据区与内存溢出

简介: Java虚拟机--运行时数据区与内存溢出

JVM内存区域



了解java内存区域的划分,和每个区域存储的数据,可以帮助我们分析问题。

JVM内存区域分成堆 ,方法区,虚拟机栈,本地方法栈, 程序计数器

640.jpg


上图说明了 运行时数据区的划分,关注

方法区,堆是线程共享

虚拟机栈,程序计数器,本地方法栈是线程私有


方法区



 存放的数据是JVM加载的类信息,常量,静态变量和编译器编译后的代码等,这里要注意的是JDK1.8之后已经将这个方法区删除了,使用元空间,metaspace代替了,理由有如下:

1.方法区存放的是常量,容易造成内存溢出,outofmemory:permGen space

2.编译后的代码,类和方法难确定大小,太小,容易造成永久代溢出,太大,容易造成堆溢出,使用元空间,不受JVM虚拟机内存限制,受本地内存的限制。

3.同时永久代的GC复杂,回收效率偏低。


640.jpg


元空间常用的配置参数



1.MetaspaceSize

初始化的Metaspace大小,控制元空间发生GC的阈值。GC后,动态增加或降低MetaspaceSize。在默认情况下,这个值大小根据不同的平台在12M到20M浮动。使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数

2.MaxMetaspaceSize

限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序。在本机上该参数的默认值为4294967295B(大约4096MB)。

3.MinMetaspaceFreeRatio

当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数(即实际非空闲占比过大,内存不够用),那么虚拟机将增长Metaspace的大小。默认值为40,也就是40%。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。

4.MaxMetasaceFreeRatio

当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。默认值为70,也就是70%。

5.MaxMetaspaceExpansion

Metaspace增长时的最大幅度。在本机上该参数的默认值为5452592B(大约为5MB)。

6.MinMetaspaceExpansion

Metaspace增长时的最小幅度。在本机上该参数的默认值为340784B(大约330KB为)。


new出来的对象就放在堆,这部分区域目的就是为了存放对象实例。也是JVM管理的内存最大的最大的一块区域。

堆又分成  新生代(YoungGeneration)和 老年代(OldGeneration),新生代还可以分成Eden, from Survivor,to Survivor


程序计数器



非常小的一个内存空间,是当前线程执行字节码的行号指示器,每个线程都有自己的程序计数器,是线程私有的,程序计数器是唯一一个不会发生内存溢出的区域。

虚拟机栈

虚拟机栈,也是线程私有的一个空间

640.jpg


虚拟机会为每个线程提供一个虚拟机栈,每个虚拟机栈都有若干个栈帧,每个栈帧存储了局部变量表,操作数栈,动态链接,返回地址,当线程执行一个方法时,这个方法对应的栈帧,就处于虚拟机栈,栈帧的顶部,每一个java方法,从被调用,到结束,对应了一个栈帧入栈到出栈的过程。


本地方法栈

虚拟机栈上执行的是 Java方法,本地方法栈上执行的是本地方法(Native Methmod),HotSpot虚拟机将,虚拟机栈和本地方法栈合二为一。

JVM 内存溢出


1.堆溢出


public class HeapOOM {


   static class OOMObject {

   }


   /**

    * VM args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

    * -XX:HeapDumpPath=D://java_pid.hprof -Xms:是初始化堆内存值 -Xmx:是堆内存最大值

    * -XX:+HeapDumpOnOutofMemoryError 在堆溢出时保存快照

    * -XX:HeapDumpPath=./java_pid.hprof来显示指定路径

    *

    * MAT 工具 http://download.eclipse.org/mat/1.6/update-site/

    * https://blog.csdn.net/u010335298/article/details/52233689/

    * https://blog.csdn.net/liu765023051/article/details/75127361

    * @param args

    */

   public static void main(String[] args) {

       List<OOMObject> list = new ArrayList<OOMObject>();

       while (true) {

           list.add(new OOMObject());

       }

   }

}


运行后会出现异常

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space


新生代中,new 出来的对象会被放到Eden,第一次Mirror GC 之后,会被转移到 from survivor, 新生代采用的是复制算法进行MirrorGC  ,当from survivor不足时,对象转移到to survivor,每进行一次MirrorGC ,年龄+1,当survivor对象年龄达到一定程度时,新生代对象,转移到老年代。


2.虚拟机栈溢出

当线程请求的栈深度大于虚拟机栈支持锁允许的最大深度,或抛出StackOverFlowError异常,即是虚拟机栈过多,导致了堆栈溢出


public class JavaVMStackSOF {
  private int stackLength = 1;
  private void stackLeak(){
    stackLength++;
    stackLeak();
  }
  /**
   * VM args: -Xss128k 
   * -Xss 栈内存容量
   * @param args
   */
  public static void main(String[] args) {
    JavaVMStackSOF oom = new JavaVMStackSOF();
    try {
      oom.stackLeak();
      // Throwable ->Error, Exception
    } catch (Throwable e) {
            System.out.println("stack length:"+oom.stackLength);
//      e.printStackTrace();
    }
  }
}


以上代码会抛出

stack length:1003

java.lang.StackOverflowError 异常


虚拟机栈溢出还有一种 OutofMemoryError异常,这个异常,可以这样理解,一台机器的物理内存是4个G,其他系统应用占用2G,堆占用1G,永久代占用512M,栈可用的空间是512M,如果每个线程设置成1M,最大可创建512个线程


640.png

/**
 * 本地虚拟机栈溢出
 * 设置每个线程的栈大小:-Xss2m
 * 运行时,不断创建新的线程(且每个线程持续执行),每个线程对一个一个栈,最终没有多余的空间来为新的线程分配,导致OutOfMemoryError
 */
public class StackOOM {
    private static int 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() {
                    @Override
                    public void run() {
                        stackOOM.doSomething();
                    }
                });
                thread.start();
            }
        } catch (Throwable e) {
            System.out.println("目前活动线程数量:" + threadNum);
            throw e;
        }
    }
}

以上代码会报错

java.lang.OutOfMemoryError: unable to create new native thread


3.方法区溢出

说了堆,栈,还有个方法区(永久代)也会出现溢出,方法区什么时候溢出,主要看,方法区存放的是类信息,常量和静态变量,与编译器编译后的代码等。

溢出一般会报错 java.lang.OutOfMemoryError: PermGen space

JDK 1.8之后去除了PermGen space 使用Metaspace 代替了,如果方法区溢出,会有如下异常:

Caused by: java.lang.OutOfMemoryError: Metaspace

import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
/**
 * 设置方法区最大、最小空间:-XX:PermSize=10m -XX:MaxPermSize=10m
 * 1.8 设置 -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 * 运行时,通过cglib不断创建JavaMethodAreaOOM的子类,方法区中类信息越来越多,最终没有可以为新的类分配的内存导致内存溢出
 */
public class JavaMethodAreaOOM {
    public static void main(final String[] args){
       try {
           while (true){
               Enhancer enhancer=new Enhancer();
               enhancer.setSuperclass(JavaMethodAreaOOM.class);
               enhancer.setUseCache(false);
               enhancer.setCallback(new MethodInterceptor() {
                   @Override
                   public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                       return methodProxy.invokeSuper(o,objects);
                   }
               });
               enhancer.create();
           }
       }catch (Throwable t){
           t.printStackTrace();
       }
    }
}

4.本机直接内存溢出

本机直接内存(DirectMemory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域,但 Java 中用到 NIO 相关操作时(比如 ByteBuffer 的 allocteDirect 方法申请的是本机直接内存),也可能会出现内存溢出的异常。

相关文章
|
2月前
|
Java
使用IDEA创建项目运行我的第一个JAVA文件输出Helloword
本文介绍了如何使用IDEA(IntelliJ IDEA)创建一个新的Java项目,并运行一个简单的Java程序输出"Hello Word"。文章详细展示了创建项目的步骤,包括选择JDK版本、设置项目名称和路径、创建包和类,以及编写和运行代码。最后,还展示了如何通过IDEA的运行功能来执行程序并查看输出结果。
152 4
使用IDEA创建项目运行我的第一个JAVA文件输出Helloword
|
1月前
|
Java
Java关键字 —— super 详细解释!一看就懂 有代码实例运行!
文章详细解释了Java关键字`super`的用途,包括访问父类的成员变量、调用父类的构造方法和方法,并提供了相应的代码实例。
116 5
Java关键字 —— super 详细解释!一看就懂 有代码实例运行!
|
1月前
|
Java Apache Maven
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
文章提供了使用Apache POI库在Java中创建和读取Excel文件的详细代码示例,包括写入数据到Excel和从Excel读取数据的方法。
60 6
Java百项管理之新闻管理系统 熟悉java语法——大学生作业 有源码!!!可运行!!!
|
2月前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
64 2
|
1月前
|
Java
jvm复习,深入理解java虚拟机一:运行时数据区域
这篇文章深入探讨了Java虚拟机的运行时数据区域,包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、元空间和运行时常量池,并讨论了它们的作用、特点以及与垃圾回收的关系。
63 19
jvm复习,深入理解java虚拟机一:运行时数据区域
|
1月前
|
存储 SQL 小程序
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
这篇文章详细介绍了Java虚拟机(JVM)的运行时数据区域和JVM指令集,包括程序计数器、虚拟机栈、本地方法栈、直接内存、方法区和堆,以及栈帧的组成部分和执行流程。
32 2
JVM知识体系学习五:Java Runtime Data Area and JVM Instruction (java运行时数据区域和java指令(大约200多条,这里就将一些简单的指令和学习))
|
1月前
|
分布式计算 大数据 Java
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
25 1
大数据-86 Spark 集群 WordCount 用 Scala & Java 调用Spark 编译并打包上传运行 梦开始的地方
|
27天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
1月前
|
IDE Java 编译器
Java:如何确定编译和运行时类路径是否一致
类路径(Classpath)是JVM用于查找类文件的路径列表,对编译和运行Java程序至关重要。编译时通过`javac -classpath`指定,运行时通过`java -classpath`指定。IDE如Eclipse和IntelliJ IDEA也提供界面管理类路径。确保编译和运行时类路径一致,特别是外部库和项目内部类的路径设置。
|
1月前
|
Java
Java关键字 —— super 与 this 详细解释!一看就懂 有代码实例运行!
本文介绍了Java中this和super关键字的用法,包括在构造方法中使用this来区分参数和成员变量、使用super调用父类构造方法和方法,以及它们在同一个方法中同时使用的场景。
120 0
Java关键字 —— super 与 this 详细解释!一看就懂 有代码实例运行!
下一篇
无影云桌面