【JVM】:线程的内存区域

【JVM】:线程的内存区域

目录

一、线程私有的内存区域1.Java虚拟机栈2.本地方法栈

二、线程共享区域1.Java堆2.方法区/元数据区3.运行时常量池4.直接内存5.Class文件常量池6.运行时常量池7.字符串常量池

一、线程私有的内存区域

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虚拟机栈区域一共会产生以下两种异常:

如果线程请求的栈深度大于虚拟机所允许的深度(-Xss设置栈容量),将会抛出StackOverFlowError异常。 StackOverFlowError异常:方法调用太深,比如递归,每次调用都是一次入栈,入栈的数量太多,就抛出这个异常。虚拟机在动态扩展时无法申请到足够的内存,会抛出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

相关推荐

大连21所高校军训时间出炉!还记得当年的挥汗岁月吗?
鞋子千千万,你却只会说shoes?外国人都是这样分类的…
鞋子千千万,你却只会说shoes?外国人都是这样分类的…
裂头蚴高温可以杀死吗
beat365最新版体育

裂头蚴高温可以杀死吗

📅 06-29 👁️ 3220
篮球世界杯
365bet手机开户

篮球世界杯

📅 06-27 👁️ 7450
qq空间我的分享在哪里?qq空间动态设置在哪?
365bet手机开户

qq空间我的分享在哪里?qq空间动态设置在哪?

📅 06-27 👁️ 9730