JVM 规范——内存区域的划分以及理解

JVM内存区域划分概况

Java虚拟机规范划分了七个内存区域,其中根据是否为线程私有的分为两大类:
1、线程私有的:线程栈,本地方法栈,程序计数器
2、线程共享的:方法区,常量池,直接内存区,堆
其中常说堆和栈分别指的是 线程栈和堆

各内存区域的理解

一、程序计数器

【作用】

java多线程是通过JVM切换时间片来实现的,对于一个CPU来说,某一时刻只有一个线程可以执行指令。而程序计数器的意义在于,线程切换到执行位置的时候,可以从程序计数器那知道下一条执行的指令

1、程序计数器是线程私有的,每个线程自己维护自己的程序计数器
2、只占用很小的一部分内存空间

二、线程栈

线程栈主要存放方法的入口和出口,以及 局部变量引用类型 ,这些局部变量包括基本类型和引用类型。线程栈是比较容易和堆分配混淆的,后面再详细讲解一下两者的内存划分

三、本地方法栈

顾名思义,该区域主要是给调用本地方法的线程分配的,该区域和线程栈的最大区别就是,在该线程的申请的内存 不受GC管理 ,需要调用者自己管理,JDK中的Math类的大部分方法都是本地方法,一个值得注意的问题是,在执行本地方法时,并不是运行字节码,所以之前所说的指令计数器是没法记录下一条字节码指令的,当执行本地方法时,指令计数器置为undefined。

四、方法区

这块区域是用来存放JVM装载的class的类信息,包括:类的方法、静态变量、类型信息(接口/父类),我们使用反射技术时,所需的信息就是从这里获取的。

伍、常量池

private final int size=50;

这个程序中size因为用final修饰,不能再修改它的值,所以就成为常量,而这常量将会存放在常量区,这些常量在编译时就知道占用空间的大小,但并不是说明该区域编译就固定了,运行期也可以修改常量池的大小,典型的场景是在使用String时,你可以调用String的 intern(),JVM会判断当前所创建的String对象是否在常量池中,若有,则从常量区取,否则把该字符放入常量池并返回,这时就会修改常量池的大小,比如JDK中java.io.ObjectStreamField的一段代码:

 ObjectStreamField(Field field, boolean unshared, boolean showType) {
     this.field = field;
     this.unshared = unshared;
     name = field.getName();
     Class ftype = field.getType();
     type = (showType || ftype.isPrimitive()) ? ftype : Object.class;
     signature = ObjectStreamClass.getClassSignature(ftype).intern();
  }

这段代码将获取的类的签名放入常量池。HotSpot中并没有单独为该区域分配,而是合并到方法区中。

六、直接内存区

直接内存区并不是JVM可管理的内存区。在JDK1.4中提供的NIO中,实现了高效的R/W操作,这种高效的R/W操作就是通过管道机制实现的,而管道机制实际上使用了本地内存,这样就避免了从本地源文件复制JVM内存,再从JVM复制到目标文件的过程,直接从源文件复制到目标文件,JVM通过DirectByteBuffer操作直接内存。

七、堆(也叫Java堆)

堆是JVM最重要的一块区域,我们通常所说的GC主要就是在这块区域中进行的,所有的java对象都在这里分配,这也是JVM中最大的内存区域,被所有线程共享,成千上万的对象在这里创建,也在这里被销毁。关于这一块后面单独分一章来讲解。

举例

package com.myproj.test.jvm;

import java.util.Date;

public class Example {

    private final int constant1 = 1;     //常量,放在【常量池】
    private static int staticVar = 2;    //静态变量,【方法区】

    //静态方法,放在【方法区】
    public static void test2(){
        System.out.println(staticVar);
    } 

    public void test(){
        /*创建了一个引用类型d,由【线程栈】为之分配内存,占4个字节,这算是一个对象(所有的引用类型都是占四个字节的)*/
        Date d = null;  
        /*创建了另外一个对象 new Date(),这里由【堆】为变量分配内存*/ 
        d = new Date();   
        System.out.println(constant1);
        System.out.println(d.toString());
    }

    public static void main(String[] args) {
        Example e = new Example();
        e.test();
    }
}

解析

1、Date d = new Date(); 这句简单的语句其实创建了两个对象,对象1是 Date d,d是一个引用类型,它占用了线程栈4个字节大小,对象2 new Date()是一个Date()类型的对象,它由堆进行内存分配

2、局部变量的内存分配:如在test()方法里的变量d在方法执行的时候,会在线程栈和堆中分配内存,但是在方法执行完毕后,变量占用的空间就会被释放

3、非局部变量的内存分配:非局部变量的内存回收,在上面的代码中new Date()就和C++里的new创建的对象一样,是在heap中分配,其占用的空间不会随着方法的结束而自动释放需要一定的机制去删除,在C++中必须由程序员在适当时候delete掉,在java中这部分内存是由GC自动回收的,但是要进行内存回收必须解决两问题:那些对象需要回收、怎么回收。这就涉及到了JVM的垃圾回收机制,这单独分一个章节来讲

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值