精华内容
下载资源
问答
  • Java内存区域划分和内存分配策略

    千次阅读 多人点赞 2020-05-15 12:32:46
    Java内存区域划分和内存分配策略 如果不知道,类的静态变量存储在那? 方法的局部变量存储在那? 赶快收藏 Java内存区域主要可以分为共享内存,堆、方法区和线程私有内存,虚拟机栈、本地方法栈和程序计数器。如下...

    Java内存区域划分和内存分配策略

    如果不知道,类的静态变量存储在那? 方法的局部变量存储在那? 赶快收藏

    Java内存区域主要可以分为共享内存,方法区和线程私有内存,虚拟机栈、本地方法栈和程序计数器。如下图所示,本文将详细讲述各个区域,同时也会讲述创建对象过程,内存分配策略, 和对象访问定位原理。觉得写得好的,可以点个收藏,绝对不亏。

    Java内存区域

    程序计数器

    程序计数器,可以看作程序当前线程所执行的字节码行号指示器。字节码解释器工作时就是通过改变计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理都需要依赖计数器完成。线程执行Java方法时,记录其正在执行的虚拟机字节码指令地址,线程执行Native方法时,计数器记录为空。程序计数器时唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域。

    理论可知,线程是通过轮流获取CPU执行时间以实现多线程的并发。为了暂停的线程下一次获得CPU执行时间,能正常运行,每一个线程内部都需要维护一个程序计数器,用来记住暂停线程暂停的位置。

    注意:光理论是不够的,在此送大家一套2020最新Java架构实战教程+大厂面试宝典,点击此处 进来获取 一起交流进步哦!

    Java虚拟机栈

    Java虚拟机栈同程序计数器一样,也是线程私有的,虚拟机栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表,操作数栈、动态链接和方法出入口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

    本地方法栈

    与虚拟机栈相似。虚拟机栈为虚拟机执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务。

    Java堆

    所有线程共享的一块内存区域。Java虚拟机所管理的内存中最大的一块,因为该内存区域的唯一目的就是存放对象实例。几乎所有的对象实例都在这里分配内存,同时堆也是垃圾收集器管理的主要区域。因此很多时候被称为"GC堆"

    方法区

    和堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、和编译器编译后的代码(也就是存储字节码文件.class)等数据

    方法区中有一个运行时常量池,编译后期生成的各种字面量和符号引用,存放在字节码文件中的常量池中。当类加载进入方法区时,就会把该常量池中的内容放入方法区中的运行时常量池。此外也可以在程序运行期间,将新的常量放入运行时常量池,比如String.intern()方法,该方法先从运行时常量池中查找是否有该值,如果有,则返回该值的引用,否则将该值加入运行时常量池。

    实例详讲

    class Demo1_Car{
        public static void main(String[] args) {
            Car c1 = new Car();
            //调用属性并赋值
            c1.color = "red";
            c1.num = 8;
            //调用行为
            c1.run();
            Car c2 = new Car();
            c2.color = "black";
            c2.num = 4;
            c2.run();
        }
    }
    Class Car{
        String color;
        int num;
        public void run() {
        	System.out.println(color + ".." + num);
    	}
    }
    

    • 首先运行程序,Demo1_car.java就会变为Demo1_car.classDemo1_car.class加入方法区,检查是否字节码文件常量池中是否有常量值,如果有,那么就加入运行时常量池。
    • 遇到main方法,创建一个栈帧,入虚拟机栈,然后开始运行main方法中的程序。
    • Car c1 = new Car(), 第一次遇到Car这个类,所以将Car.java编译为Car.class文件,然后加入方法区.然后new Car(),在堆中创建一块区域,用于存放创建出来的实例对象,地址为0X001.其中有两个属性值colornum。默认值是null和 0
    • 然后通过c1这个引用变量去设置colornum的值,调用run方法,然后会创建一个栈帧,用来存储run方法中的局部变量等。run 方法中就打印了一句话,结束之后,该栈帧出虚拟机栈。又只剩下main方法这个栈帧。
    • 接着又创建了一个Car对象,所以又在堆中开辟了一块内存,之后就是跟之前的步骤一样了。

    创建对象过程

    虚拟机在遇到一条new指令时,会首先检查这个指令的参数是否可以在方法区中定位到一个类的符号引用,并且检查这个符号引用所代表的类是否已经被加载,解析和初始化过。如果没有,则必须先执行类加载过程.

    类加载完之后,需要为对象分配内存,有两种分配内存的方法

    • 指针碰撞法(要求堆内存规整)

      Java堆中空闲内存和已使用内存分别存放在堆的两边,中间存放一个指针作为分界点的指示器,在为对象分配内存时只需要将指针向空闲区域移动创建对象所需要的内存大小即可。

    • 空闲列表法

      如果堆内存中已使用内存区域和空闲区域相互交错,此时虚拟机需要维护一个列表,记录哪些内存块是可用的,在分配时从列表中找到一块足够大的内存区域划分给对象实例并更新列表上的记录。

    多线程情况下,线程同时分配内存可能会造成冲突,比如使用指针碰撞法,线程A正在分配内存,还没有改变指针指向,线程B,又同时使用原来的指针进行内存分配。防止冲突有两种方法

    • CAS操作:虚拟机采用CAS操作,加上失败重试的方式保证内存分配的原子性
    • 本地线程分配缓冲(TLAB):预先为线程分配一部分堆内存空间(线程私有,所以不存在同步问题)用于对象实例的内存分配。只有当TLAB用完,需要分配新的TLAB时,才需要进行同步操作。

    内存分配完之后,虚拟机需要将分配到的内存空间均初始化为零值(不包括对象头)。在虚拟机中,执行完new指令后会接着执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来

    对象在内存中的布局

    对象在内存中的布局如下图所示,分为对象头、实例数据、对齐填充
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传在这里插入图片描述

    • 对象头(可以参考Java锁升级)

      mark Word, 用于存储对象自身的运行时数据,如哈希码、GC分代年龄以及锁状态标志等。类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

    • 实例数据

      对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。

    • 对齐填充

      并非必然存在,仅仅起着占位符的作用。

    对象的访问定位

    Java程序需要通过栈上的reference数据来操作堆上的具体对象。共有两种策略进行对象的访问定位

    • 句柄访问

      Java堆中划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息,需要两次寻址。

    • 直接指针访问

      Java堆中对象的布局中需要考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。

    使用句柄访问的最大好处就是reference中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中实例数据指针,而reference本身不需要修改。

    问题

    只需要记住一件事,就是Java对象的内存分配均是在堆中进行的。所以对象都存储在堆中

    但是有人可能会怀疑方法的临时变量不是存储在虚拟机栈中吗?这里我要解释一下,虚拟机栈维护了一个局部变量表,表中存储的是对象的引用,而真正存储对象的地方在堆,如果局部变量都在堆里分配,那么虚拟机栈早爆满了

    同样类的静态变量,有人又会怀疑在方法区中存储。其实不是的,方法区只存储引用,具体对象是存储在堆中的,具体实现可以发现,类静态对象是与class对象一起分配的内存。

    注意:光理论是不够的,在此送大家一套2020最新Java架构实战教程+大厂面试宝典,点击此处 进来获取 一起交流进步哦!

    参考

    深入理解java虚拟机

    展开全文
  • 1.8版本 最清晰理解Java内存区域划分

    千次阅读 2019-03-27 16:31:48
    从Java的各种数据类型的存储看Java内存区域划分 一、各种基本数据类型的存储 我们先来看一段小代码: public class{ int a = 20; public static void main(String[] args){ int b = 10; String str1 = ”abc...

    写在前面

    在正式开始文章的阅读之前,我们还要区分几个概念,这很重要:(很多博客对Java内存模型,Java内存区域结构混一谈,丝毫不加辨析,以至于很多时候看完之后还是云里雾里)

    • Java内存模型:JMM与多线程相关,看起来和Java内存区域很相似,但JMM是一个抽象的概念,不是具体存在的。JMM描述了一组规则或规范,这个规范定义了一个线程对共享变量的操作对其他线程是可见的。
    • Java内存区域|Java内存结构:Java内存结构是JVM的运行时,虚拟机在执行Java程序时会内存区域划分为若干个不同的内存区域,这些区域各自都有自己的作用。只是不同的JVM虚拟机对区域的划分稍有不同,但都遵循下面第二节所规定的规范。

    一、各种基本数据类型的存储

    我们先来看一段小代码:

    public class{
    	int a = 20;
    	public static void main(String[] args){
    		int b = 10;
    		String str1 = ”abc“;
    		String str2 = new String("abc");
    	}
    }
    

    我们先初步分析一下着三个变量的存储过程:
    int 声明的都是8中基本数据类型中的一种。

    • int a 是一个类的成员变量,成员变量的生命周期是和类在一起的,类下的每一个方法对于成员变量的值都是共享的,也就是成员变量需要多个方法都可以进行访问。
    • int b 是main里面的一个局部变量,他的生命周期随着方法的结束而结束。
    • String str1和String str2的两种声明方式相同吗? 答案是否定的,我们先阐述结果,然后带着疑问继续看后面的分析。
      首先str1和str2的内容是存储在stack(栈)中的,他们是分别指向自己应当指向的内存区域(可能是栈中,也可能是堆中)
      str1是一个String类型的内容,"abc"在编译时被放入静态常量池(也就是class常量池)中,运行时被拿到运行时常量池中的字符串常量池,然后由str1指向"abc"的区域。
      str2的 new String(“abc”)是存储在内存区域中的堆中的,这个new的过程是在运行期初始化阶段才确定的,然后Stack上的str2指向heap(堆)上的new String(“abc”)。
      因为他们是指向的不同的内存区域,System.out.println(str1 == str2); 的结果也自然就是false了。

    二、Java内存区域的划分(运行时数据区)

    Java 虚拟机在执行 Java 程序的过程中会把它管理的内存划分成若干个不同的数据区域。

    1.8之前:
    在这里插入图片描述
    1.8之后:
    在这里插入图片描述
    我们可以把它们分为两个类型的区域,一种是线程私有的,另一种是线程共享的。

    • 线程共享的区域:
      • 堆(Heap)
      • 方法区(Method Area)
    • 线程私有:
      • 程序计数器(PC)
      • 虚拟机栈(VM Stack)
      • 本地方法栈(Native Method Stack)

    2.1 程序计数器(Program Counter):

    可以看作是当前线程所执行的字节码的行号指示器,他标记着我们当前执行到了哪一条指令。类比我们计算机组成中的PC计数器,他实现着我们代码的控制流程。
    在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

    2.2 Java虚拟机栈(Stack)

    与程序计数器一样,Java虚拟机栈也是线程私有的,它的生命周期和线程相同,描述的是 Java 方法执行的内存模型,每次方法调用的数据都是通过栈传递的。
    Java 内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说是虚拟机栈中局部变量表部分。 (实际上,Java虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。)

    局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。

    Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。

    • StackOverFlowError :若 Java 虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度超过当前 Java 虚拟机栈的最大深度的时候,就抛出 StackOverFlowError 异常。

    程序实现SOF:

    public class StackOverFlow {
        public int stackSize = 0;
    
        public void stackIncre() {
            stackSize++;
            stackIncre();
        }
    
        public static void main(String[] args) throws Throwable{
            StackOverFlow sof = new StackOverFlow();
            try {
                sof.stackIncre();
            } catch (Throwable e) {
                System.out.println(sof.stackSize);
                throw e;
            }
        }
    }
    
    
    • OutOfMemery:若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完了,无法再动态扩展了,此时抛出 OutOfMemoryError 异常。
      程序实现OOM:
    public class OutOfMemory {
        public static void main(String[] args){
            List list=new ArrayList();
            for(;;){
                int[] tmp=new int[1000000];
                list.add(tmp);
            }
        }
    }
    

    Java 虚拟机栈也是线程私有的,每个线程都有各自的 Java 虚拟机栈,而且随着线程的创建而创建,随着线程的死亡而死亡。

    拓展:方法/函数如何调用?

    Java 栈可用类比数据结构中栈,Java 栈中保存的主要内容是栈帧,每一次函数调用都会有一个对应的栈帧被压入 Java 栈,每一个函数调用结束后,都会有一个栈帧被弹出。

    Java 方法有两种返回方式:

    1. return 语句。
    2. 抛出异常。
      不管哪种返回方式都会导致栈帧被弹出。

    2.3 Java本地方法栈(Native Method Stack)

    和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

    本地方法被执行的时候,在本地方法栈也会创建一个栈帧,用于存放该本地方法的局部变量表、操作数栈、动态链接、出口信息。

    方法执行完毕后相应的栈帧也会出栈并释放内存空间,也会出现 StackOverFlowError 和 OutOfMemoryError 两种异常。

    2.4 堆(Heap)

    Java 虚拟机所管理的内存中最大的一块,Java 堆是所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。
    Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆(Garbage Collected Heap)。从垃圾回收的角度,由于现在收集器基本都采用分代垃圾收集算法,所以Java堆还可以细分为:新生代和老年代:在细致一点有:Eden空间、From Survivor、To Survivor空间等。进一步划分的目的是更好地回收内存,或者更快地分配内存。
    在这里插入图片描述
    在 JDK 1.8中移除整个永久代,取而代之的是一个叫元空间(Metaspace)的区域(永久代使用的是JVM的堆内存空间,而元空间使用的是物理内存,直接受到本机的物理内存限制)。

    2.5 方法区(Method Area/Non-Heap)

    方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
    HotSpot 虚拟机中方法区也常被称为 “永久代”,本质上两者并不等价。仅仅是因为 HotSpot 虚拟机设计团队用永久代来实现方法区而已,这样 HotSpot 虚拟机的垃圾收集器就可以像管理 Java 堆一样管理这部分内存了。但是这并不是一个好主意,因为这样更容易遇到内存溢出问题。(Java 8之前的实现

    在Java 8版本之前,方法区仅是逻辑上的分区,物理上并没有独立于堆而存在,而是位于永久代中。所以这时候的方法区也是可以被回收的。在Java 8版本之后Hot Spot移除了永久代,使用本地内存来存储类元数据信息,并命名为元空间

    2.5.1 方法区和永久代的关系

    《Java 虚拟机规范》只是规定了有方法区这么个概念和它的作用,并没有规定如何去实现它。那么,在不同的 JVM 上方法区的实现肯定是不同的了。 方法区和永久代的关系很像 Java 中接口和类的关系,类实现了接口,而永久代就是 HotSpot 虚拟机对虚拟机规范中方法区的一种实现方式。 也就是说,永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现,其他的虚拟机实现并没有永久带这一说法。

    2.5.2 常用参数

    JDK 1.8 之前永久代还没被彻底移除的时候通常通过下面这些参数来调节方法区大小

    -XX:PermSize=N //方法区 (永久代) 初始大小
    -XX:MaxPermSize=N //方法区 (永久代) 最大大小,超过这个值将会抛出 OutOfMemoryError 异常:java.lang.OutOfMemoryError: PermGen
    
    

    相对而言,垃圾收集行为在这个区域是比较少出现的,但并非数据进入方法区后就“永久存在”了。

    JDK 1.8 的时候,方法区(HotSpot 的永久代)被彻底移除了(JDK1.7 就已经开始了),取而代之是元空间,元空间使用的是直接内存。

    下面是一些常用参数:

    -XX:MetaspaceSize=N //设置 Metaspace 的初始(和最小大小)
    -XX:MaxMetaspaceSize=N //设置 Metaspace 的最大大小
    

    与永久代很大的不同就是,如果不指定大小的话,随着更多类的创建,虚拟机会耗尽所有可用的系统内存。

    2.5.3 为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?

    整个永久代有一个 JVM 本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机可用内存的限制,并且永远不会得到 java.lang.OutOfMemoryError。你可以使用 -XX:MaxMetaspaceSize 标志设置最大元空间大小,默认值为 unlimited,这意味着它只受系统内存的限制。-XX:MetaspaceSize 调整标志定义元空间的初始大小如果未指定此标志,则 Metaspace 将根据运行时的应用程序需求动态地重新调整大小。

    2.6 运行时常量池

    jdk1.6、1.7、1.8实际上运行时常量池的位置都发生了很大的变化,jdk1.6运行时常量池存在于方法区,jdk1.7移到了堆区,而jdk1.8运行时常量池其实是存在于与方法区和堆区相对独立的元空间,而不是在堆区。
    运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还有常量池信息(用于存放编译期生成的各种字面量和符号引用)
    在这里插入图片描述
    既然运行时常量池时方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。

    拓展:字符串常量池和运行时常量池什么关系?

    • 运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加,符号引用可以被解析为直接引用
    • JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。

    除此之外,常量池还有class constant pool
    推荐阅读:字符串常量池、class常量池和运行时常量池

    2.7 直接内存

    直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致OutOfMemoryError异常出现。
    JDK1.4中新加入的 NIO(New Input/Output) 类,引入了一种基于通道(Channel) 与缓存区(Buffer) 的 I/O 方式,它可以直接使用Native函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据。
    本机直接内存的分配不会收到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

    三、补充内容*

    3.1 String 对象的两种创建方式:

    就是我们上面(一)中的str1和str2两种创建string类型的方式。第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象
    在这里插入图片描述

    3.2 String 类型的常量池比较特殊。

    它的主要使用方法有两种:**

    • 用双引号声明出来的 String 对象会直接存储在常量池中。
    • 如果不是用双引号声明的 String 对象,可以使用 String 提供的 intern 方String.intern() 是一个 Native 方法,它的作用是:如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此 String 内容相同的字符串,并返回常量池中创建的字符串的引用。
     String s1 = new String("计算机");
    	      String s2 = s1.intern();
    	      String s3 = "计算机";
    	      System.out.println(s2);//计算机
    	      System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象,
    	      System.out.println(s3 == s2);//true,因为两个都是常量池中的String对
    

    3.3 String 字符串拼接

    		  String str1 = "str";
    		  String str2 = "ing";
    		  
    		  String str3 = "str" + "ing";//常量池中的对象
    		  String str4 = str1 + str2; //在堆上创建的新的对象	  
    		  String str5 = "string";//常量池中的对象
    		  System.out.println(str3 == str4);//false
    		  System.out.println(str3 == str5);//true
    		  System.out.println(str4 == str5);//false
    


    尽量避免多个字符串拼接,因为这样会重新创建对象。如果需要改变字符串的花,可以使用 StringBuilder 或者 StringBuffer。

    3.4 String s1 = new String(“abc”);这句话创建了几个字符串对象?

    将创建 1 或 2 个字符串。如果池中已存在字符串文字“abc”,则池中只会创建一个字符串“s1”。如果池中没有字符串文字“abc”,那么它将首先在池中创建,然后在堆空间中创建,因此将创建总共 2 个字符串对象。

    验证:

    String s1 = new String("abc");// 堆内存的地址值
    		String s2 = "abc";
    		System.out.println(s1 == s2);// 输出 false,因为一个是堆内存,一个是常量池的内存,故两者是不同的。
    		System.out.println(s1.equals(s2));// 输出 true
    

    结果:

    false
    true
    

    3.5 八种基本数据类型,基本类型的包装类和常量池

    关于包装类的知识推荐阅读:[Java基础] Java包装类及自动装箱、拆箱


    基本数据类型(String不是基本数据类型)的数据保存在stack中,如目录(一)中的b保存在栈中,10也保存在stack中,然后由b指向10
    • Java 基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
    • 两种浮点数类型的包装类 Float,Double 并没有实现常量池技术
    		Integer i1 = 33;
    		Integer i2 = 33;
    		System.out.println(i1 == i2);// 输出true
    		Integer i11 = 333;
    		Integer i22 = 333;
    		System.out.println(i11 == i22);// 输出false
    		Double i3 = 1.2;
    		Double i4 = 1.2;
    		System.out.println(i3 == i4);// 输出false
    

    Integer缓存源代码

    /**
    *此方法将始终缓存-128到127(包括端点)范围内的值,并可以缓存此范围之外的其他值。
    */
        public static Integer valueOf(int i) {
            if (i >= IntegerCache.low && i <= IntegerCache.high)
                return IntegerCache.cache[i + (-IntegerCache.low)];
            return new Integer(i);
        }
    
    1. Integer i1=40;Java 在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
    2. Integer i1 = new Integer(40);这种情况下会创建新的对象。

    Integer比较更丰富的一个例子:

    Integer i1 = 40;
      Integer i2 = 40;
      Integer i3 = 0;
      Integer i4 = new Integer(40);
      Integer i5 = new Integer(40);
      Integer i6 = new Integer(0);
      
      System.out.println("i1=i2   " + (i1 == i2));
      System.out.println("i1=i2+i3   " + (i1 == i2 + i3));
      System.out.println("i1=i4   " + (i1 == i4));
      System.out.println("i4=i5   " + (i4 == i5));
      System.out.println("i4=i5+i6   " + (i4 == i5 + i6));   
      System.out.println("40=i5+i6   " + (40 == i5 + i6));     
    

    运行结果:

    i1=i2   true
    i1=i2+i3   true
    i1=i4   false
    i4=i5   false
    i4=i5+i6   true
    40=i5+i6   true
    

    语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。

    四、总结

    我们平时讨论最多的是stack栈内存和heap堆内存,再次对数据类型在内存中的存储问题来解释一下:

    1. 方法中声明的变量,即该变量是局部变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因

      在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量。

    • 当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在JAVA虚拟机栈中
    • 当声明的是引用变量时,所声明的变量的reference(该变量实际上是在方法中存储的是内存地址值)是放在JAVA虚拟机的栈中,该变量所指向的对象是放在堆类存中的。
    1. 类中声明 的变量是成员变量,也叫全局变量,放在堆中的(因为全局变量不会随着某个方法执行结束而销毁)。
      同样在类中声明的变量即可是基本类型的变量 也可是引用类型的变量
    • 当声明的是基本类型的变量其变量名及其值放在堆内存中的
    • 引用类型时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。引用变量名和对应的对象仍然存储在相应的堆中

    参考:https://juejin.im/post/5b7d69e4e51d4538ca5730cb#heading-18
    http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool/
    https://blog.csdn.net/zm13007310400/article/details/77534349
    https://blog.csdn.net/jingjbuer/article/details/46348667

    展开全文
  • Java内存区域划分

    千次阅读 多人点赞 2018-10-07 13:11:14
    Java虚拟机所管理的内存将包括以下几个运行时数据区域  线程共享区:方法区、堆 线程私有区:虚拟机栈、本地方法栈、程序计数器   程序计数器: 是一块较小的内存空间,可以看作当前线程所执行的...

    Java虚拟机所管理的内存将包括以下几个运行时数据区域 

    • 线程共享区:方法区、堆

    • 线程私有区:虚拟机栈、本地方法栈、程序计数器

     

    程序计数器:

    是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。

    java多线程就是通过对线程的轮流切换并分配处理器的执行时间的方式来实现的,在任意时刻,一个处理器都只会执行某一个线程中的指令。所以每条线程为了切换后能恢复到正确的执行位置,每条线程都会有一个独立的程序计数器,各条线程之间互不影响,独立存储。

     

    JAVA虚拟机栈:

    线程私有,生命周期和线程一样。是描述java方法执行的内存模型。在每个方法执行时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。局部变量表包括了编译器可知的各种基本数据类型、对象引用和returnAddress类型(指向了一个字节码指令的地址)。其中64位长度的long和double类型的数据会占用2个局部变量空间,其余的数据类型只占用1个。局部变量表所占用的内存在编译器就完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

    栈内存的管理:通过压栈和弹栈操作来完成的,以栈帧为基本单位来管理程序的调用关系,每当有函数调用时,都会通过压栈方式创建新的栈帧,每当函数调用结束后都会通过弹栈的方式释放栈帧

     

    本地方法栈:

    与java虚拟机栈发挥的作用十分相似,区别是java虚拟机栈是为虚拟机执行java方法的,本地方法栈是为虚拟机执行native方法的。在本地方法栈中使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。甚至有的虚拟机(如sun hotspot)直接把本地方法栈和虚拟机栈合二为一。

     

    JAVA堆:

    是java虚拟机所管理的内存中最大的一块。java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。所有的对象实例和数组都要在堆上分配。java堆是垃圾收集器管理的主要区域,因此很多时候也被称作GC堆。

    从内存回收的角度来看,由于现在收集器基本都采用分代手机算法,所以java堆还可以细分为:新生代和老年代;再细致一点的有Eden空间、From Survivor空间、To Survivor空间等。

    从内存分配的角度来看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区。不论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然是对象实例,进一步的划分目的是为了更好地回收内存和更快地分配内存。java堆可以处于物理上的不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

    每个Java程序都运行在一个单独的JVM实例上,每一个实例唯一对应一个堆,一个Java程序内的多个线程也就运行在同一个JVM实例上,因此这些线程之间会共享堆内存。

     

    方法区:

    方法区与java堆一样。是各个线程所共享的内存区域,它用于存储class二进制文件,包含了虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然java虚拟机规范把方法去描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与java堆区分开来。对于习惯在hotspot虚拟机上开发的开发者来说,更愿意把方法区叫做永久堆。本质上并不一样,仅仅是因为hotspot设计团队把GC分堆收集扩展至方法区,或者说使用永久堆来实现方法区而已,这样hotspot的垃圾收集器可以像管理java堆一样管理这部分内存,能够省去专门为方法去编写内存管理代码的工作。但是这样更容易出现内存溢出的问题。永久堆有上限。而其他不存在永久堆的虚拟机只要没有触碰到进程可用内存上限,就不会出现问题。在jdk7中,已经将放在永久堆的字符常量池移出。

    运行时常量池是方法区的一部分,用于存放编译器生成的各种字面量和符号引用。运行时常量池相对于class文件常量池的另外一个重要特征是具备动态性。

    需要特别注意的是:

    方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待 !

    jdk1.6字符串常量池在方法区中,1.7之后在堆中。运行时常量池一直在方法区中。

    jdk1.8开始,移除永久代概念,方法区用元空间实现。

     

    常量池:

    常量池是方法区的一部分内存。常量池在编译期间就将一部分数据存放于该区域,包含基本数据类型如int、long等以final声明的常量值,和String字符串、特别注意的是对于方法运行期位于栈中的局部变量String常量的值可以通过 String.intern()方法将该值置入到常量池中。

    下面我们来看一个例子:

    声明一个类:

    public class A {
        public final String tempString="world";//这里可以把final去掉,结果等同!!
        public final char[] charArray="Hello".toCharArray();
        public char[] getCharArray() {
            return charArray;
        }
        public String getTempString() {
            return tempString;
        }
    }

    创建测试类:

    public class TestA {
        public static void main(String[] args) {
            A a1=new  A();
            A a2=new A();
    
            System.out.println(a1.charArray==a2.charArray);
            System.out.println(a1.tempString==a2.tempString);
        }
    }

    输出结果:

    false
    true

    要想明白上面字符串对比为什么输出为true你必须知道:

    这里写图片描述

    该图片截自《深入理解Java虚拟机》

    输出为true的原因:

    • 一个Class字节码文件的Class字节码文件对象只有一个常量池,常量池被所有线程共享。
    • 在常量池中,字符串被存储为一个字符序列,每个字符序列都对应一个String对象,该对象保存在堆中。所以也就是说为什么String temp="xxx";能够当成一个对象使用!!
    • 如果多个线程去访问A类中的String字符串,每次都会到常量区中去找该字符序列的引用。
    • 所以访问A类被创建的两个A类型对象的String字符串对比会输出true。

    那么为什么final类型的字符数组就不为true了呢??

    申明(不管是通过new还是通过直接写一个数组)一个数组其实在Java中就等同创建了一个对象,即每次创建类的对象都会自动创建一个新的数组空间。

    其中要注意的是:常量池中存储字符数组存储的是每个字符或者字符串。

     

    静态域:

    位于方法区的一块内存。存放类中以static声明的静态成员变量

     

    全局变量与局部变量

    局部变量:

    在方法中声明的变量,每当程序调用方法时,系统都会为该方法建立一个方法栈,其所在方法中声明的变量就放在方法栈中,当方法结束系统会释放方法栈,其对应在该方法中声明的变量随着栈的销毁而结束,这就局部变量只能在方法中有效的原因。

    在方法中生明的变量可以是基本类型的变量,也可以是引用类型的变量:

    • 当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在方法栈中。
    • 当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在方法的栈中,该变量所指向的对象是放在堆类存中的

    全局变量:

    在类中声明的变量是成员变量,也叫全局变量,放在堆中的。

    同样在类中声明的变量即可是基本类型的变量,也可是引用类型的变量:

    • 当声明的是基本类型的变量,其变量名及其只时放在堆类存中的。
    • 当声明的是引用变量时,其声明的变量仍然会存储一个内存地址值,该内存地址值指向所引用的对象。

     

    堆、栈和方法区存储数据的区别

    栈:为即时调用的方法开辟空间,存储局部变量值(基本数据类型),局部变量引用。当一段代码或者一个方法调用完毕后,栈中为这段代码所提供的基本数据类型或者对象的引用立即被释放;注意:局部变量必须手动初始化。


    堆:存放引用类型的对象,即new出来的对象、数组值、类的非静态成员变量值(基本数据类型)、非静态成员变量引用。其中非静态成员变量在实例化时开辟空间初始化值。


    方法区:存放class二进制文件。包含类信息、静态变量,常量池(String字符串和final修饰的常量值等),类的版本号等基本信息。静态成员变量是在方法区的静态域里面,而静态成员方法是在方法区的class二进制信息里面(.class文件和方法区里面的二进制信息不一样,读取.class文件按照虚拟机需要的格式存储在方法区,这种格式包括数据结构方面。)因为是共享的区域,所以如果静态成员变量的值或者常量值(String类型的值能够非修改)被修改了直接就会反应到其它类的对象中。

     

    最后,我们用以上知识来回答几个问题

     

    从堆和栈的功能以及作用来比较,堆和栈有什么不同?

    堆主要用来存放对象,栈主要是用来执行程序的。相较于堆,栈的存取速度更快,但栈的大小和生存周期必须确定,因此缺乏一定的灵活性。而堆却可以在运行时动态的分配内存,生存期不用提前告诉编译器,但这也导致了其存取速度的缓慢。

     

    不同线程调用方法为什么是线程安全的?

    任何方法每次被线程调用,都会在栈中开辟新的空间。同一方法的不同线程执行,方法与方法之间互不影响。全局变量因为是存在堆区的对象中,所以会互相干扰。

     

    成员变量存储在哪儿?

    静态成员变量存储在方法区,非静态成员变量存储在堆区。

     

    为什么局部变量不能够static修饰?

    局部变量存储在栈区,在方法调用时不能够自动初始化必须由程序员手动初始化,否则会报错,归根结底是由于static变量和局部变量存储的位置不一样。

     

    展开全文
  • Java 内存区域-运行时数据区域

    千次阅读 2018-08-19 20:46:08
    一开始看的比较快,对JVM 运行时数据区域只有一个模糊的概念,不太清楚不同内存区域里面到底存放了那些数据,所以在此记录。   我们都知道Java 与C、C++ 最大的区别就是内存管理领域(Java 有内存动态分配和垃圾...

      最近在学习JVM 的相关知识。一开始看的比较快,对JVM 运行时数据区域只有一个模糊的概念,不太清楚不同内存区域里面到底存放了那些数据,所以在此记录。

      我们都知道Java 与C、C++ 最大的区别就是内存管理领域(Java 有内存动态分配和垃圾收集技术)。在《深入理解Java虚拟机》中描述C、C++ 对内存的管理:对于从事C、C++ 的程序开发人员来说,在内存管理区域,他们即是拥有最高权力的”皇帝”,又是从事最基础工作的”劳动人员”——既拥有每个对象的”所有权”,又担负着每个对象生命开始到终结的维护任务。

      对于Java 来说,由于Java 程序是交给JVM 执行的,所以了解Java 内存区域其实就是了解JVM 内存区域。

    一、 JVM运行时数据区域

    • 什么是运行时数据区域?

    这里我们需要了解一下Java 程序的执行流程:
    这里写图片描述

    如上图所示,首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区)

    • 看看正规的描述

    Java 虚拟机在执行Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有点区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。根据《Java 虚拟机规范》的规定,Java 虚拟机所管理的内存将会包括以下几个运行时数据区。
    (蓝色为线程私有,橙色为共有)
    JVM运行时数据区

    二、各数据区介绍

    下面我们来分别了解一下各个数据区:

    1. 程序计数器

    程序计数器(Program Counter Register)是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器(也有人称PC 寄存器)。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

    解释到这里可能有的小伙伴还是很懵。前面我们谈到Java 程序的执行流程里面谈到了

    Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后
    由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行。

    由于Java 虚拟机的多线程是通过轮流切换并分配处理器执行时间的方式来实现的,任何时候,一个处理器都只会执行一条线程中的指令。因此为了线程切换后能够恢复发到正确的位置,我们就需要程序计数器来记录程序执行的位置。举个CPU 的解释栗子:当你在看砖头书的时候你妈叫你去吃饭,作为一个肥宅,肯定快乐的扔掉书就跑了。但你又继续啃书的时候才发现不知道自己看到哪了。怎么解决?下次卡个书签呗。程序计数器就是这个道理,记录程序的字节码的执行位置,当当前线程重新拥有CPU 时可以继续执行剩下的代码。

    通过上面的解释可以很清晰的理解不同的线程肯定不能共用一个程序计数器,每条线程都需要有一个独立的的程序计数器,各条线程间计数器互不影响,独立存储。我们称这类内存区域为”线程私有”。在JVM规范中规定,如果线程执行的是一个Java方法,则程序计数器中保存的是当前需要执行的指令的字节码地址;如果线程执行的是native方法,则程序计数器中的值是空(undefined)。前面我们提到了:程序计数器是一个很小的内存地址。此内存区域是唯一一个在Java 虚拟机中没有规定任何内存泄露(OutOfMemoryError)情况的区域,程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变。就像书签一样,不管卡在那,它自己不会变。

    2. Java 虚拟机栈

    Java 虚拟机栈(Java Vitual Machine Stack)跟C 中栈有点类似。与程序计数器一样,Java 虚拟机栈也是线程私有的。虚拟机栈描述的是Java 方法执行的内存模型:

    每个方法在执行的同时会创建一个栈帧(Stack
    Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

    周大大的解释可以说是很精简了。程序每执行一个方法,就会分配一个栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。讲到这里,大家就应该会明白为什么在使用递归方法的时候容易导致栈内存溢出的现象了。
    Java 虚拟机栈

    • 局部变量表

    局部变量表,顾名思义,就是用来存储在编译器可知的基本数据类型的变量(8种基本数据类型);对象引用(reference 类型, 对于引用类型的变量,存的是指向对象起始地址的引用指针)和returnAddress 类型(方法返回地址,当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址)。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。

    • 操作数栈

    操作数栈,想必学过数据结构中的栈的朋友想必对表达式求值问题不会陌生,栈最典型的一个应用就是用来对表达式求值。想想一个线程执行方法的过程中,实际上就是不断执行语句的过程,而归根到底就是进行计算的过程。因此可以这么说,程序中的所有计算过程都是在借助于操作数栈来完成的。

    • 两种异常状况
      StackOverflowError 异常
       如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常。
      OutOfMemoryError 异常
       如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

    3. 本地方法栈

    本地方法栈和虚拟机栈发挥的作用十分相似。同样是线程私有,它们之间的区别不过是虚拟机栈为Java 方法服务,而本地方法栈为虚拟机使用到的Native 方法服务。在JVM规范中,并没有对本地方发展的具体实现方法以及数据结构作强制规定,虚拟机可以自由实现它。在HotSopt虚拟机中直接就把本地方法栈和Java栈合二为一。与虚拟机栈一样,本地方法栈也会抛出StackOverflowError 异常和OutOfMemoryError 异常。

    4. Java 堆

    Java 堆(Java Heap)是Java 虚拟机所管理的最大的一块内存。Java 堆是被所有线程共享。Java 堆的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都是在这里分配内存。这一点在《Java 虚拟机规范》中的描述:

    所有的对象实例以及数组都要在堆上分配,但是随着JIT 编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化将会导致一些微妙的变化发生,所有的对象在堆上分配也变得不那么“绝对”了。

    Java 堆是垃圾收集器的主要区域,因此很多时候也叫“GC”堆。

    5. 方法区

    方法区(Method Area)与堆一样,是被哥哥线程共享的内存区域。在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。

    在Class文件中除了类的字段、方法、接口等描述信息外,还有一项信息是常量池,用来存储编译期间生成的字面量和符号引用。

    运行时常量池(Runtime Constant Pool)是方法区的一部分,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。

    在JVM规范中,没有强制要求方法区必须实现垃圾回收。很多人习惯将方法区称为“永久代”,是因为HotSpot虚拟机以永久代来实现方法区,从而JVM的垃圾收集器可以像管理堆区一样管理这部分区域,从而不需要专门为这部分设计垃圾回收机制。不过自从JDK7之后,Hotspot虚拟机便将字符串常量池从永久代移除了。

    6. 直接内存

    直接内存(Direct Memory)不是Java 虚拟机规范中定义的内存区域。

    在JDK1.4 中新加入的NIO 类,引入了一种基于通道(Channel)和缓冲区(Buffer)的I/O 形式,他可以使用Native 函数直接分配堆外内存,然后通过一个存储在Java 堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场所显著提高性能,因为避免了在Java 堆和Native 堆中来回复制数据。

    本机直接内存受本机总内存限制。

    参考资料:

      https://www.cnblogs.com/dolphin0520/p/3613043.html
      
      《深入理解Java虚拟机》

    展开全文
  • Java知识体系最强总结(2021版)

    万次阅读 多人点赞 2019-12-18 10:09:56
    本人从事Java开发已多年,平时有记录问题解决方案和总结知识点的习惯,整理了一些有关Java的知识体系,这不是最终版,会不定期的更新。也算是记录自己在从事编程工作的成长足迹,通过博客可以促进博主与阅读者的共同...
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    文章目录Java概述何为编程什么是Javajdk1.5之后的三版本JVM、JRE和JDK的关系什么是跨平台性?原理是什么Java语言有哪些特点什么是字节码?采用字节码的最大好处是什么什么是Java程序的主类?应用程序和小程序的...
  • JVM:Java内存区域和Java内存模型

    千次阅读 2018-03-12 15:56:28
    第一遍读这本书的时候感觉能看懂,但是抓不住重点,关键就记了下以下的概念: 1.Java内存区域。 2.判断对象是否可被回收。 3.垃圾回收算法。 4.类加载机制、双亲委派模型。 5.静态分派和动态分派(实现多态的...
  • Java 内存区域和内存模型是不一样的东西,内存区域是指 Jvm 运行时将数据分区域存储,强调对内存空间的划分。 而内存模型(Java Memory Model,简称 JMM )是定义了线程和主内存之间的抽象关系,即 JMM 定义了 JVM ...
  • 本文已经收录自笔者开源的 JavaGuide: https://github.com/Snailclimb (【Java学习 面试指南】 一份涵盖部分Java程序员所需要掌握的核心知识)如果觉得不错的还,不妨去点个Star,鼓励...介绍下 Java 内存区域(运...
  • 深入理解Java虚拟机-Java内存区域与内存溢出异常

    万次阅读 多人点赞 2020-01-03 21:42:24
    文章目录概述运行时数据区域程序计数器(线程私有)Java虚拟机栈(线程私有)局部变量表操作数栈动态链接方法返回地址小结本地方法栈(线程私有)Java堆(全局共享)方法区(全局共享)运行时常量池直接内存HotSpot...
  • 比如本文我们要讨论的JVM内存结构、JAVA内存结构、JAVA内存区域、Java内存模型,这就是几个截然不同的概念,但是很多人容易弄混。 可以这样说,很多高级开发甚至都搞不不清楚JVM内存结构、JAVA内存结构、JAVA内存...
  • Java内存区域JVM

    千次阅读 2019-06-01 17:32:44
    Java虚拟机在执行Java程序的过程中会把它所管理的内存...根据《Java虚拟机规范》的规定,Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如下图所示: (1)程序计数器 程序计数器(Program:Counter Re...
  • Java内存区域与Java内存模型

    千次阅读 2018-02-25 15:09:43
    Java内存区域 Java虚拟机在运行程序时会把其自动管理的内存划分为以上几个区域,每个区域都有其用途以及创建销毁的时机,其中蓝色部分代表的是所有线程共享的数据区域,而绿色部分代表的是每个线程的私有数据区域...
  • 详解 Java 内存区域

    千次阅读 2021-01-18 14:39:18
    目录1. 概述2. 运行时数据区域2.1. 程序计数器2.2. Java 虚拟机栈2.3. 本地方法栈2.4 堆2.5 方法区直接内存方法区和永久代的关系为什么要将永久代...正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存
  • JVM:自动内存管理之Java内存区域与内存溢出

    千次阅读 多人点赞 2021-01-07 12:03:22
    一、Java内存区域与内存溢出异常 1、运行时数据区域 运行时数据分为七块 先来看看JVM内存分布图 1、程序计数器 程序计数器是一个记录着当前线程所执行的字节码的行号指示器。 Java虚拟机中每条线程都有...
  • Java内存管理:在前面的一些文章了解到javac编译的大体过程、Class文件结构、以及JVM字节码指令,下面我们详细了解Java内存区域:先说明JVM规范定义的JVM运行时分配的数据区有哪些,然后分别介绍它们的特点,并指出...
  • 全面理解Java内存模型(JMM)及volatile关键字

    万次阅读 多人点赞 2017-06-12 11:25:05
    【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) ... 出自【zejian的博客】...深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深...
  • Java内存划分和分配

    千次阅读 2018-10-18 15:03:41
    综述 在这边文章中我们将了解一下Java的内存区域是怎么...Java内存区域划分 首先通过一张图来看一下Java虚拟机是如何划分内存空间的。 程序计数器:是一块较小内存,可以看作是当前线程所执行的字节码的行号指示...
  • 主要是阅读《深入理解java虚拟机:JVM高级特性与最佳实践》第二章:Java内存区域与内存溢出异常的笔记。
  • 「每日一问」Java虚拟机内存分为哪几个区域

    万次阅读 多人点赞 2020-07-02 10:15:13
    方法区 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被JVM加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 参考答案 程序计数器、虚拟机栈、本地方法栈、堆、方法区...
  • JVM规范中的五大java内存区域

    万次阅读 多人点赞 2016-03-31 16:07:53
    JVM在执行java程序时会把它所管理的内存划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间。有的数据区域随着JVM的进程而启动,有的数据区域则依赖于用户线程的启动和结束而创建和销毁
  • java虚拟机在执行java程序的过程中会把它所管理的内存划分成若干个不同的数据区域。这些区域各有用途,以及创建和销毁的时间。有的区域随着虚拟机的进程的启动而存在,有的...java内存区域详解 程序计数器: 是一...
  • Java 内存区域

    千次阅读 2021-08-23 09:55:49
    Java 虚拟机在执行Java 程序的过程中会把它管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间。有的区域随着虚拟机进程启动而一直存在,有些区域则是依赖用户线程的启动和结束而...
  • Java内存区域

    千次阅读 多人点赞 2019-10-30 20:42:05
    正是因为 Java 程序员把内存控制权利交给 Java 虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会是一个非常艰巨的任务。 运行时数据区域 Jav...
  • java虚拟机中的内存区域划分

    千次阅读 2017-04-01 11:33:50
    Java虚拟机规范规定的java虚拟机内存其实就是java虚拟机运行时数据区,其架构如下: 其中方法区和堆是由所有线程共享的数据区。 虚拟机栈,本地方法栈和程序计数器是线程隔离的数据区。 二、详解 下面来具体介绍这...
  • Java虚拟机(JVM)内存区域划分详解 最近一直没有怎么更新自己的博客,主要是由于老哥公司最近的一个招标项目忙得焦头烂额,心力憔悴(ಥ_ಥ),趁着项目的空档期来重构一下以前的一篇关于jvm内存区域划分的博客,...
  • Java内存区域与内存溢出

    千次阅读 2020-02-09 17:27:55
    Java与C语言针对“内存管理”有很的不同。 在C语言中,开发者需要维护对象的出生和死亡,往往需要为每个new出来的对象编写配套的delete/free代码来释放内存,否则可能发生内存泄漏或溢出。 而在Java中,内存由JVM...
  • Java内存区域——堆,栈,方法区等

    千次阅读 多人点赞 2018-01-02 16:15:48
    jdk1.7中, Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。 程序计数器 1. 程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是当前线程所执行...
  • 区分-JVM内存分区和Java内存模型(Java Memory Model)

    千次阅读 多人点赞 2019-05-08 17:55:13
    也是最近被问到了Java内存模型,学识浅薄,一直以为内存分区和内存模型是一个东西,现在做一下笔记整理一下以区分和学习这两个概念及其延伸的一些知识点。 开门见山 解决问题 JVM内存分区具体指的是JVM中运行时...
  • 【深入Java虚拟机】之一:Java内存区域与内存溢出

    万次阅读 多人点赞 2013-12-30 08:20:07
    Java虚拟机在执行Java程序的过程中会把他所管理的内存划分为若干个不同的数据区域Java虚拟机规范将JVM所管理的内存分为以下几个运行时数据区:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区。下面详细...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 232,767
精华内容 93,106
关键字:

java内存5大区域

java 订阅