暑假小学期
1、JDK&JRE&JVM

JDK(Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。JDK是Java开发工具包,是Sun Microsystems针对Java开发员的产品。JDK中包含JRE,在JDK的安装目录下有一个名为jre的目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib和起来就称为jre。
Java Runtime Environment(JRE)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
JVM是Java Virtual Machine(Java虚拟机)的缩写,是整个java实现跨平台的最核心的部分,能够运行以Java语言写作的软件程序。所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。

2、JMM
JMM概念
JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式。 JMM可以理解为是一个规范,一个抽象概念,并不真实存在。
内存模型可以理解为在特定的操作协议下,对特定的内存或者高速缓存进行读写访问的过程抽象描述,不同架构下的物理机拥有不一样的内存模型,Java虚拟机是一个实现了跨平台的虚拟系统,因此它也有自己的内存模型,即Java内存模型(Java Memory Model, JMM)。
因此它不是对物理内存的规范,而是在虚拟机基础上进行的规范从而实现平台一致性,以达到Java程序能够“一次编写,到处运行”。
JMM工作流程
- 线程之间的共享变量存储在主内存(Main Memory)中。
- 共享变量 <--->主内存
- 每个线程都有一个私有的本地内存(Local Memory)。
- 本地内存中存储了该线程以读/写共享变量的副本。
- 共享变量的副本<--->本地内存

JMM特性
- 原子性
- 一个操作不能被打断,要么全部执行完毕,要么不执行。
- 可见性
一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量的这种修改(变化)。Java内存模型是通过将在工作内存中的变量修改后的值同步到主内存,在读取变量前从主内存刷新最新值到工作内存中,这种依赖主内存的方式来实现可见性的。
volatile的规则保证了volatile变量值修改后的新值立刻同步到主内存,每次使用volatile变量前立即从主内存中刷新,因此volatile保证了多线程之间的操作变量的可见性,而普通变量则不能保证这一点。并不能保证原子性。
除了volatile关键字能实现可见性之外,还有synchronized,Lock,final也是可以的。
使用synchronized关键字,在同步方法/同步块开始时(Monitor Enter),使用共享变量时会从主内存中刷新变量值到工作内存中,在同步方法/同步块结束时(Monitor Exit),会将工作内存中的变量值同步到主内存中去。
使用Lock接口的最常用的实现ReentrantLock(重入锁)来实现可见性:当我们在方法的开始位置执行lock.lock()方法,即使用共享变量时会从主内存中刷新变量值到工作内存中,在方法的最后finally块里执行lock.unlock()方法,即会将工作内存中的变量值同步到主内存中去。
final关键字的可见性是指:被final修饰的变量,在构造函数一旦初始化完成,并且在构造函数中并没有把“this”的引用传递出去,那么其他线程就可以看到final变量的值。
- 有序性
- 对于一个线程的代码而言,我们总是以为代码的执行是从前往后的,依次执行的。这么说不能说完全不对,在单线程程序里,确实会这样执行;但是在多线程并发时,程序的执行就有可能出现乱序。用一句话可以总结为:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。
3、垃圾回收GC

堆是Jvm管理的内存中最大的一块。程序的主要数据也都是存放在堆内存中的,也就是说程序所创建的对象基本上都在该区域进行内存分配,这一块区域被所有的线程所共享,通常出现线程安全问题的一般都是这个区域的数据出现的问题。通常我们所说的gc主要是针对java heap这块区域的。

一般将年轻代发生的GC称为Minor GC,对老年代进行GC称为Major GC。Major GC的速度一般会比Minor GC慢10倍以上。
JVM的堆区对象分配的一般规则:
- 对象优先在Eden区分配
- 大对象直接进入老年代
- 长期存活的对象将进入老年代
- 动态对象年龄判定(虚拟机并不会永远地要求对象的年龄都必须达到MaxTenuringThreshold才能晋升老年代,如果Survivor空间中相同年龄的所有对象的大小总和大于Survivor的一半,年龄大于或等于该年龄的对象就可以直接进入老年代)
- 只要老年代的连续空间大于新生代所有对象的总大小或者历次晋升的平均大小就会进行minor GC,否则会进行full GC。

copy用于survivior区,mark&sweep用于eden区,mark&compact用于老年代
年轻代采用的是标记-复制算法,将需要回收的对象标记,将不需要的对象移动到Survivor空间,然后将标记对象回收,该算法可以实现对大多数会失效的对象进行回收,对少部分不需要回收的对象进行转移,保证eden区拥有连续的内存空间,而且复制的效率高。
因为在年轻代不需要回收的对象一般是很少的,每次垃圾收集时都有大批对象死去,只有少量存活,选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。但是会有内存碎片。
老年代采用的是标记-整理算法,将需要回收的对象标记,将不需要的对象进行移动整理,使不需要回收的对象占用连续的内存空间,再清除回收对象,保证老年代拥有连续的内存空间,而且整理效率高。
因为在老年代需要回收的对象一般是很少的,其存活率较高、没有额外空间对它进行分配担保。
FULL GC触发条件
FullGC是针对整个Heap区而言的
在程序中调用了System.gc()方法。此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。不建议使用。
老年代空间不足。老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出错误。
Permanet Generation空间满了。也就是以前所说的方法区,Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出错误信息。
通过Minor GC后进入老年代的平均大小大于老年代的可用内存。如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC。由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
分配很大的对象。所谓大对象,是指需要大量连续内存空间的java对象,例如很长的数组,此种对象会直接进入老年代,而老年代虽然有很大的剩余空间,但是无法找到足够大的连续空间来分配给当前对象,此种情况就会触发JVM进行Full GC。
G1
G1(Garbage First)垃圾收集器是当今垃圾回收技术最前沿的成果之一。早在JDK7就已加入JVM的收集器大家庭中,成为HotSpot重点发展的垃圾回收技术。同优秀的CMS垃圾回收器一样,G1也是关注最小时延的垃圾回收器,也同样适合大尺寸堆内存的垃圾收集,官方也推荐使用G1来代替选择CMS。G1最大的特点是引入分区的思路,弱化了分代的概念,合理利用垃圾收集各个周期的资源,解决了其他收集器甚至CMS的众多缺陷。
以往对于Java堆区域的划分为:新生代和老年代,新生代又划分为 Eden区和 Survivor区,Survivor区又分为 from区和 to区。
但是现在,G1不再坚持固定大小以及固定数量的分代区域划分,而是把连续的Java堆空间划分为多个大小相等的独立区域(Region),每个Region都可以成为 Eden空间、Survivor空间、老年代空间。
这种思想上的转变和设计,使得G1可以面向堆内存任何部分来组成回收集来进行回收,衡量标准不再是它属于哪个分代,而是哪块内存存放的垃圾最多,回收收益最大,这就是G1收集器的 Mixed GC模式,即混合GC模式。