虚拟机的存储结构分为5个部分,分别是程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区,下面就 介绍各部分的内容。
程序计数器:较小的内存空间,指定当前线程执行字节码的行数
Java虚拟机栈:线程私有的,生命周期与线程同步,每个方法执行的时候,都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出入口等信息,每个方法的调用都是栈帧入栈到出栈的过程。局部变量表用于存储方法相关的局部变量,包括基本数据、对象引用和返回地址等。栈深度大于虚拟机允许的深度,会抛出StackOverFlowError。如果当Java虚拟机允许动态扩展虚拟机栈的时候,没有内存可分配报OutOfMemoryError。
本地方法栈:和虚拟机栈一样的,只是用来执行native方法
Java堆:Java虚拟机管理内存中最大的一块,Java堆是被所有线程共享的内存区域,主要存储对象的实例
方法区:线程共享的内存区域,存储被虚拟机加载的类信息、常量、静态变量、即时编译的代码数据等。方法区在物理上是不需要连续的,可以选择固定大小或者扩展大小,还可以选择不实现垃圾收集,方法区的垃圾回收比较少,主要的回收是针对常量池和类型的卸载。运行时常量是方法区的一部分,常量池用于存放编译生成的各种字面量和符合引用。
直接内存:不是虚拟机的一部分,可以直接访问堆外的内存。
Student stu = new Student():
Student stu作为引用对象,存在Java虚拟机栈上;new Student()保存在Java堆中,堆中记录Student类型的信息包括方法、接口、对象类型等地址,这些类型的执行数据存储在方法区中。
判断对象是否存在引用:引用计数法和可达性分析算法
引用计数法:对象添加一个引用计数器,每当有一个地方引用计数器就增加1,引用失效就减1,计数器为0就不可用;缺点是无法处理对象直接相互引用的问题。
可达性分析算法:也就是常说的GC Root,每次回收时候,会从一系列的“GC root”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到“GC roots”没有任何引用链相连时,则证明此对象是不可用的。也就是当两个对象相互引用的时候,如果没有GC Roots节点可以达到两个对象,表示这两个对象都是不可达的,所以也是要被回收的。
GC触发的时机:
当内存不够的时候会触发GC,或者代码手动触发GC
GC回收采用的是分代收集的算法。主要分为年轻代、年老代、持久代。
年轻代:分成Eden和2个Survivor ,大小比例是8:1:1。当一个对象被创建的时候,内存分配首先分配在年轻代,大部分对象在创建以后都不再使用,对象很快变得不可达,就会被回收掉。,由于垃圾是被年轻代清理掉的,所以被叫做Minor GC或YoungGC。采用复制算法进行回收。
年老代:对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次YoungGc后存活了下来,默认次数是15),则被保存到年老代,年老代空间一般比年轻代大,能存放更多的对象,在年老代上发生的GC次数比年轻代少。当年老代内存不足时,将执行Major GC,也叫Full GC。采用标记-清理(标记-整理)算法进行回收
持久代:用于存放静态文件,比如Java类信息、方法等。持久代对垃圾回收没有显著影响,只有类卸载、常量池回收才会进行回收。
垃圾回收用到的算法:标记-复制算法、标记-清除算法、标记整理算法
标记-复制算法:分成Eden和2个Survivor 8:1:1,多次未被回收的会被放入年老代内存。主要解决效率问题,将空间分成两部分,当这一块的内存用完时,将存活的对象分配到另一块的空间上,然后将已使用过的内存空间一次性清理掉,这样使得每次都是对半个区域进行回收,内存分配也不需要考虑内存碎片,只要移动端指针就可以了,按照顺序分配,运行高效;缺点是造成一部分的内存空间浪费。
标记-清除算法:标记所有要回收的对象,标记完成之后统一回收。主要缺点是算法效率低,会造成不连续的空间,当存储较大的对象的时候,会造成提前进行垃圾回收。
标记-整理算法:针对年老代的特点,提出了标记-整理的算法,基本过程仍然与标记清除算法一样,后序不是直接对可回收的对象进行清除,而是让所有存活的对象向一端移动,然后直接清理边界以外的内存。
触发GC的条件:
1、当前应用程序空闲,GC运行在优先级最低的线程中,当没有程序运行时,GC才会运行
2、内存不足时,GC会被强制调用。如果一次不够之后会继续一次GC,两次都不够,会直接报OOM
3、代码调用了System.gc(),但是不一定会调用系统GC,只是提醒系统这时候可以GC
减少GC开销的措施:
1、少调用System.gc()
2、减少临时对象的调用,临时对象退出后置为null
3、对象使用完后,设置为null
4、尽量使用int等基本类型,少用Integer等引用类型
5、少使用static变量
6、字符串长度变换的使用StringBuffer,不用String
7、分散创建和删除对象,一次性太多容易OOM