第八章:java内存管理
- 8.1 物理内存与虚拟内存
物理内存RAM(随机存储器),寄存单元为寄存器,用于存储计算单元执行指令的中间结果。
连接处理器和RAM或者处理器和寄存器的是地址总线,这个地址的宽度影响了物理地址的索引范围,总线的宽度决定了处理器一次可以从寄存器或者内存中获取多少个bit。
虚拟内存的出现使不同进程在同时运行时可以共享物理内存,提高内存利用率,而且能扩展内存的地址空间。
- 8.2 内核空间与用户空间
一个电脑4GB的地址空间被划分为内核空间和用户空间,程序只能使用用户空间的内存。
内核空间主要是指操作系统运行时所使用的用于程序调度、虚拟内存的使用或者连接硬件资源等的程序逻辑。
- 8.3 Java中那些组件需要使用内存
Java堆
Java堆是用于存储Java对象的内存区域,堆的大小在JVM启动时就一次向操作系统申请完成,通过-Xmx(最大)和-Xms(初始)两个选项来控制大小,一旦分配完成后就固定了。Java堆中内存空间的管理由JVM来控制,对象创建由Java应用程序控制,对象所占的空间释放由管理内存的垃圾收集器来完成。
线程
JVM运行实际程序的实体是线程,而线程需要内存空间来存储必要的数据。每个线程创建时JVM都会为它创建一个堆栈。
类和类的加载器
Java中类和加载类本身同样需要存储空间,这个区域叫永久带(PermGen区)。
卸载类(内存回收)条件:
1、Java堆中没有对表示该加载器的java.lang.ClassLoader对象引用
2、Java堆没有对表示类加载器的类的任何java.lang.Class对象的引用
3、在Java堆上该类加载器加载的任何类的所有对象都不在存活(被引用)
NIO
NIO使用java.nio.ByteBuffer.allocateDirect()方法分配内存,是本机内存而不是Java堆上的内存,增加了一次系统调用。直接ByteBuffer产生的数据和网络或者磁盘交互都在操作系统的内核空间中发生,不需要将数据复制到Java内存中,加快数据处理速度。
JNI
JNI技术使得本机代码(C语言)可以调用Java方法,即native memory
- 8.4 JVM内存结构
在Java虚拟机规范中将Java运行时数据划分为6种
PC寄存器数据
保存当前执行的程序的内存地址
Java栈
Java栈总是和线程关联,每当创建一个线程时,JVM就会为这个线程创建一个对应的Java栈,这个栈中又会含有多个栈帧,这些栈帧是与每个方法关联起来,每运行一个方法就创建一个栈帧,每一个栈帧会含有一些内存变量,操作栈和方法返回值等信息。
堆
堆是存放Java对象的地方,是JVM管理Java对象的核心内存区域,每个存储在堆中的Java对象都是这个对象的类的一个副本,它会复制包括继承自它父类的所有非静态属性
方法区
JVM方法区用于存储类结构信息的地方,在Java堆中的永久区内,在启动程序后一段时间就固定饿了
本地方法栈
是为JVM运行Native方法准备的空间,由于很多Native方法是C语言实现的,所以也叫C栈
运行时常量池
代表运行时每个class文件中的常量表
- 8.5 JVM内存分配策略
通常的内存分配策略
1、静态内存分配:指在程序编译时就能确定每个数据在运行时刻的存储空间需求
2、栈式内存分配(动态存储分配):由一个类似堆栈的运行栈来实现,程序对数据区的需求在运行时才能够为其分配内存。栈式内存分配按照先进后出的原则进行分配
3、堆内存分配:当程序真正运行到相应的代码时才知道空间大小
Java中内存分配详解
Java栈:其分配是和线程绑定在一起的,当我们创建一个线程时,JVM会为这个线程创建一个新的Java栈,一个线程的方法的调用和返回对应于这个Java栈的压栈和出栈。当线程激活一个Java方法时,JVM就会在线程的Java堆栈里新压入一个帧,这个帧自然成了当前帧。在此方法执行期间,这个帧用来保存参数、局部变量、中间计算过程和其他数据。栈中主要存放一些基本的数据类型和对象句柄(引用)。存取速度比堆快,仅次于寄存器,栈数据可以共享。静态分配内存,编译时确定生存期大小。
Java堆:每一个Java应用都唯一对应一个JVM实例,每一个实例唯一对应一个堆。应用程序在运行中所创建的所有类实例或数组都放在这个堆中,并由应用程序所有的线程共享。Java中分配堆内存是自动初始化,所有对象的存储空间都是在堆中分配的,但是这个对象的引用在堆栈中分配。可动态分配内存大小、运行时分配内存。
即创建一个对象时,堆中分配的内存实际建立这个对象,而在堆栈中分配的内存只是指向这个堆对象的指针(引用)而已。
- 8.6 JVM内存回收策略
静态内存分配和回收
Java中静态内存分配指在Java被编译时就已经能够确定需要的内存空间,当程序加载时系统把内存一次性分配给它。这些内存只有在程序结束时才被收回。
动态内存分配和回收
Java中对象的内存空间是动态分配的,就是在程序执行时才知道要分配的存储空间大小,只有等到对象不再使用时才会被回收。
如何检测垃圾
只要某个对象不再被其他活动对象引用,那么这个对象就可以被回收。活动对象指能够被一个根对象集合到达的对象。
根对象集合:
1、方法中局部变量区中对象的引用
2、Java操作栈中的对象引用
3、常量池中对象引用
4、本地方法中持有的对象引用
5、类的Class对象
基于分代的垃圾收集算法
- Young区 分为Eden区和两个Survivor区,其中新创建的对象都在Eden区,当Eden区满后触发minor GC 将Eden区仍然存活的对象复制到Survivor区中,另外一个Survivor区中的存活对象也复制到这个Survivor中,保证始终有一个Survivor区是空的。
- Old区存放的是Young区的Survivor满后触发minno GC后仍然存活的对象,当Eden区满后将对象存放到Survivor区中,如果Survivor中仍然存放不下这些对象,GC收集器会将这些对象直接存放到Old区。如果Survivor区中对象足够老,也直接存放到Old区。如果Old区也满了,将会触发Full GC回收整个堆内存。
- Perm区存放的主要是类的Class对象,如果一个类被频繁地加载,也可能会导致Perm区满,Perm区的垃圾回收也是Full GC触发的。
三类垃圾收集算法:
1、Serial Collector
是JVM在client模式下的默认的GC方式,通过JVM配置参数-XX:+UseSerialGC来指定GC使用该手机算法。当Eden空间不足时就触发Minor GC,触发Minor GC时首先检查之前每次Minor GC时晋升到Old区的平均对象大小是否大于Old区的剩余空间,如果大于,则直接触发Full GC,如果小于,则看HandlePromotionFailur参数的值。如果为true,仅触发Minor GC,否则再触发一次Full GC。
Java参数:java -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails
2、Parallel Collector
Parallel GC根据Minor GC 和Full GC的不同分为三种,分别是ParNewGC、ParallelGC和ParallelOldGC。
1)ParNewGC:通过-XX:+UseParNewGC参数来指定,对象分配和回收策略与Serial Collector类似,只是回收的线程是多线程的。
2)ParallelGC:Server下默认的GC方式,当在Eden区申请内存空间时,如果Eden区不够,那么看当前申请的空间是否大于等于Eden的一半,如果大于则这次申请的空间直接在Old中分配,小于则触发Minor GC。在触发GC之前首先会检查每次晋升到Old区的平均大小是否大于Old区的剩余空间,如果大于则再出发Full GC。在这次触发GC后仍然会按照这个规则重新检查一次。
JVM参数:java -Xms20M -Xmx20M -Xmn10M -XX:+UsePaallelGC -XX:+PrintGCDetails
3)ParallelOldGC
和ParallelGC的区别:前者Full GC进行的动作为清空整个Heap堆中的垃圾对象,清楚Perm区中已经被卸载的类信息,并进行压缩。而后者是清楚Heap堆中部分垃圾对象,并进行部分的空间压缩。
3、CMS Collector
触发规则:检查Old区或者Perm区的使用率,当达到一定比例时触发CMS GC,触发时会回收Old区中的内存空间。触发Full GC:1)Eden分配失败,Minor GC后分配到To Space,To
Space不够再分配到Old区,Old区不够再出发Full GC 2)当CMS GC正在进行时向Old申请内存失败则会直接触发Full GC。
4、三种GC优缺点对比
- 8.7 内存问题分析
GC日志分析
<collector>GC 表示收集器的名称
<starting occupancy1>表示Young区在GC前占用的内存。
<ending occupancy1>表示Young区在GC后占用的内存。
<pause time1>表示Young区局部收集时JVM暂停处理的时间。
<starting occupany2>表示JVM Heap在GC前占用的内存
<ending occupany2>表示JVM Heap在GC后占用的内存
<pause time2>表示GC过程中JVM暂停处理的总时间
根据日志判断是否存在内存泄漏,如果<ending occupancy1>-<starting occupany1>=<ending occupancy2>-<starting occupany2>,则表明这次GC对象100%被回收,没有对象进入Old区或者Perm区。如果是大于号则这次回收对象进入Old区或者Perm区。如果<ending occupany2>一直增长,而且Full GC很频繁,则可能内存泄漏。