JVM内存结构之堆、栈、方法区以及直接内存、堆和栈区别

一、  理解JVM中堆与栈以及方法区

堆(heap):FIFO(队列优先,先进先出);二级缓存;*JVM中只有一个堆区被所有线程所共享;对象和数组储存在里面;调用对象速度较慢;生命周期由虚拟机JVM的垃圾回收机制GC制定;由JVM动态分配空间;堆内存用来存放由new创建的对象和数组。

在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。

栈(stack):FILO 或者叫LIFO(线性表,后进先出);一级缓存;每个线程都会有一个独立的栈空间,所以线程之间是不共享数据的;对象的引用变量以及基本数据类型存储在里面;速度较快;生命周期:当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用;缺乏灵活性。

方法区:方法、静态变量(static)、常量池;方法区又称为永久区或者代码区,存放的是一些固定不变的代码 方法,静态的,常量池

二、  堆和栈的优缺点

1、   大家都知道java也叫做“C++-”;堆和栈对C++而言是需要程序员去直接管理堆和栈的,而JAVA是自动管理堆和栈的,栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方

2、   栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器GC会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢;栈有一个很重要的特殊性,就是存在栈中的数据可以共享。

a、   最主要的区别就是栈内存用来存储局部变量和方法调用。而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。

b、   栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。

c、   如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError。

而如果是堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError。

d、   栈的内存要远远小于堆内存,如果你使用递归的话,那么你的栈很快就会充满。如果递归没有及时跳出,很可能发生StackOverFlowError问题。

你可以通过-Xss选项设置栈内存的大小。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。(关于-Xss与-Xms以及-Xmx是不是很眼熟?我看到的时候马上想到了eclipse.ini下面设置,有想了解的这里:https://www.jb51.net/article/126323.htm)

这就是Java中堆和栈的区别。理解好这个问题的话,可以对你解决开发中的问题,分析堆内存和栈内存使用,甚至性能调优都有帮助。

三、  在代码编译与运行中去理解

Java Heap Memory

(对象级)

堆内存(heap memory)是被用来在runtime的时候给对象和jre的那些class分配内存的。注意是runtime的时候。不管你何时创建对象,创建任何一个对象,这些对象都是被创建在了heap空间里的。那个我们熟悉的gc(垃圾回收站)负责把那些不再被引用(reference)的对象从heap memory中清理掉,这也是gc的职责所在。在heap空间里创建的任何对象都是全局访问的。可以被应用程序的任何地方引用。

Java Stack Memory

(线程级)

java里的stack内存(stack memory)是被用来线程的执行的。也就是stack是线程级别的。而heap是对象级别的。这个stack里边包含了方法里边那些定义的值,这些值随着一次方法执行完毕后就消失了;还包含了引用地址。这个引用地址就是对存放在heap memory中的一个链接。你可以理解为关系数据库里边的外键,nosql中的外链。总之你理解就行。stack memory由于她是个stack结构。所以呢,他也遵循LIFO,就是后进先出的顺序。一个方法不论什么时候被调用,一个针对该方法的全新的block就会在stack memory里被创建,用来存储这个方法里的本地基本类型的值以及这个方法对其他对象的引用地址。一旦方法执行结束,这个block的状态就变为unused了,就是变为了空闲状态,就变为available了。宣布单身了,等着下一个method来用她。stack memory的size相比heap memory的size要小得多。

现在就让我们上一个simple program来更好的理解一下堆栈memory。

Memory.java

public class Memory {

public static void main(String[] args) { // Line1

int i=1; // Line 2

Object obj = new Object(); // Line 3

Memory mem = new Memory(); // Line 4

mem.foo(obj); // Line 5

} // Line 9

private void foo(Object param) { // Line 6

String str = param.toString(); //// Line 7

System.out.println(str);

} // Line 8

}

下面这个图就展示了在上面这个程序中的stack和heap memory的存储和引用关系。堆栈怎么被用来存储基本类型值(primitive value)以及对象以及对象的引用。

接下来我们就一步步的来看上面的那个program的执行情况。

  • 一旦我们运行了这个程序,它就会把所有的runtime class load 到 heap空间。当main()方法在line1那个地方被发现后,Java Runtime就会创建stack memory给main()方法这个线程来用。
  • 在line2那个地方,我们创建了一个primitive(基本类型)的局部变量,这个变量自然是被存储到了main()的stack memory里的。
  • 在line3那个位置,我们创建了一个对象,按照前面说的,这个对象自然是存储在heap memory里边的,并且在stack memory里边也有个这个对象的引用地址被存储了进去。line4的对象创建过程和line3是一样的。
  • 现在我们来到了line5这个地方,这一行我们调用了foo()方法,这时候一个block在stack的顶部(为什么是顶部了?因为栈是队列,先进先出)被创建,这个block现在专门为foo()方法服务。由于java是按值传递,所以在line6那个位置一个新的对象引用就会在foo() 方法的stack block中被创建。
  • 在line7那个位置,一个字符串被创建,这个串是在heap空间的string池(String Pool)中。并且对这个string对象的引用自然也在foo()方法的stack空间里被创建了。
  • 在line8那个地方foo()方法就被终止了,在方法结束的时候,在stack中为foo()分配的那个block重新变回空窗期,宣布available了。
  • 在line9那个地方,main()方法也要结束了。自然为main()创建的stack memory就会被destory掉了。自此,Java Runtime 释放所有的memory然后结束程序的执行!

对以上内容的原理解释(并没有深入):

JVM中的堆和栈

JVM是基于堆栈的虚拟机.JVM为每个新创建的线程都分配一个堆栈.也就是说,对于一个Java程序来说,它的运行就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进行两种操作:以帧为单位的压栈和出栈操作。 我们知道,某个线程正在执行的方法称为此线程的当前方法.我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧。这个帧自然成为了当前帧.在此方法执行期间,这个帧将用来保存参数,局部变量,中间计算过程和其他数据.这个帧在这里和编译原理中的活动纪录的概念是差不多的.

从Java的这种分配机制来看,堆栈又可以这样理解:堆栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。

每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用所有的线程 共享.跟C/C++不同,Java中分配堆内存是自动初始化的。Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在堆栈中分配,也就是说在建立一个对象时从两个地方都分配内存,在堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是一个指向这个堆对象的指针(引用)而已。

Java 把内存划分成两种:一种是栈内存,另一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。

四、  直接内存

直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中农定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通脱一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

关于NIO这里大佬博客:https://www.cnblogs.com/geason/p/5774096.html

我会在后续借鉴与总结

本机直接内存的分配不会受到Java 堆大小的限制,受到本机总内存大小限制

配置虚拟机参数时,不要忽略直接内存 防止出现OutOfMemoryError异常

直接内存(堆外内存)与堆内存比较

package com.xnccs.cn.share;

import java.nio.ByteBuffer;

/**

* 直接内存 与 堆内存的比较

*/

public class ByteBufferCompare {

public static void main(String[] args) {

allocateCompare(); //分配比较

operateCompare(); //读写比较

}

/**

* 直接内存 和 堆内存的 分配空间比较

*

* 结论: 在数据量提升时,直接内存相比非直接内的申请,有很严重的性能问题

*

*/

public static void allocateCompare(){

int time = 10000000; //操作次数

long st = System.currentTimeMillis();

for (int i = 0; i < time; i++) {

//ByteBuffer.allocate(int capacity) 分配一个新的字节缓冲区。

ByteBuffer buffer = ByteBuffer.allocate(2); //非直接内存分配申请

}

long et = System.currentTimeMillis();

System.out.println("在进行"+time+"次分配操作时,堆内存 分配耗时:" + (et-st) +"ms" );

long st_heap = System.currentTimeMillis();

for (int i = 0; i < time; i++) {

//ByteBuffer.allocateDirect(int capacity) 分配新的直接字节缓冲区。

ByteBuffer buffer = ByteBuffer.allocateDirect(2); //直接内存分配申请

}

long et_direct = System.currentTimeMillis();

System.out.println("在进行"+time+"次分配操作时,直接内存 分配耗时:" + (et_direct-st_heap) +"ms" );

}

/**

* 直接内存 和 堆内存的 读写性能比较

*

* 结论:直接内存在直接的IO 操作上,在频繁的读写时 会有显著的性能提升

*

*/

public static void operateCompare(){

int time = 1000000000;

ByteBuffer buffer = ByteBuffer.allocate(2*time);

long st = System.currentTimeMillis();

for (int i = 0; i < time; i++) {

// putChar(char value) 用来写入 char 值的相对 put 方法

buffer.putChar('a');

}

buffer.flip();

for (int i = 0; i < time; i++) {

buffer.getChar();

}

long et = System.currentTimeMillis();

System.out.println("在进行"+time+"次读写操作时,非直接内存读写耗时:" + (et-st) +"ms");

ByteBuffer buffer_d = ByteBuffer.allocateDirect(2*time);

long st_direct = System.currentTimeMillis();

for (int i = 0; i < time; i++) {

// putChar(char value) 用来写入 char 值的相对 put 方法

buffer_d.putChar('a');

}

buffer_d.flip();

for (int i = 0; i < time; i++) {

buffer_d.getChar();

}

long et_direct = System.currentTimeMillis();

System.out.println("在进行"+time+"次读写操作时,直接内存读写耗时:" + (et_direct - st_direct) +"ms");

}

}

输出:

在进行10000000次分配操作时,堆内存 分配耗时:12ms

在进行10000000次分配操作时,直接内存 分配耗时:8233ms

在进行1000000000次读写操作时,非直接内存读写耗时:4055ms

在进行1000000000次读写操作时,直接内存读写耗时:745ms

可以自己设置不同的time 值进行比较

分析

从数据流的角度,来看

非直接内存作用链:

本地IO –>直接内存–>非直接内存–>直接内存–>本地IO

直接内存作用链:

本地IO–>直接内存–>本地IO

直接内存使用场景:

有很大的数据需要存储,它的生命周期很长

适合频繁的IO操作,例如网络并发场景

直接内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显

直接内存IO读写的性能要优于普通的堆内存,在多次读写操作的情况下差异明显

***************************************************************以上内容经我学习了大佬的博客后总结和搬运的:

---------------------

原文:

https://blog.csdn.net/qq_41675686/article/details/80400775

或上海尚学堂java培训整理编辑

https://mp.weixin.qq.com/s/lVgrYh2jRBqUnSLnjUaAjg?

https://blog.csdn.net/qq_31997407/article/details/79675371

 

JVM内存结构之堆、栈、方法区以及直接内存、堆和栈区别的更多相关文章

  1. JVM堆 栈 方法区详解

    一.栈 每当启用一个线程时,JVM就为他分配一个JAVA栈,栈是以帧为单位保存当前线程的运行状态 栈是由栈帧组成,每当线程调用一个java方法时,JVM就会在该线程对应的栈中压入一个帧 只有在调用一个 ...

  2. [二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义

    前言简介 class文件是源代码经过编译后的一种平台中立的格式 里面包含了虚拟机运行所需要的所有信息,相当于 JVM的机器语言 JVM全称是Java Virtual Machine  ,既然是虚拟机, ...

  3. jvm:内存结构(堆、方法区、程序计数器、本地方法栈、虚拟机栈)

    1.jvm内存结构 静态编译:把java源文件编译成字节码文件class,这个时候class文件以静态方式存在. 类加载器:把java字节码文件加载到内存中 方法区:将字节码放到方法区作为元数据(简单 ...

  4. Java 底层机制(JVM/堆/栈/方法区/GC/类加载)

    转载:https://www.jianshu.com/p/ae97b692614e?from=timeline JVM体系结构 JVM是一种解释执行class文件的规范技术.   JVM体系结构 我翻 ...

  5. JVM之栈、堆、方法区(三)

    一.CPU和内存的交互 今天除夕,祝大家新年快乐,其实,我们知道的,我们的CPU跟内存会有非常频繁的交互,因为如果这个频繁的交互是交给我们的磁盘的话,那么随着我们的CPU运转速度越来越快,那么我们的磁 ...

  6. [转载]JAVA内存分析——栈、堆、方法区 程序执行变化过程

    面向对象的内存分析 参考:http://www.sxt.cn/Java_jQuery_in_action/object-oriented.html :尚学堂JAVA300集-064内存分析详解_栈_堆 ...

  7. 从几个sample来学习JAVA堆、方法区、JAVA栈和本地方法栈

    最近在看<深入理解Java虚拟机>,书中给了几个例子,比较好的说明了几种OOM(OutOfMemory)产生的过程,大部分的程序员在写程序时不会太关注Java运行时数据区域的结构: 感觉有 ...

  8. java 堆 栈 方法区的简单分析

    Java里的堆(heap)栈(stack)和方法区(method) 基础数据类型直接在栈空间分配, 方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收.   引用数据类型,需要用new来创 ...

  9. java中栈,堆,方法区

    最近在看面试题复习javaee,所以在这里对栈,堆,方法区做一下整理 参考了https://www.cnblogs.com/hqji/p/6582365.html 1.栈 每个线程包含一个栈区,栈中只 ...

随机推荐

  1. 浅析Web API中FromBody属性

    比较如下两段代码及测试结果: public class ValuesController : ApiController { // POST api/<controller> public ...

  2. 记录一则rebuild index消除索引碎片的效果

    背景:在一次某客户的停产维护中,有一项例行对大表rebuild索引的操作,本是按部就班的操作,其效果却出乎我的意料,大部分索引的效果前后都有4倍左右的变化,最大的那个索引前后居然差了7倍多,并且重建索 ...

  3. MACD各分时背离所对应的时间

    MACD各分时背离所对应的时间 5分钟背离结构——2小时.           15分钟背离结构——一天半(6小时).           30分钟背离结构——3天(12小时).            ...

  4. CentOS中利用Docker安装RabbitMQ

    CentOS中利用Docker安装RabbitMQ 1.拉取镜像(带管理平台) #docker pull rabbitmq:3.7.7-management 2.启动容器: #docker run - ...

  5. c#如何解析时区字符串

    常见时区缩写可参考: http://time.123cha.com/knowledge/6.html 常见时区缩写如下: IDLE +12:00 国际日期变更线,东边  NZDT +13:00 新西兰 ...

  6. kali linux wmtools安装

    1,选择挂载盘时选择自动检测 2,点击安裝vmware tools安裝 3.tar -xzf 壓縮包名 4../vmware-install.pl 5,reboot

  7. 即时通信系统中实现聊天消息加密,让通信更安全【低调赠送:C#开源即时通讯系统(支持广域网)——GGTalk4.5 最新源码】

    在即时通讯系统(IM)中,加密重要的通信消息,是一个常见的需求.尤其在一些政府部门的即时通信软件中(如税务系统),对即时聊天消息进行加密是非常重要的一个功能,因为谈话中可能会涉及到机密的数据.我在最新 ...

  8. python seek()方法报错:“io.UnsupportedOperation: can't do nonzero cur-relative seeks”

    今天使用seek()时报错了, 看下图 然后就百度了一下,找到了解决方法 这篇博客https://www.cnblogs.com/xisheng/p/7636736.html 帮忙解决了问题, 照理说 ...

  9. LoadRunner录制登录机票网址,并回放,加断言

    回放录制登录过程脚本,加断言 在页面登录的过程如下: 1先进入http://127.0.0.1:1080/WebTours/index.htm 2之后获取userSession信息 3在输入信息后点击 ...

  10. [转载]C#中Invoke的用法()

    invoke和begininvoke 区别 一直对invoke和begininvoke的使用和概念比较混乱,这两天看了些资料,对这两个的用法和原理有了些新的认识和理解. 首先说下,invoke和beg ...