Java内存管理
性能指标
java.lang.Runtime.getRuntime()
得到程序使用/空闲的内存
Runtime runtime = Runtime.getRuntime();
// Run the garbage collector
runtime.gc();
// Calculate the used memory
long memory = runtime.totalMemory() - runtime.freeMemory();
long time = System.currentTimeMillis();
//get current time
动态内存分配: new()
动态内存回收:delete()
内存管理三种基本模式
- 静态内存分配 Static allocation
在编译阶段就已经确定好了内存分配 - 动态内存分配 Dynamic allocation
在运行时动态分配内存,建立新的内存对象
基于堆和栈的内存管理都是动态分配 (heap-based / stack-based)
以下三种模式的区别在于:如何与何时在程序对象(reference)与内存对象(object)之间建立联系
Static 静态
在将程序load进内存的时候、或开始执行时,确定所有对象的分配
运行时无法改变
缺点:不支持递归,不支持动态创建的可变长的复杂数据类型
Stack-based mode 动态,基于栈
缺点:无法支持复杂数据类型
原因:某些对象延续的时间比创建它的方法所延续的时间更长(所以stack不行)
Heap-based mode(free mode) 动态,基于堆
在一块内存里分为多个小块,每块包含一个对象,或者未被占用
代码中的一个变量ref可在不同时间被指向到不同的内存对象上,无法在编译阶段确定。内存对象也可以进一步指向其他对象
堆:自由模式的内存管理,动态分配,可管理复杂的动态数据结构
Java内存管理模型
如何在堆heap上创建新对象
当某个对象不再有reference指向它,如何删除对象、释放内存
每个在JVM运行的线程都有自己的线程栈.thread stack,管理其局部数据,各栈之间彼此不可见
- The thread stack contains information about what methods the thread has called to reach the current point of execution.
所有局部的基本数据类型都在栈上创建(All local variables of primitive types)
One thread may pass a copy of a primitive variable to another thread, but it cannot share the primitive local variable itself.
多线程之间传递基本类型数据,传递的是copy,而非基本类型局部变量本身(与函数传参类似)
所有对象都在堆上创建
- This includes the object versions of the primitive types (e.g. Byte, Integer, Long, etc).
- 即使对象object是局部变量local,也是在堆上创建
堆上创建的对象可被所有线程共享引用
一个线程可访问对象时,就可以访问对象内的成员变量
如果两个线程调用同一个对象上的某个方法,它们都可以访问这个对象的成员变量,但每个线程分别保留对局部变量的拷贝。
Java Virtual Machine (JVM)内存结构
栈
- Pass arguments to methods
- Return a result from a method
- Store intermediate results while evaluating expressions
- Store local variables
堆
- Objects
- Arrays
- By using “new” operator
Method Area/Metaspace
- 用于存储被VM加载的类信息、常量、静态变量等,e.g.类名,类方法名
- HotSpot JVM中用Permanent Area (Perm)实现该区域,并作为heap的一部分
- Java 8之后改名为Metaspace (使用native memory)
Native Stacks 本地方法栈
- manage native methods (coded in C) used by JVM
Program Counter Register (PC)
- 代码行号指示器,用于指示,跳转下一条需要执行的命令
e.g.
- 局部变量是一个对象的引用,那么这个引用存在线程栈中,指向的对象存在堆中
- 一个对象的基本数据类型的成员存在堆中,如果一个成员变量是对一个对象的引用,也存在堆中。即一个对象的不是static的任何成员变量都存在堆中。static的成员变量存储在method area中
- 静态类变量存储在 Heap/Method Area
垃圾回收
可达和不可达对象
Reachable Objects vs. Unreachable Objects
内存回收的首要问题:如何把可达对象与不可达对象分离开来?
roots of a computation 根对象
- The system’s root object
- Any object attached to a local entity or formal argument of a routine currently being executed (including the local entity result for a function)
- In common language implementations roots include
- Words in the static area 静态区域的数据
- Registers 寄存器
- Words on the execution stack that point into the heap. 目前的执行栈中的数据所指向的内存对象
活对象:从root可达的对象
死对象:从root不可达,将被内存回收
GC的成本指标
- Execution time 执行时间
- Memory usage 所占用的内存/对程序所使用内存的影响
- Delay time 延迟时间
四种算法
Reference counting 引用计数
Keep a note on each object in your garage, indicating the number of live references to the object. If an object’s reference count goes to zero, throw the object out (it’s dead).
Mark-Sweep 标记-清除
Put a note on objects you need (roots).
Then recursively put a note on anything needed by a live object.
Afterwards, check all objects and throw out objects without notes
Mark-Compact 标记-整理
Put notes on objects you need.
Move anything with a note on it to the back of the garage.
Burn everything at the front of the garage (it’s all dead).
Copying 复制
Move objects you need to a new garage.
Then recursively move anything needed by an object in the new garage.
Afterwards, burn down the old garage (any objects in it are dead)!
JVM垃圾回收及其调优
在JVM中,在四种GC方法基础上做进一步的包装,以提高GC性能
Java GC将堆分为不同的区域,各区域采用不同的GC策略,以提高GC的效率
To see that the garbage collector starts working add the command line argument “-verbose:gc” to your virtual machine.
JVM内存分区
内存分区:
将堆内存分成三部分:新生代 young generation,旧生代old generation,永久区permanent generation(Java8后改名为metaspace)
- 刚创建的对象被放在YG(Eden S0 S1)
- 存活一段时间后被升入OG
- PG里存放VM和Java class metadata和Strings和类静态变量
Java GC策略
GC策略:
只有当某个区域不能再为对象分配内存时(满),才启动GC
年轻代:
- 只有一小部分对象可较长时间存活,故采用copy算法减少GC代价
- minor GC
- 初始:eden区满且S-from区为空时,一次minor GC执行,eden区存活的对象移入S-to区,
- eden区满时,一次minor GC执行,eden区和S-from中存活的对象移入S-to区,eden和S-from区清空,存活对象只存在与S-from区
- 三个区域之间的copy
- 如果多次minor GC后对象仍存活,则将其copy到old generation
- 所需时间较短
年老代:
- 这里的对象有很高的幸存度,使用Mark-Sweep或Mark-Compact算法
- 如果old generation满了意味着无法进行下一次minor GC了,则启动full GC
- Minor GC和full GC独立进行,减小代价
永生代:
当perm generation满了之后,无法存储更多的元数据,也启动full GC
GC调优
尽可能减少GC时间,一般不超过程序执行时间的5%
一旦初始分配给程序的内存满了,就抛出内存溢出异常
"Exception in thread java.lang.OutOfMemoryError:Java heap space".
- Specifying VM heap size 确定堆的大小
- Choosing a garbage collection scheme 选择GC模式
- Using verbose garbage collection to determine heap size 使用verbose GC参数查看内存详细信息以确定堆的大小
- Automatically logging low memory conditions 自动记录内存将要不足的情况
- Manually requesting garbage collection 手工请求GC
- Requesting thread stacks