1.JVM存储结构:
JVM内存结构可以大致分为线程私有区域和共享区域,线程私有区域由虚拟机栈、本地方法栈、程序计数器组成,而共享区由堆、元数据空间(方法区)组成。
2.虚拟机/本地方法栈:
StackOverflowException异常出现原因:JVM会为每个方法生成栈帧然后将栈帧压入虚拟机栈中,例如设置JVM参数-Xss为1m,如果在方法里创建一个128kb的数组,那这个方法在同一个线程中只能递归四次,待递归到第五次就会报栈溢出异常,因为每次递归都需要128kb的空间,递归到第五次明显超出了。
3.程序计数器:
程序计数器是一个记录当前线程所执行的字节码的行号指示器。JVM的多线程是通过CPU时间片轮转(即线程轮换流并分配处理器执行时间)算法来实现的。也就是说,某个线程在执行过程中可能会因为时间片耗尽而被挂起,而另一个线程获取到时间片开始执行。简单来说,程序计数器的主要功能是记录当前线程所执行的字节码的行号指示器。
4.方法区(元数据区)
方法区存储了类的元数据信息、静态变量、常量等数据。
5.堆(heap)
使用new关键字创建的对象都会进入堆中,堆也是GC重点照顾的区域,堆会被划分为:新生代、老年代,而新生代还会被划分为Eden区和Survivor区。
新生代的Eden区和Survivor区,是根据JVM回收算法来的,只是现在大部分都是使用的分代回收算法,所以在介绍堆将新生代归纳为Eden区和Survivor区。
6.对象什么时候被回收
JVM判断对象回收的方式有两种:
① 引用计数:JVM为每个对象维护一个引用计数,假设A对象引用计数为零说明没有任务对象引用A对象,那A对象就可以被回收,但是引用计数无法解决循环引用的问题
② GC Roots:通过一系列的名为GC Roots的对象作为起始点,从这些节点向下搜索,搜索过程成为引用链。当一个对象到GC Roots没有任何引用链时,则证明对象是不可用的
在Java中,可以作为GC Roots的对象包括以下几种:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法中JNI(Native方法)的引用的对象
7.常见的垃圾器回收算法
① 标记清除: 该算法分为两步,标记、清除两个阶段,标记阶段从根集合(GCRoots)开始扫描,没到一个对象就会标记该对象为存活状态,清除阶段在扫描完成后将没有标记的对象清除。
缺点: 会产生内存碎片。
② 标记整理:标记整理不会发生内存碎片的问题,从根集合(GC Roots)开始扫描进行标记然后清除无用的对象,清除完成后它会整理内存
缺点:虽然不会产生内存碎片,但是每次都要移动对象,成本较高。
③ 复制算法:将JVM堆分成两等份,如果堆设置为1GB,那是用复制算法就会被划分为两块区域,各为512MB,总是使用其中的一块给对象分配内存,分配满了后,GC就会标记,然后存活的对象会被移动到另一块空白的区域,然后清除所有没有存活的对象,重复这样的处理,始终有一块空白区域没有被合理利用
缺点:造成空间浪费,堆内存利用率只有50%
8. 什么时候对象进入老年代
新创建的对象一开始都会停留在新生代,但随着JVM的运行,存活时间长的对象会慢慢的移动到老年代中
根据对象年龄:
JVM会给对象增加一个年龄计数器,对象每熬过一个GC,年龄+1,带对象到达设置的阈值(默认是15岁)就会被移动到老年代,可通过-XX:MaxtrnuringThreshold调整这个阈值
动态对象判断:
根据对象年龄有另一个策略也会让对象进入老年代,不用等15词GC之后进入老年代,规则是,假设存放对象的Survivior,一批对象的总大小大于这块Survivor内存的50%,那么大于这批对象年龄的对象,就直接进入老年代
大对象直接进入老年代:
如果设置了-XX:PretenureSizeThreshold这个参数,那么如果你要创建的对象大于这个参数值,如分配一个超大数组,则直接把这个对象放入老年代,不会经过新生代,可以避免大对象GC时消耗太多时间
9.如何判断对象已死:
① 引用计数器法:
给每一个对象添加一个引用计数器,每当一个地方引用到它时,计数器+1;否则,-1.但是主流的虚拟机没有选择这种算法来管理内存,原因是当某些对象互相引用时,无法判断这些对象是否已死
②可达性分析算法:
GC Roots,作为垃圾收集的起点,是虚拟机栈中本地变量表中引用的对象、方法区中静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI(Native方法)引用的对象。当一个对象到GC Roots没有任何引用链相连(GC Roots到这个对象不可及)时,说明该对象不可用,是死对象
③ 方法区回收:
上面说的都是对堆内存中对象的判断,方法区中主要回收的是废弃的常量和无用的类。判断常量是否被废弃可以判断是否有地方引用这个变量,如果没有引用则为废弃的常量。判断类是否废弃需要用时满足如下条件:该类所有的实例已经被回收(堆中不存在任何该类的实例)。加载该类的ClassLoader已经被回收。该类对应的java.lang.Class对象在任何地方没有被引用(无法通过反射访问该类的方法)
10. 什么是空间分配担保策略
JVM在Minor GC之前,会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果大于,则此次Minor GC是安全的;如果小于,虚拟机会查看handlePromotionFailure设置的值是否允许担保失败。如果值为true,会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象平均大小,如果大于则尝试一次Minor GC,但这次Minor GC依然是有风险的。如果值为false,则改为进行一次Full GC