Java内存模型
现在讲究的是高并发,分布式,高可用,充分压榨机器的性能,机器性能最大化,良好的JVM调优就可以增加电脑的负载
Java程序执行流程
Java运行时数据区域
-
方法区:最重要的内存区域,多线程共享,保存了类的信息(名称、成员、接口、父类),反射机制是其重要的组成部分,动态进行类操作的实现
-
堆内存(Heap):保存对象的真实信息,该内存牵扯到释放问题(GC)
-
栈内存(Stack) :线程的私有空间,在每一次进行方法调用的时候都会存在有栈帧,采用先进后出的设计原则
-
本地变量表:局部参数或形参,允许保存有32位的插槽(Solt),如果超过了32位的产毒就需要开辟两个连续性的插槽(long,double)Valatile关键字问题
-
操作数栈:执行所有的方法计算操作
-
常量池引用:String类实列,...
-
返回地址:方法执行完毕后的恢复执行的点
-
-
程序计数器:执行指定的一个顺序编码,该区域的所占比率几乎可以忽略
-
本地方法栈:与栈内存的功能类似,区别在于是为本地方法服务的
Java内存模型
-
合理的内存模型可以使GC的性能更加强大,不必太大的浪费服务器的性能,从而减少阻塞所带来的程序的性能影响
-
Java中数据保存的内存位置:堆内存(调优、原理)
-
最需要强调的就是jdk1.8之后所带来的内存结果改变以及GC策略提升
jdk1.8:
当内存不足的时候就需要进行伸缩区的控制,当内存充足的时候就可以考虑将伸缩区所占用的内存释放掉,如果伸缩区一直占有这个区域,会造成其他区域的内存相对少,肯定会在成额外的计算性能的影响,导致程序的整体性能下降,如果我们确定我们当前是高并发的访问,并且我们的程序是独立在一台服务器上,那么这台服务器的资源就应该全部给我使用,而不是给伸缩区留一大块动态区域
伸缩区的看见 = MaxMemory - TotalMemory ( 特别大的一块内存空间被闲置了起来 )
jdk1.8以前:
GC执行分析
-
对象实列化需要依靠关键字new来完成,所有新对象都会在伊甸园区开辟,如果伊甸园区的内存空间不足,就会发生MiorGC
-
有些对象执行了后翅MinorGC后依然存在,伊甸园区的内存空间是有限的,那么这些对象将进入到存活取(存活区有两个,一个负责保存存活对象,一个负责晋升,对象只占用一个空间,永远都有一个空的)
-
如果经历了若干次MinorGC回收处理之后发现看见依然不够使用,那么则进行老年代的GC回收,执行一个MajorGC(FUll GC 性能很差),如果可以回收空间,则继续进行MINorGC
-
如果MajorGC失败,则内存已经被占用完,抛出OOM异常
-
如果新创建的对象的空间占用过大,会直接保存在老年代之中
-
【备注]】: jdk1.8默认会根据系统的不同二选择不同的GC回收策略
jdk1.9-jdk11 使用的默认GC就是G1
垃圾回收
-
-
引用计数:早期的JVM使用的方法,会储存对象的所有引用数,会控制这个引用数的多少,在引用数为0的时候,则 视为可以进行垃圾回收
-
对象引用遍历:现在大部分JVM采用的方法,从一组对象开始,沿着该对象涉及到的对象形成一个树 ( 自己理解 ),通过递归的方式,确定可达对象,不可达对象就会被视为不再需要,将其进行标记作为垃圾收集,释放对应的内存,但是释放的内存都是离散的,不是连续的,不足于装载新的对象,很多GC会有优化操作,会重新组织内存中的对象,并进行压缩,形成连续的内存空间供使用。
-
引用对象的强度:包装了我们实际需要的对象,使我们可以直接维护对引用对象的直接引用,可以理解为:给某个对象的引用进行了一层增强,使其拥有了其他的状态,我们可以根据他的状态进行辨别,对象在没有任何引用指向的时候才会被当作垃圾回收,如果我们想对有引用指向的对象做垃圾回收处理,这个时候就涉及到了引用对象的概念,引用对象的强度将决定垃圾回收器的行为,注意:我们普通使用的引用都是强度最大的引用,
-
【强可达、软可达、弱可达、虚可达、不可达】,
-
对象的可达性状态会触发垃圾回收期作出相应的行为:
-
软可达对象可能会任凭垃圾回收器去回收;
-
弱可达对象将会被垃圾回收器回收;
-
-
-
为了避免程序的运行线程和GC的回收线程发生并发影响,JVM采用安全点机制来实现STW(Stop The World),也就是当垃圾收集线程发起了STW请求后,工作线程开始进行安全点检查,只有大概所有线程都进入安全点以后,垃圾回收线程才可以开始工作,在垃圾收集线程工作的过程中,工作线程没执行一行代码都会进行安全点检测,如果这行代码安全就继续执行,如果这行代码不安全,就将全部线程挂起,这样保证在垃圾回收线程的工作过程中,工作线程也可以继续执行
-
安全点:列如阻塞线程肯定是安全点,运行的jni线程如果不放问java对象也是安全的,如果线程在编译生成机器码那他也是安全的,Java虚拟机在有垃圾回收县城执行期间,没自信一个字节码都会进行安全检测
-
-
基础垃圾收集算法:清除算法会造成垃圾碎片,清除后整理压缩又浪费CPU且耗时,复制算法浪费内存但是很快
-
基础假设:
-
大部分的Java对象都生命周期都很短暂,只有很少部分以对象能存活很久,新建的对象放到新生代,当经过多次垃圾回收且还存在的对象,就将其移动到老年代,针对不同的区域采用不同的算法,因我新生代的对象存活周期很短,经常都需要垃圾回收,所以一般采用速度很快的复制算法,所以新生代分分成两块,一块eden区,两块大小相同的servivor区(用作于复制)
-
-
对象初期都在eden区分配空间,由于堆的内存空间是共享的,所以分配内存需要加锁且同步,不然会发生两个对象引用指向用一块内存的情况发生,为了避免频繁的加锁,一个线程可以申请一块连续的内存空间,后续内存的分配就在这里进行,这个访问被称为Tlab,Tlab里面维护了两个指针,一个是当前空余内存的起始位置,另一个Tail指向尾巴申请的内存结束位置,分配内存的时候就只需要进行指针加法并判断是否大于tail,如果超过则需要重新申请Tail,
-
当eden区满了就会进行一次minorGC,将ende区存活的对象和form区的对象移动到to区,然后交换form和to的指针
内存回收算法
-
年轻代回收算法:
-
"复制"清理算法,将保留的对象复制到存活区之中,存活区的内容会保存到老年代资中
-
伊甸园总是会有大量的新对象产生,所有HotSpot虚拟机使用了BTP(单一CPU的时候所有的兑现一次保存),TLAB(拆分 为不同的块,根据CPU的核心个数拆分),技术形式处理
-
-
老年代回收算法
-
"标记—清除"算法,想进行对象的第一次标记,在这段时间以内会暂停程序的执行(如果标记的时间厂或者对象的内容过多,这个暂停的时间就会很长,就会产生串行标记,并行标记等问题)
-
"标记—压缩"算法,基于"标记—清除"算法,将零散的内存空间进行整理压缩重新集合再分配
-
【年轻代】串行GC / 并行回收GC / 并行GC
【老年代】 串行GC / 并行GC / CMS
STW: [Stop—The—World]
-
暂时挂起所有的程序的执行线程,进行无用的对象标记
-
没有任何意向合适的GC回收操作,从JDK1.8开始提供有G1收集器,在JDK11之后提供额ZGC
G1算法(未来主流)
支持大内存(4G-64G),支持有多CPU,减少STW停顿时间,可以保证并发状态下程序的执行