目录
1.JVM介绍
1.1 JVM概念
2.Java运行时数据区域
2.1 线程私有的内存区域
2.2 线程共享区域
3.常量池的补充
4.垃圾回收
4.1 如何判断对象已死
4.1.1 可达性分析算法
4.1.2 引用计数算法
4.2 编程语言类型
4.3 Java的引用类型(了解)
4.4 JVM的GC内存划分
4.4.1 虚拟机栈:
4.4.2 方法区(jdk1.7)/元空间(jdk1.8)
4.4.3 堆
4.5 垃圾回收的过程
4.6 垃圾回收算法
4.6.1 标记-清除算法(Mark-Sweep算法:最基础的收集算法)
4.6.2 复制算法(Copying算法:新生代的收集算法)
4.6.3 标记-整理算法(Mark-Compact算法,老年代收集算法)
4.6.4 分代收集算法(Generational Collection算法,主流虚拟机的实现)
4.7 垃圾收集器
4.8 JVM参数
JVM:是Java虚拟机;JVM通过软件模拟Java字节码的指令集,JVM中只是主要保留了PC寄存器,其他的寄存器都进行了裁剪,JVM是一台被定制过的现实当中不存在的计算机。
线程私有介绍:
由于JVM的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现,因此在任何一个确定的时刻,一个处理器(多核处理器则指的是一个内核)都只会执行一条线程中的指令。因此为了切换线程后能恢复到正确的执行位置,每条线程都需要独立的程序计数器,各条线程之间计数器互不影响,独立存储。就把类似这类区域称为“线程私有”的内存。
(1)程序计数器:一块比较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器;
(2)Java虚拟机栈:
虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时都会创建一个栈用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应一个栈在虚拟机中入栈和出栈的过程。
之前讲的栈区域就是此处的虚拟机栈,就是虚拟机栈中的局部变量表部分;
此区域一共会产生以下两种异常:
(3)本地方法栈:本地方法栈与虚拟机栈的作用完全一样,他两的区别是本地方法栈为虚拟机使用的Native方法服务,而虚拟机栈为JVM执行的Java方法服务;
(1)Java堆:
(2)方法区/元数据区:
用于存储已被虚拟机加载的这类信息、常量、静态变量、及时编译器编译后的代码等数据;
(3)运行时常量池:
常量池可以分为Class文件常量池、运行时常量池、字符串常量池。
Java堆中存放着几乎所有的对象实例,垃圾回收在对堆进行垃圾回收前,首先要判断这些对象哪些还存活,哪些已经死去,判断死去的有如下几种算法:
通过一系列的称为“GC Roots”的对象作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为引用链,当一个对象的GC Root没有任何引用链相连(用图论表示,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。
Java/C#等语言就是使用可达性算法进行垃圾回收的。
给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1,任何时刻计数器为0的对象就是不可能再被使用的,即对象已死;
但是,主流的JVM中没有选用引用计数法来管理内存,最主要的原因就是引用计数法无法解决对象的循环引用问题;
Python/ActionScript等语言都是基于引用计数法;
编程语言选择垃圾回收的算法,一般和语言有关。动态语言、解释型语言一般是使用引用计数算法,而静态语言、编译型语言一般是使用可达性分析算法。
动态语言:
动态语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程,永远也不用给任何变量指定数据类型,该语言会在第一次赋值给变量时,在内部将数据类型记录下来。
静态语言:
静态类型语言与动态类型语言刚好相反,它的数据类型是在编译期间检查的,也就说在写程序时要声明所有变量的数据类型;
编译型语言:
编译是指在应用程序执行之前,就将程序源代码“翻译”成目标代码(机器语言)。再次运行,可直接使用上一次翻译好的机器码,不需要重新编译;
解释型语言:
常见编程语言的类型:
java的语言类型:
Java属于混合型语言(M),即有编译期编译的过程,也存在运行期解释和编译的过程;
Java的编译是通过javac将java文件编译成class字节码文件,在运行时,JVM通过解释字节码将其翻译成对应的机器指令,逐条读入,逐条解释翻译。经过解释执行,其执行速度必然会比可执行的二进制字节码程序慢很多,为了提高效率,引入了JIT技术。
JIT即时编译器的作用及时将一些热点代码直接编译成机器码,提高运行效率。
运行期通过解释器(Interpreter)和即时编译器(Just In Time Compiler,JIT编译器)配合完成解释、编译工作。
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象的引用链是否可达,判定对象是否存活都与“引用”有关。
Java将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Week Reference)、虚引用(Phantom Reference)4种,这四种强度依次逐渐减弱。
强引用:
指在程序代码之中普遍存在的,类似“Object obj=new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
软引用:
用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
弱引用:
用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。
虚引用:
也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
每一个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机中入栈和出栈的过程;
这个区域在GC中一般称为永久代(Permanent Generation);
永久代的垃圾回收主要回收两部分内容:废弃常量和无用的量。此区域进行垃圾收集的“性价比”一般比较低;
Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”(Garbage Collected Heap)
从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以Java堆中还可细分为:
(1)新生代(Yong Generation):又可以分为Eden空间,From Survivor空间、To Survivor空间;
(2)老年代(Old Generation):
(3)Full GC:
垃圾回收的时机:虚拟机设置的参数+创建对象空间不足时;
算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
它的主要不足有两个:
标记过程仍与“标记-清除”过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存;
常用的垃圾回收器搭配方式:
(1)用户体验:ParNew + CMS
(2)性能要求高/吞吐量:parallel Scavenge + Parallel Old
根据什么条件来决定吞吐量/用户体验:
前提:运行同样的代码,设置不同的参数(垃圾回收间隔时间)——垃圾回收间隔时间越长,垃圾越多,回收越长,用户线程停顿的时间越长;
收集器有:
(1)Serial收集器(新生代收集器,串行GC)
(2)ParNew收集器(新生代收集器,并行GC)
(3)Paraller Scavenge收集器(新生代收集器,并行GC)
(4)Serial Old收集器(老年代收集器,串行GC)
(5)Paraller Old收集器(老年代收集器,并行GC)
(6)CMS收集器(老年代收集器,并发GC)
1. 初始标记(CMS initial mark) 初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快, 需要“Stop The World”。
2. 并发标记(CMS concurrent mark) 并发标记阶段就是进行GC Roots Tracing的过程。(根据gc引用链查找引用对象的过程)
3. 重新标记(CMS remark) 重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生 变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,仍然需要“Stop The World”。
4. 并发清除(CMS concurrent sweep) 并发清除阶段会清除对象。
耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作(即第2、3步并发执行)
1.CPU资源敏感:用户体验优先,吞吐量越低,要求系统性能越高;
2.无法处理浮动垃圾(第四个阶段,用户线程并发执行产生的垃圾),可能出现“Concurrent Mode Failure”导致另一次Full GC,停顿时间就越长(另外的GC方案)
3.空间碎片问题:标记-清除算法导致,在survivor对象升级到老年代,创建大对象分配在老年代,可能因空间不足触发另一次full gc,停顿时间比较长;
(7)G1收集器(全区域的垃圾回收器)
jvm参数,即为配置jvm虚拟机的参数,都在调用java命令开启虚拟机的时候指定。
比较重要参数如下:
-Xmssize
设置堆的初始化大小。如设置堆空间初始化大小为6m:-Xms6291456 或 -Xms6144k 或 -Xms6m
-Xmxsize
设置堆的最大值。如设置堆空间的最大值为80m:-Xmx83886080 或 -Xmx81920k 或 -Xmx80m
-Xmnsize
设置年轻代的大小(初始化及最大值)。如设置256m大小的年轻代:-Xmn256m 或 -Xmn262144k 或-Xmn268435456
-XX:NewSize
设置年轻代的初始化大小。
-XX:MaxNewSize
设置年轻代的最大值。
-XX:PermSize=size
设置永久代的大小(jdk1.7方法区)
-XX:MaxPermSize=size
设置永久代的最大值(jdk1.7方法区)
-XX:MetaspaceSize=size
设置永久代的大小(jdk1.8元空间)
-XX:MaxMetaspaceSize=size
设置永久代的最大值(jdk1.8元空间)
-XX:SurvivorRatio=ratio
设置Eden区与Survivor区的空间比例,默认为8,即Eden=8,Survivor From=1,Survivor To=1。
-XX:MaxTenuringThreshold=threshold
对象晋升到老年代的年龄阈值
-XX:PretenureSizeThreshold=size
在老年代分配大对象的阈值,单位只能使用byte,如3m,只能写为3145728
-XX:+PrintGCDetails
打印gc详细信息