JVM(一)JVM内存结构

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/mxxrgxg/article/details/82893678

Java代码需要运行在虚拟机(JVM)上,而JVM为了方便管理内存,会把自己所管理的内存划分为若干个不同的数据区域,用作不同的用途,先看一下大致划分

存放内容:

    ·大多数创建的对象

    ·数组值

GC情况:

    GC工作的主要区域,回收不再被使用的对象

内存溢出:

    当需要为对象分配内存时,堆的内存占用已达到了设置的最大值,则抛出OutOfMenoryError

参数设置:

    -Xms最小堆内存,默认为物理内存的1/64,但小于1G

扫描二维码关注公众号,回复: 3999738 查看本文章

    -Xmx最大堆内存,默认为物理内存的1/4,但小于1G

注意:

    实际使用时,设置最大堆内存=最小堆内存,这样堆内存就不会频繁调整了

方法区

存放内容:

    ·已加载的类的信息(名称、修饰符等)

    ·静态变量(static)、常量(final)

    ·类中的方法信息

    ·运行时常量池(编译器生成的各自字面量和符号引用存储在class文件的常量池中,即所说的静态常量池,这些内容会在类加载之后进入运行时常量池)JDK1.6常量池在方法区,1.7在堆

GC情况:

    ·回收未被引用的类

    ·堆常量池的回收

内存溢出:

    当类加载器加载class文件到内存中时,提取的类信息要存放在方法区,而方法区的内存占用已达到了设置的最大值,则抛出OutOfMenoryError

参数设置:

    -XX:permSize方法区最小值,默认为16M  

    -XX:MaxPermSize方法区最大值,默认为64M

虚拟机栈

栈中的

存放内容: 

    ·局部变量表:八大基本数据类型数据、对象引用(该空间在编译期已经分配好,运行期不变)

    ·操作数栈

运行状况:    

    每创建一个线程就会对应创建一个虚拟机栈,栈中有多个栈帧,每调用一个方法就往栈中创建一个栈帧,栈帧是用来存放方法数据和部分过程结果的数据结构,每一个方法从调用到最终返回结果的过程,就对应一个栈帧从入栈到出栈的过程

    线程运行过程中,只有一个栈帧处于活跃状态,称为“当前活动栈帧”(对应方法为当前方法,类为当前类),当前活动栈帧始终是虚拟机栈的栈顶元素

GC情况:

    栈的生命周期和线程同步,随着线程销毁或结束,它们占用的内存会自动释放,不需要GC

内存溢出/泄露:

    1、Java程序启动一个新的线程,而没有足够的空间为该线程分配一个栈时,会抛出OutOfMenoryError

    2、当线程调用一个方法时,JVM压入一个新的栈帧到这个线程对应的栈中,只要这个方法还没返回,这个栈帧就存在,如果方法嵌套的调用层数太多,如递归,随着栈帧的增多,最终导致这个线程栈中的栈帧总大小超出-Xss所设置大小,抛出StackOverFlowError

大小设置:

    -Xss设置每个线程的堆栈大小,通常1M即可

本地方法栈

    和虚拟机栈作用相似,不过虚拟机栈为Java方法提供服务,本地方法栈为Native方法提供服务

程序计数器(PC寄存器)

存放内容:

    保存当前线程执行的虚拟机字节码指令的内存地址

GC情况:

    唯一一个没有规定任何OutOfMenory情况的区域

    

内存结构的定义是由JVM所决定的,所以JVM产商不同,内存结构会稍有不同,不过大体上都受《Java虚拟机规范》的约束。此处我们提几个比较重要的点:

  1. 规范中定义的方法区,只是一种概念上的区域,只说明了它应该具有什么样的功能,但并没有规定这个区域应该处于何处,所以不同的虚拟机可能实现不同;
  2. 不同版本JDK的方法区所处位置不同,上面的图划分了逻辑区域,并不是物理区域,比如某些版本的JDK中,方法区是在堆中实现的;
  3. 运行时常量池用于存放编译期生成的各种字面量(数字)和符号应用,但Java并不要求常量只有在编译期才能产生,比如在运行期,String.intern也会把新的常量放入池中;
  4. Java程序运行期间除了以上描述的内存外,还有一块内存区域可供使用,即直接内存。《Java虚拟机规范》并没有定义它,因为它并不由JVM进行管理,它是由本地方法库直接在堆外申请的内存区域;
  5. 堆和栈的数据划分也不是绝对的,比如HotSpot的JIT会针对对象分配做相应的优化。

 

实例解析:

//例1
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = new Integer(40);
System.out.println("i1=i2 ?" + (i1==i2));//true  因为Java创建了[-128,127]的缓存数据,会直接引用该值在常量池的地址
System.out.println("i1=i3 ?" + (i1==i3));//false new出来的对象在堆中,而i1在常量池

Integer i4 = i2 + 0;
Integer i5 = new Integer(0);
Integer i6 = i3 + i5;
Integer i7 = i2 + i5;
System.out.println("i1=i4 ?" + (i1==i4));//true  
System.out.println("i1=i6 ?" + (i1==i6));//true  因为Java数学计算都先进行拆箱,这里的i6就相当于Integer i6 = 40;
System.out.println("i1=i7 ?" + (i1==i7));//true  同上
//例2
int j0 = 400;
Integer j1 = 400;
Integer j2 = 400;
Integer j3 = new Integer(400);
System.out.println("j0=j1 ?" + (j0==j1));//true  因为Integer会自动拆箱成int
System.out.println("j1=j2 ?" + (j1==j2));//false 因为超过了127,所以会自动装箱成new Integer
System.out.println("j1=j3 ?" + (j1==j3));//false j1和j3都是new Integer

Integer j4 = 0;
Integer j5 = j2 + j4;
Integer j6 = new Integer(0);
Integer j7 = j3 + j6;
Integer j8 = j2 + j6;
System.out.println("j1=j5 ?" + (j1==j5));//false  
System.out.println("j1=j7 ?" + (j1==j7));//false
System.out.println("j1=j8 ?" + (j1==j8));//false
//例3
String a = "我只是一个字符串比较长的字符串";
String b = "我只是一个字符串比较长的字符串";
String c = new String("我只是一个字符串,比较长的字符串");
System.out.println("a=b ?" + (a==b));  //true  直接存入常量池
System.out.println("a=c ?" + (a==c));  //false 堆与常量池的比较
    	
String d = "我只是一个字符串";
String e = "比较长的字符串";
String f = d + e;
String g = "我只是一个字符串" + "比较长的字符串";
System.out.println("a=f ?" + (a==f));  //false
System.out.println("a=g ?" + (a==g));  //true
//关于最后两条的解释,可以看下底部参考文章中的《触摸java常量池》中的解释

 

参考文章

JVM内存结构 VS Java内存模型 VS Java对象模型

JVM内存结构

触摸java常量池

猜你喜欢

转载自blog.csdn.net/mxxrgxg/article/details/82893678