精华内容
下载资源
问答
  • Java类与对象的地址空间分析

    千次阅读 2018-03-03 23:00:41
    大家都知道Java除了8种基本的数据类型就只有引用类型,也就是说类属于应用类型,我们操作类的时候实际上就是操作类的地址空间,而这一切的实现过程就是引用。下面我将通过举一个例子详细分析程序运行过程中类地址...

        在进入正题之前,先说一些相关的知识。大家都知道Java除了8种基本的数据类型就只有引用类型,也就是说类属于应用类型,我们操作类的时候实际上就是操作类的地址空间,而这一切的实现过程就是引用。下面我将通过举一个例子详细分析程序运行过程中类地址空间的变化。

    public class Computer {
    	String brand;
    	float price;
    	
    	public Computer(String brand, float price) {
    		super();
    		this.brand = brand;
    		this.price = price;
    	}
    	
    	public Computer() {
    	}
    
    	public void playgame(){
    		System.out.println(" use"+brand+"computer play game");
    	}
    }
    
    public class Student {
    	String name;
    	int age;
    	String id;
    	String grade;
    	String sex;
    	Computer computer;
    	public Student(String name, int age, String id, String grade, String sex, Computer computer) {
    		super();
    		this.name = name;
    		this.age = age;
    		this.id = id;
    		this.grade = grade;
    		this.sex = sex;
    		this.computer = computer;
    	}
    	public Student() {
    		super();
    	}
    	
    	public void study(){
    		System.out.println(name+" is studying.");
    	}
    
    }
    
    public class TestObject {
    	public static void main(String[] args) {
    		Student student=new Student();
    		Computer c=new Computer("联想",4999);
    		student.name="perry";
    		student.age=18;
    		student.computer=c;
    		student.study();
    		System.out.println(student.computer.brand);
    		c.brand="华硕";
    		System.out.println(student.computer.brand);
    	}
    
    }
    

        以上代码有三个类,其中Student类持有Computer类的对象,而TestObject类则是用来测试上面那两个类,下面我将逐句分析main方法里面代码所引起的内存空间的变化。过程图如下所示:


    首先说明一下,以下所说的过程均在字节码文件中发生,但我们无法看懂字节码文件,所以通过代码来分析。进入正题,编译器编译完成之后,代码开始执行。先从main入口方法开始,第一行代码为Student student=new Student();,在运行到Sudent时,JVM(虚拟机)便开始在方法区寻找Student类的信息,但程序中是第一次使用Student类,所以方法区中没有该类的信息,接着JVM便会使用类加载器加载该类。接着运行到student,运行到这里之后会在栈中生成一个变量,这个变量指向后面创建的对象。new Student()则是以加载的Student类为模板在堆中创建一个对象,这里用的是空构造方法,所以对象的所有属性均是默认值,这个对象便是student变量指向的对象,需要注意的是变量指向的是对象的首地址,再声明一下图中使用的地址均是随意捏造的,实际运行中只知引用地址为4个字节长,所以无需注意这个。回归正传,运行第二句Computer c=new Computer("联想",4999);,这一句也是创建对象,过程与第一句大同小异,就不重复解释了。第三句student.name="perry";,首先解释一下,在加载类的信息完之后,所有的常量。声明一下,字符串也是常量,并且在本次测试中常量只有字符串。在执行第三句的时候便是将student的name属性指向TestObject类常量池中的“perry”常量。第四局是student.age=18; 这一句因为age属性为基本类型,所以直接赋值为18,。第五句是student.computer=c; 这一句将student对象的属性computer指向之前通过第二句在堆上建立的Computer类的对象,这样变量c和student.computer指向了相同的对象即相同的地址。第六句是执行study()方法,Java在建立对象的时候并不会再次建立方法,而只是将方法指向方法区中类的方法。

        最后三句是为了做一个测试,

    System.out.println(student.computer.brand);//这一句输出结果大家都知道,结果是:联想

    c.brand="华硕";

    System.out.println(student.computer.brand);//那么这一句呢?有些同学便会觉得还是联想,事实是华硕.前文分析第五句的时候已经说过,变量c和student.computer指向了相同的对象即相同的地址,而c.brand="华硕";本质上是改变c变量指向地址所在对象的属性,即执行这一句过后堆中的Computer类的对象的属性已经改变,所以student.computer指向的便是已经改变的Computer类的对象,故最后输出的是"华硕"。

        最后再补充一下,方法区中并不会出现相同的常量,不同类中的常量池有一套共享机制,所以不同类中出现相同的常量时,后者会引用前者而不会再次生成。另外,理解对象需要牢记,操作对象实际上是操作内存地址














    展开全文
  • JAVA进程空间

    千次阅读 2016-10-05 13:05:29
    http://www.ibm.com/developerworks/cn/java/j-nativememory-linux/ http://blog.csdn.net/chaofanwei/article/details/19418753 http://www.open-open.com/lib/view/open1431570358826.html http://vani

    参考文章:
    http://www.ibm.com/developerworks/cn/java/j-nativememory-linux/
    http://blog.csdn.net/chaofanwei/article/details/19418753
    http://www.open-open.com/lib/view/open1431570358826.html
    http://vanillajava.blogspot.com/2014/12/on-heap-vs-off-heap-memory-usage.html

    目录:
    1.本机内存
    2.JAVA进程空间
    3.JAVA heap
    4.JAVA堆内内存和堆外内存

    1.本机内存

    Java 应用程序在 Java 运行时的虚拟化环境中运行,但是运行时本身是使用 C 之类的语言编写的本机程序,它也会耗用本机资源,包括本机内存。本机内存是可用于运行时进程的内存,它与 Java 应用程序使用的 java 堆内存不同。每种虚拟化资源(包括 Java 堆和 Java 线程)都必须存储在本机内存中,虚拟机在运行时使用的数据也是如此。这意味着主机的硬件和操作系统施加在本机内存上的限制会影响到 Java 应用程序的性能。

    1.1本机内存所受限制

    1.1.1硬件限制

    本机进程遇到的许多限制都是由硬件造成的,而与操作系统没有关系。每台计算机都有一个处理器和一些随机存取存储器(RAM),后者也称为物理内存。处理器将数据流解释为要执行的指令,它拥有一个或多个处理单元,用于执行整数和浮点运算以及更高级的计算。处理器具有许多寄存器 —— 常快速的内存元素,用作被执行的计算的工作存储,寄存器大小决定了一次计算可使用的最大数值。

    处理器通过内存总线连接到物理内存。物理地址(处理器用于索引物理 RAM 的地址)的大小限制了可以寻址的内存。例如,一个 16 位物理地址可以寻址 0x0000 到 0xFFFF 的内存地址,这个地址范围包括 2^16 = 65536 个惟一的内存位置。如果每个地址引用一个存储字节,那么一个 16 位物理地址将允许处理器寻址 64KB 内存。

    处理器被描述为特定数量的数据位。这通常指的是寄存器大小,但是也存在例外,比如 32 位 390 指的是物理地址大小。对于桌面和服务器平台,这个数字为 31、32 或 64;对于嵌入式设备和微处理器,这个数字可能小至 4。物理地址大小可以与寄存器带宽一样大,也可以比它大或小。如果在适当的操作系统上运行,大部分 64 位处理器可以运行 32 位程序。

    1.1.2操作系统和虚拟内存

    如果您编写无需操作系统,直接在处理器上运行的应用程序,您可以使用处理器可以寻址的所有内存(假设连接到了足够的物理 RAM)。但是要使用多任务和硬件抽象等特性,几乎所有人都会使用某种类型的操作系统来运行他们的程序。

    在 Windows 和 Linux 等多任务操作系统中,有多个程序在使用系统资源。需要为每个程序分配物理内存区域来在其中运行。可以设计这样一个操作系统:每个程序直接使用物理内存,并且可以可靠地仅使用分配给它的内存。一些嵌入式操作系统以这种方式工作,但是这在包含多个未经过集中测试的应用程序的环境中是不切实际的,因为任何程序都可能破坏其他程序或者操作系统本身的内存。

    虚拟内存 允许多个进程共享物理内存,而且不会破坏彼此的数据。在具有虚拟内存的操作系统(比如 Windows、Linux 和许多其他操作系统)中,每个程序都拥有自己的虚拟地址空间 —— 一个逻辑地址区域,其大小由该系统上的地址大小规定(所以,桌面和服务器平台的虚拟地址空间为 31、32 或 64 位)。进程的虚拟地址空间中的区域可被映射到物理内存、文件或任何其他可寻址存储。当数据未使用时,操作系统可以在物理内存与一个交换区域(Windows 上的页面文件 或者 Linux 上的交换分区)之间移动它,以实现对物理内存的最佳利用率。当一个程序尝试使用虚拟地址访问内存时,操作系统连同片上硬件会将该虚拟地址映射到物理位置,这个位置可以是物理 RAM、一个文件或页面文件/交换分区。如果一个内存区域被移动到交换空间,那么它将在被使用之前加载回物理内存中。图 1 展示了虚拟内存如何将进程地址空间区域映射到共享资源:
    这里写图片描述

    程序的每个实例以进程 的形式运行。在 Linux 和 Windows 上,进程是一个由受操作系统控制的资源(比如文件和套接字信息)、一个典型的虚拟地址空间(在某些架构上不止一个)和至少一个执行线程构成的集合。

    虚拟地址空间大小可能比处理器的物理地址大小更小。32 位 Intel x86 最初拥有的 32 位物理地址仅允许处理器寻址 4GB 存储空间。后来,添加了一种称为物理地址扩展(Physical Address Extension,PAE)的特性,将物理地址大小扩大到了 36 位,允许安装或寻址至多 64GB RAM。PAE 允许操作系统将 32 位的 4GB 虚拟地址空间映射到一个较大的物理地址范围,但是它不允许每个进程拥有 64GB 虚拟地址空间。这意味着如果您将大于 4GB 的内存放入 32 位 Intel 服务器中,您将无法将所有内存直接映射到一个单一进程中。

    地址窗口扩展(Address Windowing Extension)特性允许 Windows 进程将其 32 位地址空间的一部分作为滑动窗口映射到较大的内存区域中。Linux 使用类似的技术将内存区域映射到虚拟地址空间中。这意味着尽管您无法直接引用大于 4GB 的内存,但您仍然可以使用较大的内存区域。

    1.1.2 内核空间和用户空间

    尽管每个进程都有其自己的地址空间,但程序通常无法使用所有这些空间。地址空间被划分为用户空间 和内核空间。内核是主要的操作系统程序,包含用于连接计算机硬件、调度程序以及提供联网和虚拟内存等服务的逻辑。

    作为计算机启动序列的一部分,操作系统内核运行并初始化硬件。一旦内核配置了硬件及其自己的内部状态,第一个用户空间进程就会启动。如果用户程序需要来自操作系统的服务,它可以执行一种称为系统调用 的操作与内核程序交互,内核程序然后执行该请求。系统调用通常是读取和写入文件、联网和启动新进程等操作所必需的。

    当执行系统调用时,内核需要访问其自己的内存和调用进程的内存。因为正在执行当前线程的处理器被配置为使用地址空间映射来为当前进程映射虚拟地址,所以大部分操作系统将每个进程地址空间的一部分映射到一个通用的内核内存区域。被映射来供内核使用的地址空间部分称为内核空间,其余部分称为用户空间,可供用户应用程序使用。

    内核空间和用户空间之间的平衡关系因操作系统的不同而不同,甚至在运行于不同硬件架构之上的同一操作系统的各个实例间也有所不同。这种平衡通常是可配置的,可进行调整来为用户应用程序或内核提供更多空间。缩减内核区域可能导致一些问题,比如能够同时登录的用户数量限制或能够运行的进程数量限制。更小的用户空间意味着应用程序编程人员只能使用更少的内存空间。

    默认情况下,32 位 Windows 拥有 2GB 用户空间和 2GB 内核空间。在一些 Windows 版本上,通过向启动配置添加 /3GB 开关并使用 /LARGEADDRESSAWARE 开关重新链接应用程序,可以将这种平衡调整为 3GB 用户空间和 1GB 内核空间。在 32 位 Linux 上,默认设置为 3GB 用户空间和 1GB 内核空间。一些 Linux 分发版提供了一个 hugemem 内核,支持 4GB 用户空间。为了实现这种配置,将进行系统调用时使用的地址空间分配给内核。通过这种方式增加用户空间会减慢系统调用,因为每次进行系统调用时,操作系统必须在地址空间之间复制数据并重置进程地址-空间映射。图 2 展示了 32 位 Windows 的地址-空间布局:

    31 位 Linux 390 上还使用了一个独立的内核地址空间,其中较小的 2GB 地址空间使对单个地址空间进行划分不太合理,但是,390 架构可以同时使用多个地址空间,而且不会降低性能。

    进程空间必须包含程序需要的所有内容,包括程序本身和它使用的共享库(在 Windows 上为 DDL,在 Linux 上为 .so 文件)。共享库不仅会占据空间,使程序无法在其中存储数据,它们还会使地址空间碎片化,减少可作为连续内存块分配的内存。这对于在拥有 3GB 用户空间的 Windows x86 上运行的程序尤为明显。DLL 在构建时设置了首选的加载地址:当加载 DLL 时,它被映射到处于特定位置的地址空间,除非该位置已经被占用,在这种情况下,它会加载到别处。Windows NT 最初设计时设置了 2GB 可用用户空间,这对于要构建来加载接近 2GB 区域的系统库很有用 —— 使大部分用户区域都可供应用程序自由使用。当用户区域扩展到 3GB 时,系统共享库仍然加载接近 2GB 数据(约为用户空间的一半)。尽管总体用户空间为 3GB,但是不可能分配 3GB 大的内存块,因为共享库无法加载这么大的内存。

    在 Windows 中使用 /3GB 开关,可以将内核空间减少一半,也就是最初设计的大小。在一些情形下,可能耗尽 1GB 内核空间,使 I/O 变得缓慢,且无法正常创建新的用户会话。尽管 /3GB 开关可能对一些应用程序非常有用,但任何使用它的环境在部署之前都应该进行彻底的负载测试。参见 参考资料,获取关于 /3GB 开关及其优缺点的更多信息的链接。

    2.JAVA进程空间

    这里写图片描述

    图一:java内存结构划分

    由上图可知,java内存主要分为6部分,分别是 程序计数器,虚拟机栈,本地方法栈,堆,方法区和直接内存 ,下面将逐一详细描述。
    1.程序计数器
    线程私有,即每个线程都会有一个,线程之间互不影响,独立存储。
    代表着当前线程所执行字节码的行号指示器。

    2.虚拟机栈

    线程私有, 它的生命周期和线程相同 。
    描述的是 java方法执行的内存模型 :每个方法在执行的同时多会创建一个栈帧用于存储局部变量表、操作数栈、动态链表、方法出口等信息。
    每一个方法从调用直至完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。
    局部变量表存放了编译期可知的各种基本数据类型和对象引用,所需内存空间在编译期确定。
    -Xoss 参数设置本地方法栈大小( 对于HotSpot无效 )
    -Xss 参数设置栈容量 例: -Xss128k

    3.本地方法栈

    同虚拟机栈,只不过本地方法栈位虚拟机使用到的native方法服务。
    Sun HotSpot虚拟机把本地方法栈和虚拟机栈合二为一 。

    4.java堆

    线程共享
    主要用于分配对象实例和数组。
    -Xms 参数设置最小值
    -Xmx 参数设置最大值 例:VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

    若-Xms=-Xmx,则可避免堆自动扩展。
    -XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在出现内存溢出是dump出当前的内存堆转储快照。

    Java 堆是分配了对象的内存区域。大多数 Java SE 实现都拥有一个逻辑堆,但是一些专家级 Java 运行时拥有多个堆,比如实现 Java 实时规范(Real Time Specification for Java,RTSJ)的运行时。一个物理堆可被划分为多个逻辑扇区,具体取决于用于管理堆内存的垃圾收集(GC)算法。这些扇区通常实现为连续的本机内存块,这些内存块受 Java 内存管理器(包含垃圾收集器)控制。

    堆的大小可以在 Java 命令行使用 -Xmx 和 -Xms 选项来控制(mx 表示堆的最大大小,ms 表示初始大小)。尽管逻辑堆(经常被使用的内存区域)可以根据堆上的对象数量和在 GC 上花费的时间而增大和缩小,但使用的本机内存大小保持不变,而且由 -Xmx 值(最大堆大小)指定。大部分 GC 算法依赖于被分配为连续的内存块的堆,因此不能在堆需要扩大时分配更多本机内存。所有堆内存必须预先保留。

    保留本机内存与分配本机内存不同。当本机内存被保留时,无法使用物理内存或其他存储器作为备用内存。尽管保留地址空间块不会耗尽物理资源,但会阻止内存被用于其他用途。由保留从未使用的内存导致的泄漏与泄漏分配的内存一样严重。

    当使用的堆区域缩小时,一些垃圾收集器会回收堆的一部分(释放堆的后备存储空间),从而减少使用的物理内存。

    对于维护 Java 堆的内存管理系统,需要更多本机内存来维护它的状态。当进行垃圾收集时,必须分配数据结构来跟踪空闲存储空间和记录进度。这些数据结构的确切大小和性质因实现的不同而不同,但许多数据结构都与堆大小成正比。

    5.方法区

    线程共享
    用于存储已被虚拟机加载的类信息、常量、静态变量、即使编译后的代码等数据 。
    别名永久代(Permanent Generation)

    -XX:MaxPermSize 设置上限
    -XX:PermSize 设置最小值 例:VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
    运行时常量池(Runtime Constant Pool)是方法区的一部分 。
    Class文件中除了有类的版本、字段、方法、接口等信息外,还有一项是常量池 (Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放 。

    运行时常量池相对于Class文件常量池的一个重要特征是具备动态性:即除了Class文件中常量池的内容能被放到运行时常量池外,运行期间也可能将新的常量放入池中,比如 String类的intern()方法。

    6.直接内存

    直接内存并不是虚拟机运行时数据区的一部分。
    在NIO中,引入了一种基于通道和缓冲区的I/O方式,它可以使用native函数直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。

    -XX:MaxDirectMemorySize 设置最大值,默认与java堆最大值一样。

    例 :-XX:MaxDirectMemorySize=10M -Xmx20M

    对于32位操作系统来说,系统最大内存为4G。
    系统给每个进程的内存是有限制的,譬如32位的windows 限制为2G

    3.JAVA heap

    JAVA heap中都有什么?

    这里写图片描述

    4.JAVA堆内内存和堆外内存

    什么是堆外内存

    4.1.广义的堆外内存

    说到堆外内存,那大家肯定想到堆内内存,这也是我们大家接触最多的,我们在jvm参数里通常设置-Xmx来指定我们的堆的最大值,不过这还不是我们理解的Java堆,-Xmx的值是新生代和老生代的和的最大值,我们在jvm参数里通常还会加一个参数-XX:MaxPermSize来指定持久代的最大值,那么我们认识的Java堆的最大值其实是-Xmx和-XX:MaxPermSize的总和,在分代算法下,新生代,老生代和持久代是连续的虚拟地址,因为它们是一起分配的,那么剩下的都可以认为是堆外内存(广义的)了,这些包括了jvm本身在运行过程中分配的内存,codecache,jni里分配的内存,DirectByteBuffer分配的内存等等

    4.2.狭义的堆外内存

    而作为java开发者,我们常说的堆外内存溢出了,其实是狭义的堆外内存,这个主要是指java.nio.DirectByteBuffer在创建的时候分配内存,我们这篇文章里也主要是讲狭义的堆外内存,因为它和我们平时碰到的问题比较密切
    这里写图片描述

    4.3.使用堆外内存时,为什么要主动调用System.gc

    既然要调用System.gc,那肯定是想通过触发一次gc操作来回收堆外内存,不过我想先说的是堆外内存不会对gc造成什么影响(这里的 System.gc除外),但是堆外内存的回收其实依赖于我们的gc机制,首先我们要知道在java层面和我们在堆外分配的这块内存关联的只有与之关联的 DirectByteBuffer对象了,它记录了这块内存的基地址以及大小,那么既然和gc也有关,那就是gc能通过操作 DirectByteBuffer对象来间接操作对应的堆外内存了。DirectByteBuffer对象在创建的时候关联了一个 PhantomReference,说到PhantomReference它其实主要是用来跟踪对象何时被回收的,它不能影响gc决策,但是gc过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到 java.lang.ref.Reference.pending队列里,在gc完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块

    对于System.gc的实现,之前写了一篇文章来重点介绍, JVM源码分析之SystemGC完全解读(http://lovestblog.cn/blog/2015/05/07/system-gc/) ,它会对新生代的老生代都会进行内存回收,这样会比较彻底地回收DirectByteBuffer对象以及他们关联的堆外内存,我们dump内存发现 DirectByteBuffer对象本身其实是很小的,但是它后面可能关联了一个非常大的堆外内存,因此我们通常称之为『冰山对象』,我们做ygc的时候会将新生代里的不可达的DirectByteBuffer对象及其堆外内存回收了,但是无法对old里的DirectByteBuffer对象及其堆外内存进行回收,这也是我们通常碰到的最大的问题,如果有大量的DirectByteBuffer对象移到了old,但是又一直没有做cms gc或者full gc,而只进行ygc,那么我们的物理内存可能被慢慢耗光,但是我们还不知道发生了什么,因为heap明明剩余的内存还很多(前提是我们禁用了 System.gc)。

    4.4.为什么要使用堆外内存

    一般情况下,Java中分配的非空对象都是由Java虚拟机的垃圾收集器管理的,也称为堆内内存(on-heap memory)。虚拟机会定期对垃圾内存进行回收,在某些特定的时间点,它会进行一次彻底的回收(full gc)。彻底回收时,垃圾收集器会对所有分配的堆内内存进行完整的扫描,这意味着一个重要的事实——这样一次垃圾收集对Java应用造成的影响,跟堆的大小是成正比的。过大的堆会影响Java应用的性能。

    对于这个问题,一种解决方案就是使用堆外内存(off-heap memory)。堆外内存意味着把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机)。这样做的结果就是能保持一个较小的堆,以减少垃圾收集对应用的影响。

    堆外内存,它和内存池一样,也能缩短垃圾回收时间,但是它适用的对象和内存池完全相反。内存池往往适用于生命期较短的可变对象,而生命期中等或较长的对象,正是堆外内存要解决的。堆外内存有以下特点:
    •对于大内存有良好的伸缩性
    •对垃圾回收停顿的改善可以明显感觉到
    •在进程间可以共享,减少虚拟机间的复制

    DirectByteBuffer在创建的时候会通过Unsafe的native方法来直接使用malloc分配一块内存,这块内存是heap 之外的,那么自然也不会对gc造成什么影响(System.gc除外),因为gc耗时的操作主要是操作heap之内的对象,对这块内存的操作也是直接通过 Unsafe的native方法来操作的,相当于DirectByteBuffer仅仅是一个壳,还有我们通信过程中如果数据是在Heap里的,最终也还是会copy一份到堆外,然后再进行发送,所以为什么不直接使用堆外内存呢。对于需要频繁操作的内存,并且仅仅是临时存在一会的,都建议使用堆外内存,并且做成缓冲池,不断循环利用这块内存。

    4.5.为什么不能大面积使用堆外内存

    如果我们大面积使用堆外内存并且没有限制,那迟早会导致内存溢出,毕竟程序是跑在一台资源受限的机器上,因为这块内存的回收不是你直接能控制的,当然你可以通过别的一些途径,比如反射,直接使用Unsafe接口等,但是这些务必给你带来了一些烦恼,Java与生俱来的优势被你完全抛弃了—开发不需要关注内存的回收,由gc算法自动去实现。另外上面的gc机制与堆外内存的关系也说了,如果一直触发不了cms gc或者full gc,那么后果可能很严重。

    展开全文
  • 以IO输入为例,首先是用户空间进程向内核请求某个磁盘空间数据,然后内核将磁盘数据读取到内核空间的buffer中,然后用户空间的进程再将内核空间buffer中的数据读取到自身的buffer中,然后进程就可以访问使用这些数据...

      IO是基于缓存区来做的,所谓的输入和输出就是从缓存区中移入和移出数据。以IO输入为例,首先是用户空间进程向内核请求某个磁盘空间数据,然后内核将磁盘数据读取到内核空间的buffer中,然后用户空间的进程再将内核空间buffer中的数据读取到自身的buffer中,然后进程就可以访问使用这些数据。

        内核空间是指操作系统内核运行的空间,是为了保证操作系统内核的能够安全稳定地运行而为内核专门开辟的空间;而用户空间是指用户程序运行的空间。这里要在磁盘空间和用户空间中加一个内核空间的缓存区的原因有两个:一个是用户空间的程序不能直接去磁盘空间中读取数据,必须由经由内核空间通过DMA来获取;另一个原因是一般用户空间的内存分页与磁盘空间不会对齐,因此需要由内核空间在中间做一层处理。

        而目前的操作系统,用户空间和内核空间的区分一般采用虚拟内存来实现,因此用户空间和内存空间都是在虚拟内存中。使用虚拟内存无非是因为其两大优势:一是它可以使多个虚拟内存地址指向同一个物理内存;二是虚拟内存的空间可以大于物理内存的空间。

       对于第一点在进行IO操作时就可以将用户空间的buffer区和内核空间的buffer区指向同一个物理内存。这样用户空间的程序就不需要再去内核空间再取回数据,而是可以直接访问,节省内存空间。

        对于第二点,当用户程序访问内存地址时,一般的操作如下:首先虚拟内存系统会到物理内存去查找该虚拟地址是否存在。如果存在,如A,则直接从物理内存中读取;如果不存在,如D则会抛出一个信号。这时虚拟内存系统会去磁盘空间中找,找到后再按一定的策略,将其置入到内存中,如将C和D交换。然后由用户程序就可以使用D中的数据。这样就保证了用户程序可以读取一些大型的文件。

     

    https://juejin.im/post/5af942c6f265da0b7026050c

    https://www.cnblogs.com/hapjin/p/5736188.html

    展开全文
  • Java内存机制和内存地址

    千次阅读 2021-02-12 20:49:43
    当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。 堆内存用来存放由new创建的对象和数组。 ...

    问题一:

    String str1 = "abc";

    String str2 = "abc";

    System.out.println(str1==str2); //true

    问题二:

    String str1 =new String ("abc");

    String str2 =new String ("abc");

    System.out.println(str1==str2); // false

    问题三:

    String s1 = "ja";

    String s2 = "va";

    String s3 = "java";

    String s4 = s1 + s2;

    System.out.println(s3 == s4);//false

    System.out.println(s3.equals(s4));//true

    由于以上问题让我含糊不清,于是特地搜集了一些有关java内存分配的资料,以下是网摘:

    Java 中的堆和栈

    Java把内存划分成两种:一种是栈内存,一种是堆内存。

    在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配。

    当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。

    堆内存用来存放由new创建的对象和数组。

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

    在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。

    引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。

    具体的说:

    栈与堆都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

    Java的堆是一个运行时数据区,类的(对象从中分配空间。这些对象通过new、newarray、anewarray和multianewarray等指令建立,它们不需要程序代码来显式的释放。堆是由垃圾回收来负责的,堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

    栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。

    栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:

    int a = 3;

    int b = 3;

    编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有3这个值,如果没找到,就将3存放进来,然后将a指向3。接着处理int b = 3;在创建完b的引用变量后,因为在栈中已经有3这个值,便将b直接指向3。这样,就出现了a与b同时均指向3的情况。这时,如果再令a=4;那么编译器会重新搜索栈中是否有4值,如果没有,则将4存放进来,并令a指向4;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。要注意这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况a的修改并不会影响到b, 它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。

    String是一个特殊的包装类数据。可以用:

    String str = new String("abc");

    String str = "abc";

    两种的形式来创建,第一种是用new()来新建对象的,它会在存放于堆中。每调用一次就会创建一个新的对象。

    而第二种是先在栈中创建一个对String类的对象引用变量str,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str指向”abc”,如果已经有”abc” 则直接令str指向“abc”。

    比较类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==,下面用例子说明上面的理论。

    方式一:

    String str1 = "abc";

    String str2 = "abc";

    System.out.println(str1==str2); //true

    可以看出str1和str2是指向同一个对象的。

    方式二:

    String str1 =new String ("abc");

    String str2 =new String ("abc");

    System.out.println(str1==str2); // false

    用new的方式是生成不同的对象。每一次生成一个。

    因此用第一种方式创建多个”abc”字符串,在内存中其实只存在一个对象而已. 这种写法有利与节省内存空间. 同时它可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。

    另一方面, 要注意: 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,创建了String类的对象str。担心陷阱!对象可能并没有被创建!而可能只是指向一个先前已经创建的对象。只有通过new()方法才能保证每次都创建一个新的对象。由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

    展开全文
  • Java 8: 元空间(Metaspace)

    万次阅读 多人点赞 2017-04-13 18:06:26
    前言    很多开发者都在其系统中见过“java.lang.OutOfMemoryError: PermGen space”这一问题。这往往是由类加载器相关的内存泄漏以及新类加载器的创建导致的,通常出现于代码热部署时。相对于正式产品,该问题在...
  • Java空间和堆空间

    千次阅读 2017-06-05 17:22:29
    对这两个最初的影响是: 栈空间,存放变量名,或者说地址(根据这个地址可以到堆空间找到变量的值)。 堆空间,存放对象的值 。。。
  • JAVA 空间分配担保

    千次阅读 2017-04-16 17:29:54
    JAVA 空间分配担保
  • 解析一个Java对象占用多少内存空间

    千次阅读 2019-07-13 16:02:15
    说明: alignment, 对齐, 比如8字节的数据类型long, 在内存中的起始地址必须是8的整数倍。 padding, 补齐; 在对象所占据空间的末尾,如果有空白, 需要使用padding来补齐, 因为下一个对象的起始位置必须是4/8字节(32bit...
  • Java 变量地址

    千次阅读 2019-02-11 19:48:28
    Java 变量地址
  • 下面我们来测试一下,可以看到,依然达到了效果,说明我们模拟的链地址法也生效了, 以上就是通过开发地址法和链地址法解决hash冲突的两种方式,希望对大家理解hashMap的底层原理有所帮助…感谢观看!
  • 了解到什么是ccs区,一般都是实际执行了jstat -gc 之后,看Java堆的gc相关的几个分区的gc信息,前面的s0,s1,e区,o区,还好猜,研究过分区的,不难猜出来这个分区是啥意思,M区虽然不知道是Metaspace元空间,但是...
  • 在稍微了解Java内存分区的时候,大多数文章都是出自深入理解jvm这本书,上来就是给你分了 程序计数器,Java虚拟机栈,本地方法栈,堆,方法区,还有个直接内存,还说方法区里面有个常量池。在写这本书的时候,jdk还...
  • JAVA中引用本身占用内存空间的问题

    千次阅读 2018-05-28 09:47:18
    基本数据的类型的大小是固定的,这里就不多说了。对于非基本类型的Java对象,其大小就值得商榷。...这样在程序中完成了一个Java对象的生命,但是它所占的空间为:4byte+8byte。4byte是上面部分所说的Java栈中...
  • 【JDK】Java 8的元空间(metaspace)

    千次阅读 2017-05-28 16:01:07
    我们会介绍为什么需要移除持久代,以及它的替代者,元空间(metaspace)。这是上一篇文章内存管理之垃圾回收的续集。 Java 6中的堆结构是这样的: 持久代 持久代中包含了虚拟机中所有可通过反射获取到的数据,比如...
  • java内存空间详解

    千次阅读 2012-03-22 15:37:58
    Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详细介绍一下Java在内存分配方面的知识。一般Java在内存分配时会涉及到...
  • JST空间分析工具包是一套JAVA API,提供一系列的空间数据分析操作。 1.下载 工具包下载地址:点击打开链接 2.学习资料 工具包里的官方入门文档:doc文件夹里的JTS Developer Guide.pdf。下载地址官方API:jts-1.14\...
  • JAVA获取对象内存地址

    千次阅读 2018-08-21 17:18:53
    为了观察JVM的GC过程中各个对象所处的位置,需要观察JVM各个对象的内存地址,与年轻代与老年代进行比对,确定对象位置。 package com.memory.tools; import sun.misc.Unsafe; import java.lang.reflect.Field; ...
  • java 获取Linux 磁盘空间的使用情况

    千次阅读 2018-03-25 13:13:19
    java 获取Linux 的磁盘使用空间。这样的话,可以在系统后台随时监控linux的磁盘使用情况。其实原理很简单,就是使用java执行Linux执行,然后解析显示结果版权属于: 技术客原文地址: ...
  • java 存储空间 简单分析

    万次阅读 2016-08-20 01:37:59
      基础数据类型(Value type)直接在栈(stack)空间分配,方法的形式参数,直接在栈...引用数据类型,需要用new来创建,既在栈空间分配一个地址空间(reference),又在堆空间分配对象的类变量(object) 。方法
  • 为什么String 的变量输出不是地址? 因为所有的类继承Object类,所以单独输出一个对象的时候,他会调用Object.toString,打印出来的...关于Java中String内存地址的问题。 在创建String类型的变量的时候建议使用直接赋值
  • Java 8的元空间(metaspace)

    千次阅读 2016-12-28 17:35:06
    很多开发者都在其系统中见过“java.lang.OutOfMemoryError:PermGenspace”这一问题。这往往是由类加载器相关的内存泄漏以及新类加载器的创建导致的,通常出现于代码热部署时。相对于正式产品,该问题在开发机上出现...
  • java查看windows的磁盘空间大小信息

    千次阅读 2014-07-16 10:12:58
    java查看windows的磁盘空间大小信息 代码下载地址:http://www.zuidaima.com/share/1550463266294784.htm
  • Java源码下载 1个目标文件 摘要:Java源码,网络相关,HTTP   Java实现HTTP连接与浏览,Java源码下载,输入html文件地址或网址,显示页面和HTML源文件,一步步的实现过程请下载本实例的Java源码,代码中包括丰富的...
  • 例如,java中的数据类型就是已经实现的数据结构的实例。例如int和double,各有各的范围以及该类型允许使用的一组运算等。 抽象数据类型:是指一个数据对象、数据对象中各元素之间的结构关系和一组操作。 简单...
  • 有人说字符串常量池在java堆中,可又有人说常量池存在元空间中。 分享几篇知乎文章 关于jvm运行时数据区的模型: 1、面试官 | JVM 为什么使用元空间替换了永久代? 2、Java方法区与元空间 为了解决这个问题,下面...
  •  1、 快速排序的基本思想 :  通过一趟排序将待排序记录分割成独立的两部分...若形参为引用方式,则也只需要为其分配存储一个地址空间,用它来存储对应实参变量的地址,以便由系统自动引用实参变量。
  • 最近工作任务就是调用k8s的接口服务,然后也是解决了很多坑目前调用K8s ...调用k8s使用rest api形式一个是安全的地址https:127.0.0.1:6443,这个地址需要有认证权限的,可以是token方式或者是证书,这个也是我一开始...
  • JAVA8 JVM的变化: 元空间(Metaspace)

    万次阅读 2016-04-26 10:22:38
    本文系翻译:原文地址 你注意到了吗?JDK 8早期可访问版本已经提供下载了,java 开发人员可以使用java 8 提供的新的语言和运行特性来做一些实验。其中一个特性就是完全的移除永久代(Permanent Generation (PermGen...
  • Java内存划分和分配

    千次阅读 2018-10-18 15:03:41
    综述 在这边文章中我们将了解一下Java的内存区域是怎么划分的以及每个区域的...首先通过一张图来看一下Java虚拟机是如何划分内存空间的。 程序计数器:是一块较小内存,可以看作是当前线程所执行的字节码的行号指示...
  • Java 8: 从永久代(PermGen)到元空间(Metaspace)

    万次阅读 多人点赞 2016-08-04 21:08:59
    永久代(PermGen)和元空间(Metaspace)的今世前缘:  原文链接:原文作者:Monica Beckwith 以下为本人翻译,仅用于交流学习,版权归原作者和InfoQ所有,转载注明出处,请不要用于商业用途 在Java虚拟机(JVM)...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 432,003
精华内容 172,801
关键字:

java地址空间

java 订阅