JVM运行时数据区构成
程序运行时,根据不同功能划分了不同的数据区。
一些区是程序启动后创建的,一些是线程创建后而创建的,即线程私有的。
程序计数器
线程私有。
每个线程都有一个PC,用于记录当前线程执行指令的地址。
通常是java程序是多线程的,若是只有一个内核运行情况下,一次只能运行一个线程,线程会来回切换执行。
当线程挂起时,需要pc来保存当前运行到指令的位置,以便在它重新启动时,能够根据pc的保存的位置继续执行。
Java栈区
线程私有。
线程启动时创建,当方法调用时会创建一个Stack Frame
栈帧,用于存储本地变量以及部分结果,对于该栈的操作,只存在,方法调用时,入栈,
方法结束时出栈。
本地方法栈区
线程私有。
执行本地方法的栈区。
堆区
线程共有。
最常说的堆,用于存放对象实例,在虚拟机启动时就分配。
规范没有规定GC的配置,不过通常是这样划分的:
- 新生代 young(1/3)
- eden (8/10)
- from (1/10)
- to (1/10)
- 老年代 old(2/3)
根据分代算法,实际可用的最大内存的为: eden + (from or to) + old = 9/10, 即最大占用90%的堆区
判断对象是否存活的算法:
- 引用计数
对于每个对象,记录被引用的个数,若没有引用,则回收。缺点,无法解决引用循环的问题。 - 可达性分析方法 (根搜索方法)
通过一些GCRoot为节点对象开始,搜索通过的路径为引用链(ReferenceChain),若一个对象没有被引用链连接时,则回收。
GC Root:- Java栈中的引用对象 (形参指向;方法中本地变量指向)
- 本地方法栈的中引用的对象
- 激活的线程
- 被系统类加载器加载的Class对象
- 用于线程同步的锁对象
垃圾回收算法
标记清除 将没有引用的内存块清除。 有碎片。
标记整理 将剩余的有引用的对象挪到另一端。解决碎片问题。效率低。
复制算法 将内存块一分为二,将存活的对象对象复制到另一边。 效率高,利用率低
分代算法 根据对象不同的存活的时间使用不同的方法: 新生代的from和to区使用复制算法,老年代使用标记整理算法
新建对象放大到eden区,eden区满时,将存活的放到from,此后若是对象一直存活,则现在from和to往返,并记录其往返次数,达到一定次数时, 将该对象放到老年代。引用类型
为了方便程序员,引用类型分了几种。
强引用 常见的,new一个对象,赋予一个变量,即该变量对该对象有一个强引用。
宁愿内存满了,也会持有该引用,可能会导致OOM。软引用 GC时,若是内存不够,则回收,内存够了,则继续保留。
适用一些缓存的处理。弱引用 若是GC时,该对象仅有弱引用,则回收。
既可以通过该引用找到该变量,又不想干预其对象被回收。(既可得到了她的人,又不想对其负责的感觉)
适用例子:ThreadLocal,WeakHashMap
WeakHashMap,若是put(key,value)进去,若是除了该map对其key有引用之外,该key没有引用了,则应该回收key对象。 ThreadLocal变量作为线程私有,¡通过Thread中的ThreadLocalMap中获取,而在该ThreadLocalMap中的Entry中,其Entry1
2
3
4static class Entry extends WeakReference<ThreadLocal<?>>{
Object value;
...
}当使用完ThreadLocal变量, 在该ThreadLocalMap中,将Key设置为弱引用,防止用户将threadLocal=null,key内存泄漏。
若是有一个线程私对象A,通过Thread()->ThreadLocalMap->Key->A来获取,
在外部没有对A有引用的情况下,若是Entry是一个强引用,那么,线程不销毁,则A会一直存在。 当线程需要在线程池复用时,A就就不能被释放了。
而 Entry就是一个弱引用,若是一个线程结束时,该线程私有的变量,Thread ->(Strong Reference) ThreadLocalMap ->(SoftReference) Entry -> Key
可以看到若是不用WeakReference,则当线程结束结束后,该线程不能得到回收虚引用 比弱引用更弱,不会对引用的对象造成影响,需配合ReferenceQueue使用。(本人没搞懂~)
JVM启动时可配置参数
- -Xms 堆初始大小 -Xms256m
- -Xmx 堆最大大小
- -Xmn 堆中新生代的大小
- -Xss 每个线程栈堆大小
- …
方法区
线程共享
存储虚拟机加载类的信息,常量,静态变量。
JDK1.7之前,其大小固定,容易爆,之后可以动态分配了。
运行时常量池 Run-Time Constant Pool
这个区可以包括在方法区里面,是每个加载的Class或Interface私有的,其分配内存时机是当该Class类的实例对象创建时。(不是Class对象) 它包括了一些字面常量,field references域引用,必须在运行时解析。