JVM管理的内存
- 程序计数器(PC):每条线程都有独立的PC
- JVM栈(Java栈):描述的是Java方法执行的内存模型,每个方法被执行的时候会创建一个栈帧用于存储局部变量表、操作栈、动态链接、方法出口等信息,也是线程私有的。
- 本地方法栈:与Native方法相关
- Java堆:线程共享的内存地址空间,用于存放对象实例,也是GC管理的主要区域。
- 直接内存:与Native堆相关
- 方法区:存储已被JVM加载的类信息、常量、静态变量等数据,这个区域的内存回收目标主要针对常量池的回收和类的卸载。方法区也是线程共享的内存区域,虽然被描述为堆的一个逻辑部分,但别名为非堆(Non-Heap)。
- 运行时常量池:方法区的一部分,常量池存放编辑期生成的各种字面量和符号引用,当类加载后则存放到方法区的运行时常量池。
内存分配规则
- 对象优先在Eden区分配,Minor GC(新生代GC)后对象不足以放进Survivor区则进入老年代。
- 大对象直接进入老年代
- 长期存活的对象讲进入老年代:通过年龄计数器来实现。
- Survivor同龄对象大小之和大于空间一半,大于这个年龄的对象进入老年代。
- 判断晋升老年代对象是否大于老年代所剩空间,是则进行Full GC(老年代GC)。
GC回收相关
PC、Java栈、本地方法栈随线程和方法的生命周期,因此内存分配与回收具有确定性。
Java堆和方法区只有在运行期间才能知道需要创建哪里对象,这部分内存分配和回收是动态的,GC关注这部分的内存。通过根追溯(Root Tracing)法,即一条引用链来判断对象存活,来确定是否回收对象(引用计数法不能解决对象相互引用等问题)。方法区主要回收废弃常量和无用的类。
垃圾回收算法:
- 标记清除算法(Mark-Sweep):回收后会造成内存不连续
- 复制算法(Copying):将内存分为一块较大的Eden空间和两块较小Survivor空间,将Eden和其中一个Survivor存活对象复制到另外一块Survivor空间,最后回收前面那两个空间。当Survivor空间不够时,由老年代空间进行分配担保。新生代对象中大部分都是朝生夕死的,所以常采用这种算法来回收新生代。
- 标记整理算法(Mark-Compact):将所有存活对象移向一端,直接清理其他端边界以外的对象。
- 分代收集:根据对象的存活周期将不同的内存划分为几块,一般分为新生代(Young)和老年代(Old),对不同年代采取不同回收算法。例如,对新生代采取复制算法,对老年代采取标记清除或标记整理回收算法。
一个虚拟机可以有多个不同的GC,如HotSpot就有7种作用于不同年代的GC,另外GC关注的线程数目(单线程还是多线程)也可能是不同的。
Java内存模型
JVM试图定义一种内存模型来屏蔽各种硬件和操作系统间内存访问差异,以实现在各个平台都能达到一致的并发效果。
内存间的相互操作:
- lock
- unlock
- read
- load
- use
- assign
- store
- write
当一个变量被定义为volatile之后具备两种特效:可视性和禁止重排序。可视性的意思可以粗略理解为,虽然进行了原子操作,但是并不保证另外的线程看到的是最新修改后的值,可视性则保证最新值已经刷新到了主内存。重排序的意思是:不能保证变量赋值操作的顺序与代码的执行顺序一致,如果在本线程内观察,所有的操作都是有序的,即线程内有串行的语义;如果在一个线程观察另外一个线程,由于指令重排序,操作可能是无序的。
参考:
《深入理解Java虚拟机》
http://www.th7.cn/Program/java/201409/276272.shtml