PS :https://blog.csdn.net/column/details/java-vm.html
Java虚拟机内存模型
- 虚拟机栈:Java方法的内存模型,即每个方法的执行都会创建一个虚拟机栈帧,方法的执行过程就是栈帧的入栈出栈,每个栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息(运行期会有JIT优化,但我们理论上认为这部分所需内存编译期可知并写入了方法表);线程独立;StackOverflowError和OutOfMemoryError
- 局部变量表:存储编译期可知的基本数据类型、对象引用reference、返回值类型returnAddress(它指向了一条字节码指令的地址);局部变量表的空间在java编译成class文件时就已经确定其最大容量,运行期间不会改变;局部变量表是可以复用的,当指令执行超过某变量的作用范围则该指令占有的slot(变量槽,描述每个变量占用空间的最小单位,long、double可能需要两个slot来表示)可以被重用。局部变量表的变量并不会赋予初始值,直接使用会报错。
- 操作数栈:方法开始时为空,执行过程中将各种操作入栈出栈(即执行iadd等指令)。编译成class时确定栈大小,java虚拟机是基于操作数栈的,执行速度相对慢,但可移植性强;Android虚拟机是基于寄存器的,执行速度快,移植性差
- 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
- 方法返回地址:正常时,调用者的PC计数器的值作为返回地址(把返回值压入调用者的操作数栈中,调整PC计数器的值以指向方法调用指令后面的一条指令);异常退出则由异常处理器决定
- 本地方法栈:native方法的内存模型,与虚拟机栈雷同。
- Java堆:存放对象实例和数组;线程共享;Java垃圾收集器的主要对象,根据垃圾收集的需要还可分为老年代、新生代等。可能划分出多个线程私有的分配缓冲区。OutOfMemoryError
- 方法区:存储虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等,线程共享;这个内存区可不实现垃圾回收,若实现主要回收废弃的常量(如方法区中有“abc”常量,当前系统无String对象引用“abc”即可回收)和无用类(满足下列条件:1.堆中不存在实例2.加载该类的ClassLoader被回收3.类对应的Class对象没有引用,无法通过反射加载);OutOfMemoryError
- 运行时常量池:方法区的一部分,用于存放编译期生成的各种字面量和符号引用;
- 程序计数器:当前线程(线程独立)所执行的虚拟机字节码的行号指示器。即正在执行的虚拟机字节码指令的地址。执行Java代码时指向字节码地址,执行Native方法则为空,不会OOM。(程序计数器的作用在于多线程,单线程无需程序计数器也可以根据字节码的跳转命令顺利执行,多线程中需要挂起恢复时才需要知道之前执行到什么位置)
- 直接内存:非虚拟机规范内容,受本机总内存的大小及处理器寻址空间限制;OutOfMemoryError
PS:不用对象置null的意义
这个其实主要在于局部变量表的slot可以被复用机制产生。
{
byte[] placeholder = new byte[64 * 1024 * 1024];
}
//int a = 0; 或者 placeholder = null;
System.gc();
上述代码,刚出了placeholder作用范围马上GC过来回收垃圾,此时placeholder并不会被回收,因为这块空间没有需要被复用,GC并不会回收它。而如果把注释打开,则因为需要填充新的变量,GC就会把placeholder占用的空间回收填充新的变量了。
JVM和DVM的区别?
垃圾回收机制
虚拟机栈、本地方法栈、程序计数器中的数据随线程的生命而创建回收,随方法的入栈出栈而创建回收,无需垃圾回收机制。垃圾回收机制的重点在Java堆和方法区
对象是否已经无用判断?
1.引用计数法:每个对象有一个引用计数器来记录当前引用数,为0则可回收。缺陷:很难解决对象之间互相循环引用的问题
2.可达性分析法:GC Roots枚举根节点(一个集合)没有引用链相连的对象就是不可用对象。GC Roots的集合对象包括:①虚拟机栈(栈帧中本地变量表)中引用的对象②方法区中类静态属性引用的对象③方法区中常量引用的对象④本地方法栈中JNI引用的对象
对象的回收
GC Roots不可达则标记对象;判断是否执行finalize()方法(对象没有覆盖finalize方法或finalize已经被虚拟机调用过则不执行),若需要执行,则执行后再次判断对象是否GC Roots可达,还是不可达则回收(可在finalize中与GC Roots对象关联)
GC Roots枚举根节点集合的维护
代码执行过程中有符合条件的对象都需要加入到GC Roots集合中,且获取时需要暂停整个系统,否则分析结果无法得到保障。通常,代码执行到安全点才会停下来维护GC Roots枚举(主动式中断:GC设置一个标志,各线程执行时执行到安全点会去轮询,主动挂起,维护GC Roots枚举)
垃圾回收算法
- 标记清除算法:产生大量碎片
- 标记整理算法:耗时,有连续空间
- 复制算法:浪费内存空间
- 分代收集算法:年轻代(空间较小,垃圾收集频繁而快速)、老年代(空间较大,垃圾收集较少,一般采用标记整理法,较为耗时)
JDK1.7中垃圾回收器
- Serial (串行)收集器
- ParNew
与Serial类似,只不过新生代GC线程是多线程而已 - Parallel
与Serial类似,只不过新生代GC线程和老年代GC线程都是多线程而已
- CMS (优点:并发收集、低停顿 缺点:对CPU资源非常敏感,无法处理浮动垃圾,产生大量碎片)
①初始标记(CMS initial mark) 标记GC Roots能直接关联到的对象;阻塞,耗时短
②并发标记(CMS concurrenr mark) 标记回收对象;并发
③重新标记(CMS remark) 标记因用户运行产生的可回收对象;阻塞
④并发清除(CMS concurrent sweep) 清理;并发
- G1
①初始标记(Initial Marking)
②并发标记(Concurrent Marking)
③最终标记(Final Marking)
④筛选回收(Live Data Counting and Evacuation)
杂
- -Xmx –Xms:指定最大堆和最小堆
- -Xmn 设置新生代大小
- -XX:NewRatio 新生代(eden+2*s)和老年代(不包含永久区)的比值。例如:4,表示新生代:老年代=1:4,即新生代占整个堆的1/5
- -XX:SurvivorRatio 设置两个Survivor区和eden的比值。例如:8,表示两个Survivor:eden=2:8,即一个Survivor占年轻代的1/10
- -XX:+HeapDumpOnOutOfMemoryError OOM时导出堆到文件,根据这个文件,我们可以看到系统dump时发生了什么
- -XX:+HeapDumpPath 导出OOM的路径
- -XX:OnOutOfMemoryError 在OOM时,执行一个脚本。 可以在OOM时,发送邮件,甚至是重启程序
- -XX:PermSize -XX:MaxPermSize 设置方法区的初始空间和最大空间
- -Xss 设置栈空间的大小
成员变量存在于堆内存中,局部变量存在于栈内存中,静态变量存在于方法区中