【JVM】:线程的内存区域

一、线程私有的内存区域

1.Java虚拟机栈

每个方法执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。
(1)和线程相关:不同线程内,即使运行同一个方法,也是处于不同内存
(2)和方法有关:即使是同一个线程,递归调用某个方法,每次调用都会生成该次方法调用的方法栈帧。
例1:

public class JVMStackLook {
    public static void test(int[] array,int index){
        System.out.println(index);
        if (index <= 2){
            return;
        }
        test(array,index-1);
    }

    public static void main(String[] args) {
        int[] array = {4,5,10,1,15};
        test(array,10);
    }
}

流程图:
在这里插入图片描述
局部变量定义赋值时,对象(包括数组)赋值的是引用,基本数据类型和String赋值的是字面量。
例2:

public class JVMStackLook {
    public static void main(String[] args) {
        Node node = new Node("A");
        test(node);
        System.out.println(node.name);
        System.out.println(node.next.name);
    }
    public static void test(Node node){
        node.next = new Node("B");
        node = new Node("C");
    }
    public static class Node{
        private Node next;
        private String name;
        public Node(String name){
            this.name = name;
        }
    }
}

流程图:
在这里插入图片描述
Java虚拟机栈区域一共会产生以下两种异常:

  1. 如果线程请求的栈深度大于虚拟机所允许的深度(-Xss设置栈容量),将会抛出StackOverFlowError异常。
    StackOverFlowError异常:方法调用太深,比如递归,每次调用都是一次入栈,入栈的数量太多,就抛出这个异常。
  2. 虚拟机在动态扩展时无法申请到足够的内存,会抛出OOM(OutOfMemoryError)异常。
    OOM(OutOfMemoryError)异常内存溢出:线程调用方法,创建方法该次调用的栈帧,内存不足抛出OOM异常。

面试题:内存溢出和内存泄漏的区别?
内存泄漏Memory Leak:程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

  • 一般出现内存泄漏的情况:长生命周期存活的对象,内部持有不使用对象的引用,导致不使用的垃圾对象无法回收。
  • 一般程序经常出现内存泄漏的例子:在使用长期存活的数据结构、数组时,都要考虑对象引用导致内存泄漏的问题。

内存溢出(Out Of Memory,简称OOM):应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存。
结果:此时程序就运行不,系统会提示内存溢出(进程都会挂掉,情况严重)

2.本地方法栈

本地方法栈与虚拟机栈的作用完全一样,他俩的区别无非是本地方法栈为虚拟机使用的Native方法服务,而虚拟机栈为JVM执行的Java方法服务。

二、线程共享区域

1.Java堆

在JVM启动时创建,所有的对象实例以及数组都要在堆上分配。
如果在堆中没有足够的内存完成实例分配并且堆也无法再拓展时,将会抛出OOM。

2.方法区/元数据区

用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
此区域的内存回收主要是针对常量池的回收以及对类型的卸载。当方法区无法满足内存分配需求时,将抛出OOM异常。

3.运行时常量池

编译期及运行期间产生的常量被放在运行时常量池中。
这里所说的常量包括:基本类型、包装类(包装类不管理浮点型,整形只会管理-128到127)和String。
类加载时,会查询字符串常量池,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

4.直接内存

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

5.Class文件常量池

Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。
编译时生成的信息,要在运行后,类加载时把变量和对象的引用在内存中关联起来。编译时无法关联,所以使用符号引用间接关联。

6.运行时常量池

运行时常量池相对于Class文件常量池的另外一个重要特征是具备动态性,Java语言并不要求常量一定只有编译期才能产生,也就是并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中。

7.字符串常量池

存储字符串对象,或是字符串对象的引用。
在这里插入图片描述
例一:

public class StringInMemory {
	public void test1(){// 测试通过
        String s1 = "hello";
        String s2 = "hello";
        Assert.assertTrue(s1 == s2);
    }
}

true:因为都是常量池中的字面量。

例二:

public class StringInMemory {
    public void test2(){// 测试通过
        String s1 = "hello";
        String s2 = "hel" + "lo";
        Assert.assertTrue(s1 == s2);
    }
}

true:s2 = “hel” + “lo”
JVM在编译期间会进行优化:s2为字面量拼接的字符串"hello",存在常量池

例三:

public class StringInMemory {
	public void test3(){// 测试不能通过
        String s1 = "hello";
        String s2 = new String("hello");
        Assert.assertTrue(s1 == s2);
    }
}

s2创建了以下对象
1.“hello”:存在字符串常量池,如果常量池已有"hello"就不创建
2.new String(“hello”):存在堆中。
s1为常量池的"hello"对象,s2为堆中的对象new String(“hello”)

例四:

public class StringInMemory {
    public void test4(){// 测试通过
        String s1 = "hello";
        String s2 = new String("hello");
        Assert.assertTrue(s1 == s2.intern());
    }
}

调用intern方法时,如果池已经包含与equals(Object)方法确定的相当于此String对象的字符串,则返回来自池的字符串。 否则,此String对象将添加到池中,并返回对此String对象的引用。由此可见,对于任何两个字符串s和t,s.intern() == t.intern() 是true当且仅当s.equals(t)是true。

例五:

public class StringInMemory {
    public void test5(){// 测试不能通过
        String s1 = "hello";
        String s2 = "hel";
        String s3 = "lo";
        String s4 = s2 + s3;
        Assert.assertTrue(s1 == s4);
    }
}

// s4是用s2和s3两个常量池中的对象相加新生成的对象,存在堆中

例六:

public class StringInMemory {
    public void test8(){//执行成功
        // 方法栈:s1局部变量
        // 常量池生成的对象:hel,lo。注意常量池没有生成hello对象
        // 堆生成的对象:new String("hel")、new String("lo")、字符串+操作生成的new String("hello")
        String s1 = new String("hel")+new String("lo");
        // 字符串常量池中,获取或创建一个字符串对象或引用
        // 字符串常量池创建一个引用,指向s1指向的对象new String("hello")
        s1.intern();
        String s2 = new StringBuilder("hel").append("lo").toString();
        // s1的引用地址指向堆里边new String("hello")
        // s2.intern();
        Assert.assertTrue(s1 == s2.intern());
    }
}

false
在这里插入图片描述

例七:

public class StringInMemory {
    public void test7(){
        String s1 = new String("hello");
        String s2 = new StringBuilder("hel").append("lo").toString();
        Assert.assertTrue(s1 == s2.intern());
    }
}

true
在这里插入图片描述

例八:

public class StringInMemory {
    public void test8(){
        String s1 = "hello";
        String s2 = new StringBuilder("hel").append("lo").toString();
        Assert.assertTrue(s1 == s2);
    }
}

false
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值