Java学习笔记——内存管理Java内存管理

Java内存管理

简介

Java虚拟机的内存管理分为以下几个运行时数据区:

  • 方法区
  • 虚拟机栈
  • 本地方法栈
  • 程序计数器

其中,方法区是所有线程共享的数据区,而其他的是线程隔离的数据区。

Java堆,又称GC堆,是GC的管理的主要区域。在虚拟机启动时创建。主要作用是存放对象实例,几乎所有的对象实例都会存放在Java堆中。Java堆可以处于物理不连续的内存空间中,只要逻辑连续即可。通常Java堆是可扩展的。当Java堆无法申请到所需的内存空间来存放实例,也无法扩展时,会抛出,OutOfMemoryError异常。

OOM实践

public class HeapOOM{
  static class OOMObject{
  }
  public static void main (String[]args) {
      List<OOMObject> list=new ArrayList<OOMObject>();
      while (true) {
          list.add(new OOMObject);    
      }
  }
}

如上面demo代码所展示的。由于OOMObject的实例是存放在堆上。当使用死循环进行创建时,便会逐渐占满堆的空间,最后产生OutOfMemoryError


虚拟机栈

Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈是Java方法执行的内存模型。每个方法在执行的同时会创建一个栈帧。用于存放局部变量表操作数栈动态链接方法出口等信息。

局部变量表存放了编译期可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double)、对象引用(reference)、returnAddress类型(指向一条字节码指令的地址)。

局部变量表所需的内存空间在编译期内完成分配。当进入一个方法时,这个方法需要帧中分配多大的局部内存是完全确定的,期间不会改变大小。

在这一区域中,虚拟机有两种异常。

  1. 线程请求的栈深度大于虚拟机所允许的深度。
  2. 虚拟机栈无法申请足够的动态内存。

OOM实践

线程请求的栈深度大于虚拟机所允许的深度
public class JavaVMStackSOF{ 
    private int stackLength=1;
    public void stackLeak  {
        stackLength++; 
        stackLeak();
        
    }
    public static void main (String[] args) throws Throwable{            
        JavaVMStackSOF oom = new JavaVMStackSOF();  
        try{
            oom.stackLeak();   
        }catch Throwable (e) {
            System.out.println ("stack length "+oom.stackLength);
            throw e ;
        }
    }
}
虚拟机栈无法申请足够的动态内存
public class JavaVMStackOOM{ 
    private void dontStop  {
        while (true) {
        }
    }
    public void stackLeakByThread  { 
        while (true) {
            Thread thread=new Thread (new Runnable()  { 
                @Override
                public void run  {
                    dontStop();   
                }
            });   
            thread.start();    
        }
    }
    public static void main (String[] args) throws Throwable{ 
        JavaVMStackOOM oom=new JavaVMStackOOM();    
        oom.stackLeakByThread();
    }

我们通过两种形式的迭代,展现了虚拟机栈两种OOM的产生原因。


方法区

与Java堆一样是各个线程共享的内存区域。它用于存放虚拟机加载的类信息、常量、静态变量和即时编译器编译后的代码。

OOM实践

/**
*VM Args -XX PermSize=10M-XX MaxPermSize=10M *@author zzm
*/
public class RuntimeConstantPoolOOM{
    public static void main (String[] args) {      
        List<String> list = new ArrayList<String>();      
        int i=0;
        while (true) {
            list.add(String.valueOf(i++).intern());    
        }
    }
}
intern

public String intern()

返回字符串对象的规范化表示形式。

一个初始时为空的字符串池,它由类 String 私有地维护。

当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。

它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。

所有字面值字符串和字符串赋值常量表达式都是内部的。

返回:

一个字符串,内容与此字符串相同,但它保证来自字符串池中。

说明中的字符串池,也就是我们上文说的方法区。


本地方法栈

本地方法栈与虚拟机栈的机理类似,不再赘述。


程序计数器

  1. 简述 程序计数器(program counter register)只占用了一块比较小的内存空间,有时可以忽略不计的。
  2. 作用 可以看作是当前线程所执行的字节码文件(class)的行号指示器。在虚拟机的世界中,字节码解释器就是通过改变计数器的值来选取下一条执行的字节码指令,分支、循环、跳转、异常处理、线程恢复都需要它完成/
  3. 特性
    1. 因为处理器在一个确定是时刻只会执行一个线程中的指令,线程切换后,是通过计数器来记录执行痕迹的,因而可以看出,程序计数器是每个线程私有的。
    2. 如果执行的是java方法,那么记录的是正在执行的虚拟机字节码指令的地址的地址,如果是native方法,计数器的值为空(undefined)。
    3. 这个内存区域是唯一一个在java虚拟界规范中没有规定任何OutOfMemoryError的情况的区域。

直接内存

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError 异常出现,所以我们放到这里一起讲解。

在JDK 1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java 堆里面的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java 堆和Native 堆中来回复制数据。显然,本机直接内存的分配不会受到Java 堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括RAM 及SWAP 区或者分页文件)的大小及处理器寻址空间的限制。

服务器管理员配置虚拟机参数时,一般会根据实际内存设置-Xmx等参数信息,但经常会忽略掉直接内存,使得各个内存区域的总和大于物理内存限制(包括物理上的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。

如有问题,欢迎指正。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java帮帮-微信公众号-技术文章全总结

数据结构基础(1) --Swap ; Bubble-Sort ; Select-Sort

Swap的简单实现 //C语言方式(by-pointer): template <typename Type> bool swapByPointer(T...

3308
来自专栏java思维导图

深入浅出Java中JVM内存管理

Java岗位面试,JVM是对程序员基本功考察,通常会问你对JVM了解吗? 可以分几部分回答这个问题,首先JVM内存划分 | JVM垃圾回收的含义 | 有哪...

702
来自专栏微信公众号:Java团长

Java提高篇——对象克隆(复制)

不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

1083
来自专栏java一日一条

Java内存管理原理及内存区域详解

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间。Java虚拟机所管理的内存将会包...

331
来自专栏java一日一条

Java内存管理原理及内存区域详解

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间。Java虚拟机所管理的内存将会包...

291
来自专栏Java职业技术分享

可能是把Java内存区域讲的最清楚的一篇文章

对于 Java 程序员来说,在虚拟机自动内存管理机制下,不再需要像C/C++程序开发程序员这样为内一个 new 操作去写对应的 delete/free 操作,不...

700
来自专栏每日一篇技术文章

Swift3.0 - 流控制

需求二: 输入一个顶点 判断是否在X轴上,或者Y轴上,或者既不在x轴,也不再Y轴上

602
来自专栏java系列博客

深入理解Java内存模型(四)——volatile

1312
来自专栏李鹏的专栏

Java 虚拟机管理的内存运行时数据区域解释

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的...

820
来自专栏Android 研究

Java虚拟机基础——2JVM运行时数据区

本篇文章主要讲解JVM运行时数据区,所以我们按照线程是否私有的维度将本篇文章一分为二,分为线程私有数据区和所有线程共有的数据区。而在线程私有的数据区又可以分为程...

845

扫码关注云+社区