jvm内存模型:
多cpu的系统中,每个cpu都会有自己的告诉缓存,一般分为L1、L2、L3,这样可以减少主内存的查询压力,也带来了缓存一致性的问题。
指令重排:
代码在JVM执行的时候,为了提高性能,编译器和处理器都会对代码编译后的指令进行重排序。分为3种:
a:编译器优化重排:编译器的优化前提是在保证不改变单线程语义的情况下,对重新安排语句的执行顺序。
b:指令并行重排:如果代码中某些语句之间不存在数据依赖,处理器可以改变语句对应机器指令的顺序
如:int x = 10;int y = 5;对于这种x y之间没有数据依赖关系的,机器指令就会进行重新排序。但是对于:int x = 10; int y = 5; int z = x+y;这种的,因为z和x y之间存在数据依赖(z=x+y)关系。在这种情况下,机器指令就不会把z排序在xy前面。
c:内存系统的重排序:通过之前的学习,我们知道了处理器和主内存之间还存在一二三级缓存。这些读写缓存的存在,使得程序的加载和存取操作,可能是乱序无章的。
内存屏障(memory barrier)是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操作执行的顺序; b) 影响一些数据的可见性(可能是某些指令执行后的结果)。编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。
volatile:
Synchronized:
jvm内存结构:
方法区、堆、虚拟机栈、本地栈、程序计数器
其中方法区和堆是共享的,而其他的是线程独有的。
java GC泛指java的垃圾回收机制,该机制是java与C/C++的主要区别之一,我们在日常写java代码的时候,一般都不需要编写内存回收或者垃圾清理的代码,也不需要像C/C++那样做类似delete/free的操作。
哪些内存要回收:
java内存模型中分为五大区域已经有所了解。我们知道程序计数器
、虚拟机栈
、本地方法栈
,由线程而生,随线程而灭,其中栈中的栈帧随着方法的进入顺序的执行的入栈和出栈的操作,一个栈帧需要分配多少内存取决于具体的虚拟机实现并且在编译期间即确定下来【忽略JIT编译器做的优化,基本当成编译期间可知】,当方法或线程执行完毕后,内存就随着回收,因此无需关心。
而Java堆
、方法区
则不一样。方法区存放着类加载信息,但是一个接口中多个实现类需要的内存可能不太一样,一个方法中多个分支需要的内存也可能不一样【只有在运行期间才可知道这个方法创建了哪些对象没需要多少内存】,这部分内存的分配和回收都是动态的,gc关注的也正是这部分的内存。
为了高效的回收,jvm将堆分为三个区域
1.新生代(Young Generation)NewSize和MaxNewSize分别可以控制年轻代的初始大小和最大的大小
2.老年代(Old Generation)
3.永久代(Permanent Generation)【1.8以后采用元空间,就不在堆中了】
如何判断对象是否存活:
1、引用计数算法
给对象增加一个引用计数器,引用一次+1,引用失效-1,直到为计数器为0就判断对象不会再被引用,但是难以解决循环引用的问题
2、可达性分析算法
通过一系列称为“GC ROOTS”的对象为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC ROOTS没有任何引用链相连,也就是说从GC ROOTS到这个对象不可达,则证明此对象是不可用的,需要被回收。
即使可达性算法中不可达的对象,也不是一定要马上被回收
要真正宣告对象死亡需经过两个过程。
1.可达性分析后没有发现引用链
2.查看对象是否有finalize方法,如果有重写且在方法内完成自救[比如再建立引用],还是可以抢救一下,注意这边一个类的finalize只执行一次,这就会出现一样的代码第一次自救成功第二次失败的情况。[如果类重写finalize且还没调用过,会将这个对象放到一个叫做F-Queue的序列里,这边finalize不承诺一定会执行,这么做是因为如果里面死循环的话可能会时F-Queue队列处于等待,严重会导致内存崩溃,这是我们不希望看到的。]
垃圾收集算法
1.标记/清除算法【最基础】
2.复制算法
3.标记/整理算法
jvm采用`分代收集算法`对不同区域采用不同的回收算法。
新生代:Eden空间和From Survivor、To Survivor按8:1:1,采用复制算法,优先使用Eden区,若Eden区满,则将对象复制到第二块内存区上。但是不能保证每次回收都只有不多于10%的对象存货,所以Survivor区不够的话,则会依赖老年代年存进行分配】。
GC开始时,对象只会存于Eden和From Survivor区域,To Survivor【保留空间】为空。
GC进行时,Eden区所有存活的对象都被复制到To Survivor区,而From Survivor区中,仍存活的对象会根据它们的年龄值决定去向,年龄值达到年龄阈值(默认15是因为对象头中年龄战4bit,新生代每熬过一次垃圾回收,年龄+1),则移到老年代,没有达到则复制到To Survivor。
老年代:
由于老年代存活率高,没有额外空间给他做担保,必须使用这两种算法。
垃圾收集器:
年轻代收集器
Serial、ParNew、Parallel Scavenge
老年代收集器
Serial Old、Parallel Old、CMS收集器
特殊收集器
G1收集器[新型,不在年轻、老年代范畴内]
1、cms和G1的区别
cms:CMS收集器以最小的停顿时间为目标的收集器,使用“标记-清除”算法进行的垃圾回收,容易产生内存碎片,优点是并发收集,低停顿 缺点是无法处理浮动垃圾,并发收集会造成内存碎片过多
G1:G1收集器可预测垃圾回收的停顿时间,G1收集器使用的是“标记-整理”算法,进行了空间整合,降低了内存空间碎片 ,优点控制回收垃圾的时间,减少空间碎片
你熟悉的JVM调优参数,使用过哪些调优工具?
-Xms(设置堆区的初始内存)、-Xmx(设置堆区的最大内存)、-Xmn(设置新生代的大小)、-Xss(设置线程栈的大小)。
一般用于查看服务运行时状态的主要命令包括:jstat、jmap、top、jstack。
jstat -gcutil pid
s0: 新生代survivor space0简称 就是准备复制的那块 单位为%
s1:指新生代s1已使用百分比,为0的话说明没有存活对象到这边
e:新生代eden(伊甸园)区域(%)
o:老年代(%)
ygc:新生代 次数
ygct:minor gc耗时
fgct:full gc耗时(秒)
GCT: ygct+fgct 耗时