JVM和GC基础

1)并行和并发的区别:

并行和并发的区别:
	并⾏是多个线程同时在运⾏,其发⽣在多核CPU 下,每个 CPU执⾏⼀个线程,多个线
程同时执⾏;
	并发是多个线程交替执⾏,多个线程之间是串⾏的;

2)对象终⽌机制 finalization

finalization
	Java 提供了对象终⽌机制finalization,来允许开发⼈员在对象被回收之前,来执⾏⾃定义的处理逻辑;
	这个机制是通过 Object 的finalize()方法来触发的,在这个finalize()⽅法内我们可以做任何我们想做的事情,⽐如关闭连接,释放资源等,也可以给对象重新建立起引用。
	
	
Java 中的对象有三种状态:
	第⼀种是可达状态,也就是此状态下,该对象不可以被回收;
	第二种是复活状态,此状态发⽣在当一个对象处于不可达的时候、触发了对象终⽌机制,此时我们的对象重写了finalize()方法,并在finalize()方法里面给该对象重新建⽴起引⽤后,会触发对象的复活状态。
	第三种是不可达状态 也就是可以回收的状态;这个状态的对象是没有重写finalize⽅法的对
象,或者是已经复活过一次、而再次变的不可达的对象。	
	PS:finalize()只会执⾏⼀次,当对象第二次不可达的时候,不会再执⾏这个⽅法。

3)JVM的主要组成部分:

1.JVM的主要由4部分组成:
               1. 类加载器(ClassLoader)
               2. 运⾏时数据区(Runtime Data Area)
               3. 执⾏引擎(Execution Engine)
               4. 本地库接⼝(Native Interface)
               
2.运行流程/职责与功能:
	1> 程序在执⾏之前,先要把java代码编译成字节码(class⽂件),
	2> jvm⾸先需要把字节码文件通过⼀定的⽅式【类加载器】,加载到JVM的内存中【运⾏时数据区】,
	3> 然后就是,class字节码⽂件并不能直接交给底层操作系统去执⾏,因此需要特定的命令解析器【执⾏引擎】,将字节码文件翻译成底层系统指令,再交由CPU去执⾏,⽽这个过程中需要调⽤其他语⾔的接⼝【本地库接⼝】,来实现翻译的过程。
	这就是这4个主要组成部分的职责与功能。

3.运⾏时数据区是由哪些模块组成的:
           @1. 程序计数器(Program Counter Register)
           @2. Java虚拟机栈(Java Virtual Machine Stacks)
           @3. 本地⽅法栈(Native Method Stack)
           @4. Java堆(Java Heap)
           @5. ⽅法区(Methed Area)

5)运行时常量池:

运行时常量池:
	运⾏时常量池是⽅法区的⼀部分,Class⽂件中除了有类的版本、字段、⽅法、接⼝等描述信息外,
	还有⼀项信息是常量池(⽤于存放编译期⽣成的各种字⾯量和符号引⽤),这部分数据在类加载后进⼊⽅法区的运⾏时常量池中。

6)直接内存

	直接内存,也叫 本地内存 或者 堆外内存;

	直接内存(Direct Memory)并不是JVM的运⾏时数据区中的⼀部分,但这部分内存也会被频繁的使⽤,⽽且可能导致内存溢出。
	在JDK1.4中新加⼊了NIO类,这是⼀种基于Channel通道与缓冲区Buffer的IO⽅式,它通过⼀个存储在Java堆中的DirectByteBuffer对象来进行对这块直接内存的引⽤操作,因此更⾼效,避免了Java堆和Native堆来回交换数据的时间。

	直接内存的分配不会受到Java堆⼤⼩的限制,但是受到本机总内存⼤⼩限制,在设置虚拟机参数的时候,不能忽略直接内存,不能使得内存区域的总和⼤于物理内存的限制,不然有可能会导致出现内存溢出错误。

------------
什么是非Java堆?
	Java如果想要和外界进行通讯,把Java 堆中的内容传输到外界,则需要把Java堆复制到非Java堆,如果使用native堆,则避免了内容在Java堆和非Java堆之间的copy.

在什么场景下使用非Java堆?
	和外界通讯的时候,一般使用非Java堆来完成。

7)JVM类加载机制:

加载 ——> 验证 ——> 准备 ——> 解析 ——> 初始化

1>加载:
	加载是指JVM根据字节流,生成一个代表这个类的java.lang.Class对象的过程,该java.lang.Class对象主要是封装这个类在⽅法区中的数据。
	
2>验证:
	验证阶段的主要⽬的是确保被加载的类满⾜Java虚拟机规范,不会造成安全问题。

3>准备:
	准备阶段,负责为类的静态成员分配内存,并设置默认初始值。
    注意:
	1、这时候进⾏内存分配的仅包括静态变量,⽽不包括实例变量,实例变量是在对象实例
化的时候才随着对象⼀块分配在Java堆中。
	2、这⾥所设置的初始值,通常情况下是数据类型对应的默认的零值(如0、null、fals等)。
	
4>解析:
	解析阶段,将符号引⽤替换为直接引⽤的过程。	
   说明:
	符号引⽤。即⼀个字符串,但是这个字符串给出了⼀些能够唯⼀性识别⼀个⽅法、⼀个变
量、⼀个类的相关信息。
	直接引⽤。可以理解为⼀个内存地址,或者⼀个偏移量。⽐如类⽅法,类变量的直接引⽤
是指向⽅法区的指针;⽽实例⽅法,实例变量的直接引⽤则是从实例的头指针开始算起到这个实例变量位置的偏移量。
	举个例⼦来说,现在调⽤⽅法hello(),这个⽅法的地址是0xaabbccdd,那么hello就是符号引⽤,0xaabbccdd就是直接引⽤。
	在解析阶段,虚拟机会把所有的类名、⽅法名、字段名这些符号引⽤替换为具体的内存地址或偏移量,也就是直接引⽤。
	
5>初始化:
	初始化,则是为标记为常量值的字段赋值的过程。

8)什么是GC Root:

GC Root
	⼀个指针/引⽤,它保存了堆⾥⾯的对象(的指向),⽽⾃⼰⼜不存储在堆中,那么它就可以是
⼀个GC ROOT,
	可以作为 GC Roots 的节点主要是一些全局性的引⽤(如常量或者静态属性引⽤
的对象)。

9)对象提升规则/年龄计数器:

对象提升规则/年龄计数器:
	虚拟机给每个对象定义了⼀个年龄计数器,对象每经过⼀次年轻代的垃圾回收然后存活
下来就会加1,当达到⼀定年龄后(默认是15)会将该对象放⼊到⽼年代中。

10)常见的垃圾标记算法:

常见的垃圾标记算法:
	引用计数法;
	可达性分析;
	
1>引用计数法:
	一个对象如果没有任何与之关联的引用,即,它们的引用计数都为0,则说明该对象不太可能会再被用到,那么这个对象就是可回收的对象。
	
2>可达性分析:
	为了解决引用计数法的循环引用问题,引入了可达性分析的方法,即,如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
	不可达对象不等于可回收对象,只有当不可达对象经过两次标记之后都被标记为可回收对象,则该对象才真正成为可回收对象。

11)常见的垃圾收集算法:

常见的垃圾收集算法:
		标记清除算法;
		复制算法;
		标记整理算法;
		分代收集算法;
		
1>标记清除算法:
	这个是最基础的垃圾回收算法,分为两个阶段,标记和清除。
	标记阶段,标记出所有需要被回收的对象;
	清除阶段,回收掉 被标记的对象们 所占用的内存空间;
	缺陷:内存碎片化问题,后续可能会发生大对象找不到可利用空间的问题,产生OOM(内存溢出)错误。
	
2>复制算法:
	按内存容量划分为等大小的两块,每次只使用其中一块,当这一块内存满了之后,将里边尚存活着的对象复制到另一块上去,把已使用的这一块内存清空掉。
	
3>标记整理算法:
	糅合了上边两个算法的特点;分为两个特点,标记和整理,
	标记阶段仍然是标记出来需要被回收的对象,
	整理阶段是将尚存活着的对象移向内存的一端,然后清除掉 除了这一端 之外的其他所有对象。
	
4>分代收集算法:
	根据对象的不同生命周期,将内存区域划分为新生代和老年代;	
	新生代采用复制算法;
	老年代采用标记清除算法;

	新生代-复制算法: 
	   新生代的特点是,每次垃圾回收时都有大量垃圾需要被回收,每次需要复制的操作比较少,因为新生代中尚存活的对象比较少,所以每次只需要复制 少量的对象 到另一半内存区域;

12)垃圾收集器的分类:

按照不同的划分⻆度,可以将 GC 分为 不同的类型; 

1> 按照 线程数 划分 可以分为 串⾏垃圾回收器 和 并⾏垃圾回收器;
	串⾏回收指的是同⼀段时间内只允许⼀件事情发⽣,当有多个 CPU 的时候也只能有⼀
个 CPU ⽤于执⾏垃圾回收操作,并且在执⾏回收的时候,程序中的⼯作线程会被暂停,回
收结束后才会恢复,这就是串⾏回收。
	和串⾏回收相反,并⾏回收可以使⽤多个 CPU 来执⾏垃圾回收,因此提升了应⽤的吞吐量。
	
2> 按照 ⼯作模式 分可以划分为 并发式回收器 和 独占式回收器;
	并发式(注意不是并⾏)回收器与应⽤程序线程交替执⾏,以尽量减少应⽤程序的停顿时间,独占式垃圾回收器⼀旦运⾏就停⽌应⽤中的其他线程,直到回收结束。

3> 按照 碎⽚处理 ⽅式分可以分为 压缩式垃圾回收器 和 ⾮压缩式垃圾回器;
	压缩式垃圾回收器会在回收完成后 对 存货对象(仍存活着的对象)进⾏压缩整理,消除回收后的碎⽚,⾮压缩式的垃圾回收器不会进⾏此过程.

4> 按照 ⼯作内存区间 划分⼜可以分为 年轻代垃圾回收器 和 老年代垃圾回收器.

13)常见的垃圾收集器:

1.Serial收集器
	Serial收集器是JAVA虚拟机中最基本、历史最悠久的收集器,Serial收集器是⼀个串⾏垃圾收集器,它进⾏垃圾收集时,必须暂停其他所有的⼯作线程,直到它收集结束,才能恢复其他的工作线程。它也有着优于其他收集器的地⽅:简单⽽⾼效(与其他收集器的单线程⽐),而且因为是适用于单个CPU的环境,所以Serial收集器没有线程切换的开销。
	
2. Seiral Old 收集器 
	 Seiral Old 收集器是用来执⾏年⽼代垃圾收集的垃圾收集器。Seiral Old 收集器同样也采⽤了串⾏回收机制,只不过回收算法为标记-整理算法,。
	 
3. ParNew 收集器
	ParNew收集器是Serial收集器的多线程版本,除了使⽤多条线程进⾏垃圾收集之外,其余⾏为都与Serial收集器⼀致。也因为它是多线程的收集器,所以会带来多线程切换的开销。
	
4.Parallel 收集器
	Parallel 收集器也是针对Eden内存回收,也是多线程垃圾收集器,采⽤的也是跟ParNew⼀样的,标记复制算法;唯⼀的区别在于Parallar收集器更注重吞吐量,通过JVM的相关配置可以调整Heap堆等相关内存区域的⼤⼩,从⽽达到调整吞吐量的效果;因此它也被称为吞吐量优先的垃圾收集器, 	
	需要注意的是:垃圾收集器中的吞吐量和低延迟这两个⽬标是相互⽭盾的,如果要选择高吞吐量,⼀定需要降低垃圾收集的频率。
	
5. CMS垃圾收集器:
	CMS是JDK1.5推出的第⼀款真正意义上的并发(Concurrent)收集器,第⼀次实现了让垃圾收集线程与⽤户线程同时⼯作; CMS是一个⽼年代收集器。采⽤"标记-清理"算法。
	CMS优点  (1)并发收集 (2)尽可能降低停顿 ,是⼀个很优秀的垃圾收集器!
	CMS缺点:由于CMS采⽤"标记-清理"算法实现,所以当垃圾收集后,会产⽣⼤量的内存碎⽚。

6. G1垃圾收集器:
	该垃圾收集器是JDK1.7版本时候推出的新的垃圾收集器,
	G1是⼀个压缩式的并⾏独占分代收集器,
	和其他的收集器⼀样,当⼀个年轻代收集执⾏时,整个年轻代会被回收,所有的应⽤线程都会被暂停,G1会启动多线程进⾏年轻代回收,
	和年轻代不⼀样.⽼年代的G1收集器和其他的收集器不⼀样,G1的⽼年代不需要整个⽼
年代被回收,⼀次只需要扫描/回收⼀⼩部分的⽼年代 Region 就可以了,此外,需要注意
的是这个⽼年代 Region 是和年轻代⼀起回收的, 
	特点: 
	   从分代上来说,G1仍然是区分年轻代和⽼年代,并且年轻代还是分为 Eden 去和 Survivor区,但是从堆结构上来说,它并不要求整个 Eden 区,年轻代或者⽼年代包含的 Region 是物理连接的,G1使⽤了全新的分区算法 。
	   在JDK1.9以后,将不再推荐使⽤CMS垃圾回收器,官⽅推荐使⽤ G1 垃圾回收器.

14)内存溢出和内存泄漏:

1)内存溢出:(Out Of Memory—OOM)

系统已经不能再分配出你所需要的空间,比如你需要100M的空间,系统只剩10M了,这就叫内存溢出。

例子:
	一个盘子用尽各种方法只能装4个果子,你装了5个,结果掉倒地上不能吃了。这就是溢出。
	比方说栈,栈满时,再做 进栈操作 必定产生空间溢出,叫上溢,
		   栈空时,再做 退栈操作 也会产生空间溢出,称为下溢。
	说白了就是我承受不了那么多,称为内存溢出。

2)内存泄漏: (Memory Leak)

​ 强引用所指向的对象不会被回收,可能导致内存泄漏,虚拟机就算抛出OOM(内存溢出)也不会去回收他指向的对象。

​ 意思就是:你创建对象的时候为该对象开辟了一段空间,当你用完时忘记释放该对象了,并且该对象还是一个强引用类型的对象,这时内存就会一直被占用着,一次没关系,但是(内存泄漏)次数多了,就会导致内存溢出。

3)内存泄漏的分类:

以发生的方式来分类,内存泄漏可以分为4类:

@1. 常发性内存泄漏。

​ 发生内存泄漏的代码会被多次执行到,每次被执行的时候都会导致一块内存泄漏。

@2. 偶发性内存泄漏。

​ 发生内存泄漏的代码只有在某些特定环境或操作下才会发生。

@3. 一次性内存泄漏。

​ 发生内存泄漏的代码只会被执行一次,比如由于算法上的缺陷,导致有且仅有一块内存发生泄漏。

@4. 隐式内存泄漏。

​ 程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄漏,因为最终程序释放了所有申请的内存。但是对于一个服务器程序,需要运行几天,几周甚至几个月,不及时释放内存也可能导致最终耗尽系统的所有内存。所以,我们称这类内存泄漏为隐式内存泄漏

	从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,作为一般的用户,根本感觉不到内存泄漏的存在。
	真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。从这个角度来说,一次性内存泄漏并没有什么危害,因为它就泄露一次,不会堆积,*而隐式内存泄漏危害性则非常大*,因为较之于常发性和偶发性内存泄漏它*更难被检测到*。

4)发生内存溢出的几种可能的情况:

对代码分析找出可能发生内存溢出的位置, 可能出现的几种情况:

1、检查对数据库查询中,是否有一次获得全部数据的查询。
		一般来说,如果一次取十万条记录到内存,就可能引起内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引起内存溢出。因此对于数据库查询尽量采用分页的方式查询。

2、检查代码中是否有死循环或递归调用。

3、检查是否有大循环重复产生新对象实体。

4、检查List、MAP等集合对象是否有使用完后,未清除的问题。
		List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

5)内存溢出的原因及解决方法:

1. 修改JVM启动参数,直接增加内存。(-Xms,-Xmx参数一定不要忘记加。)

2. 检查错误日志,查看“OutOfMemory”错误前是否有其 它异常或错误。

3. 对代码进行走查和分析,找出可能发生内存溢出的位置。

4. 使用内存查看工具动态查看内存使用情况  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值