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架构沉思录

Java虚拟机内存初探

Java 程序的执行过程:Java 源代码文件(.Java文件)-> Java Compiler(Java编译器)->Java 字节码文件(.class文件)-...

1312
来自专栏Java 源码分析

Spring笔记(一)

1.基本介绍 spring 是一个一站式框架,也就是有了它 web 层,service 层还有 dao 层都能直接搞定而不需使用其他的框架。 这三层分别就是: ...

3295
来自专栏Java编程技术

什么是重排序与中断

Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序可以保证最终执行的结果是与程序顺序执行的结果一致,并且只会对不存在数据依赖性的指令进行重排序,...

762
来自专栏JMCui

多线程编程学习二(对象及变量的并发访问).

一、概念 非线程安全:会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",也就是取到的数据其实是被更改过的. 线程安全:获得的实例变...

37914
来自专栏奔跑的蛙牛技术博客

什么是字节码?

字节码(Byte-code)是一种包含执行程序,由一序列 op 代码/数据对组成的二进制文件,是一种中间码。字节是电脑里的数据量单位。

2373
来自专栏JAVA高级架构

深入了解 Java 之虚拟机内存

在讨论JVM内存区域分析之前,先来看一下Java程序具体执行的过程: ? Java 程序的执行过程:Java 源代码文件(.Java文件)-> Java Com...

3337
来自专栏向治洪

java虚拟机构造原理

 Java虚拟机的生命周期 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。程序开始执行时他才运行,程序结束时他就停止。你在同一台机器上运行三...

1856
来自专栏Ryan Miao

java并发编程读书笔记(1)-- 对象的共享

1. 一些原则 RIM(Remote Method Invocation):远程方法调用 Race Condition:竞态条件 Servlet要满足多个线程的...

3528
来自专栏谦谦君子修罗刀

静态内存区域解析

通常在代码中产生的bug,往往是源于概念不清晰。知己知彼百战不殆,对内存这块了如指掌,能极大优化代码的性能。 一、内存四区建立流程讲解 ? 如上图所示,首先操作...

2656
来自专栏待你如初见

Java多线程

823

扫码关注云+社区