第8章:JVM内存管理
Java和C++之间由一堵内存动态分配和垃圾收集器的“高墙”,墙外人想进去,墙内人想出去。因为在Java中,分配内存和回收内存都是由JVM自动完成的,很少遇到C++程序中内存泄漏问题。但是这些特点有一点点惯坏了Java程序员,当遇到OutofMemoryError,该怎么解决?所以我们需要了解Java是如何管理内存的,并能根据错误日志快速定位出错原因。
物理内存与虚拟内存
- 物理内存
- 物理内存就是RAM(随机存储器),以及寄存器(存储计算单元指令的中间结果,寄存器的大小决定了一次计算可使用的最大数值)。
- 连接处理器与RAM或者处理器与寄存器的是地址总线,地址总线的宽带影响物理地址的索引范围,决定了处理器一次可从寄存器或内存中获取多少比特。同时也决定了处理器最大可寻址的地址空间。
- 运行程序需现象操作系统申请内存地址,操作系统管理内存的申请空间是按进程来管理的,每个进程拥有一段独立的地址空间,不能相互重合,操作系统保证每个进程只能访问自己内存空间。
- 虚拟内存
- 虚拟内存可使多个进程在同时运行时共享物理内存,只是空间上的,逻辑上不能访问。
- 虚拟内存可以提高内存利用率,操作系统把不在活动的进程的物理内存的数据移到磁盘文件中,将高效的物理内存留给正在活动的程序使用。
内核空间和用户空间
计算机的姿势空间分为内核空间和用户空间:
- 程序能够使用的是用户空间的内存
- 内核空间是操作系统运行时使用的用于程序调度、虚拟内存的使用或者连接硬件资源等的程序逻辑。
Java的使用内存的组件
Java堆
- Java堆是Java虚拟机管理的内存最大的一块,用于存放对象实例,几乎所有的对象实例分配内存都是在堆上完成。
- Java堆是垃圾收集器管理的主要区域,因此又被称为“GC堆”
- Java堆中内存管理由JVM来控制;对象创建由Java应用程序控制;对象所占的内存释放由垃圾收集器完成。
线程
- JVM运行实际程序的实体就是线程,每一个线程创建,JVM就会为它创建一个堆栈
- 线程所占空间相比堆空间来说比较小,但是过多,线程堆栈使用的总内存也会很大
类和类加载器
- 类加载器是按需加载,比如加载一个jar包,不是加载jar包中所有的类,只会加载程序中明确使用的类到内存中。
- 可能会出现重复加载,导致永久代内存泄漏
NIO(重点,需要参考其他资料来系统学习)
- 在JDK1.4版本添加了新I/O(NIO)类库,引入一种基于通道和缓冲区来执行I/O的新方式;
JNI
JNI技术可使本机代码(如C语言程序)可以调用Java方法,就是通常所说的native memory;
JVM内存结构
JVM按照运行时数据的存储结构来划分没存结构的:
PC寄存器
严格来说是一个数据结构,用于保存当前正常执行的程序的内存地址。(记录中断线程的程序的内存地址)
Java栈
- 每创建一个线程时,JVM会为线程创建一个对应的Java栈,Java栈中又包含多个栈帧(一个方法就是一个栈帧),每个栈帧含有一些内部变量(方法内的变量)、操作栈和方法返回值。
- Java栈与Java线程对应的,这个数据不是共享的,所以不用担心数据一致性问题,也不存在同步锁的问题
堆
- 堆是Java对象的地方,是JVM管理Java对象的核心存储区域,是Java程序员最关心的。
- 堆是被所有Java线程所共享的,需要注意同步问题,方法和对应的属性需要保持一致性。
方法区
JVM方法区是用于存储类结构信息的地方,也属于Java堆的一部分,也就是通常说的Java堆的永久代,这个区域的被所有的线程共享。
运行时常量池
Runtime Constant Pool代表运行时每个class文件中的常量表
本地方法栈
本地方法栈是为JVM运行Native方法准备的空间
JVM内存分配策略
- 静态内存分配:
- 在程序编译时就能确定每个数据在运行时的存储空间需求,因此在编译时就给他们分配固定的内存空间。
- 此方法不允许程序代码有可变数据结构,也不允许有嵌套或者递归的结构出现。
- 栈内存分配(动态存储分配)
- 根据程序模块所需的数据区大小才能够为其分配内存
- 栈内存分配方式按照先进先出的原则进行分配
- 堆内存分配
- 当程序真正运行带相应代码时才会知道空间大小
Java中内存分配详解
- Java堆的分配和线程绑定一起,新建线程,JVM为线程创建一个新的Java栈,线程方法的调用和返回对应这个Java栈的压栈和出栈。栈中存放基本类型的变量数据(int ,shortmlong,byte,double,boolean,char)和对象句柄(引用)。
- 每个应用程序都唯一对应一个JVM实例,每个实例对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,有应用程序共享。
- 从堆和栈的功能和作用来说:堆主要是来存放对象,栈主要用来执行程序。
JVM回收策略
静态内存分配和回收
这些内存不会在程序执行时发生变化,知道程序执行结束时内存才被回收。例如Java的类和方法中的局部变量包括原生数据类型(int ,long,char)和对象的引用都是静态分配内存。
动态内存分配和回收
内存的分配是在对象创建时发生的,内存的回收也是以对象不再引用为前提的。
如何检测垃圾
垃圾收集器完成的两件事:
- 能够正确的检测出垃圾对象
- 能够释放垃圾对象占用的内存空间
只要对象不再被其他活动对象引用,这个对象可以被回收;
基于分代的垃圾收集算法(Hotspot中使用)
设计思路:将对象按照寿命长短来分组,分为年轻代和老年代,新创建的对象被分为年轻代,如果对象经过几次回收后任然存活,那么再把这个对象划分为年老代。年老代的收集额度不像年轻代那么频繁,这样减少了每次垃圾收集时所要扫描的对象的数量,从而提高了垃圾回收效率。
将堆划分为若干个子堆,每个子堆对应一个年龄代:
Hotspot的三类垃圾收集算法:
- Serial Collector
- Paralle Collector
- CMS Collector