通俗来说,jvm是用来创建对象的,jvm占有一定的内存,每去创建对象都会去占有一定的内存,而内存空间是一定的,当jvm的使用率达到100%时,jvm就无法继续正常运行,所以,为了让jvm能够被更多的对象循环的使用,出现了垃圾回收机制,回收那些没用的对象。
线程:隶属于进程,属于运行中的程序的一个单独的路线,或者单独的逻辑
理论上来说,cpu会根据每个进程中的线程数量分配一定比例的时间片
所以,从cpu的角度来看,程序时顺序执行的,但是以人的角度看,程序时并行执行的,因为事件太短了,切换太快。
每个线程独享一块内存空间,但是,线程与线程之间又共享进程的内存空间。
jvm其实就是一个程序,占有一定的内存,需要对jvm的内存进行划分
jvm就是按照线程是否共享进行区域的划分,划分为两大类
线程独享区
有多少个线程就有多少个线程独享区
每一个线程只能访问独享区域的数据,线程之间不能共享
线程独享区随着线程的创建而创建,随着线程的回收而回收
线程共享区
属于线程都可以访问的区域
所以本区域中会放大量的对象,所有线程都可以创建自己的对象
当线程被销毁时,共享区域的数据就会被垃圾回收掉
但是,并不会马上回收,需要等待达到垃圾回收的阈值之后才会进行回收
程序计数器:
程序计数器只为当前线程所服务
例如:
cpu将时间片分给线程,然后线程开始执行代码(指令)
Thread1 —>50 线程一在一个时间片内执行到了第50行
Thread2 —>35 线程二得到时间片并执行到了第35行
Thread1 —>? 线程一又得到时间片,那么他从哪里开始执行?,
程序计数器会记录当前线程要执行指令的内存地址。
他只占用一下部分的内存区域,只记录一个地址,所以我们认为程序计数器是不会出现内存溢出问题的分区。
虚拟机栈:
存放当前线程中所声名的变量
对于基本数据类型,由于他们已经确定了所占内存的大小,所以直接存储。
对于引类型数据,由于他们不确定所占内存大小(比如:类,接口,数组),所以,会将值得引用(地址)存放在虚拟机栈中,对象存放在堆内存中。
栈帧的概念:
比如,如果我们线程中又多个方法得调用,比如a->b->c :a调用b,b在调用c,那么对于变量是怎么存储的?
开始发挥虚拟机栈的先进后出的特性,回对栈帧进行压栈和出栈的操作。
先将a方法的栈帧压栈,再将b方法的栈帧压栈,最后是c,然后调用的时候,c先出栈,然后b,最后a,然后从栈帧中拿对应的数据
这就是多个方法中的变量,通过栈帧的方式,存储在虚拟机栈中。
栈帧的数据结构:
分为5个层
局部变量:存放当前方法的局部变量,基本类型存放值,引用类型存放地址
操作数栈:对方法中的变量提供计算的区域
常量数据的引用:常量数据会存放常量池中
方法的返回地址:存放方法的返回值 ----> 返回值分为两种,1.正确的执行并返回,2.方法发生错误,抛出异常
描述信息:
本地方法栈:
他的作用和虚拟机栈很相似
java中有些代码依赖于其他非java语言的(c)
维护非java语句执行过程中产生的数据
堆内存:
jvm启动的时候,就被创建,其空间大小也就确定。
所有的对象实例以及数组都运行分配在堆上。
在方法结束后,堆中的对象不会被马上移除,仅仅在垃圾回收的时候才会被移除。
堆,是GC(垃圾回收)的重点区域。
堆空间的细分:(垃圾回收分代收集理论)
新生区: 占内存的1/3
新生区分为Eden区,to区,form区,当Eden区满了的时候就会触发YGC来回收机制,有用的继续会放入to区或者form区,当达到一个阙值的时候会去到老年区。
老年区: 占内存的2/3
新生区与老年区的默认比1:2 通过-XX:NewRatio=1 可以设置两个的比例
(jdk7叫永久区)元空间:方法区用,不归堆空间管
可以通过 -Xms 来进行设置初始堆空间的内存大小,-Xmx ,来设置堆空间的最大内存大小,超过这个空间大小就会发生OOM(内存溢出)
如果想查看堆内存具体的分配情况, -XX:+PrintGCDetails
eg: -Xms600m -Xmx600m -XX:+PrintGCDetails 这就是设置堆内存的大小,以及打印出分配详情
-Xmn: 设置新生代的大小
垃圾回收的几个Minor GC、Major GC、Full GC:
Minor GC:新生区的垃圾回收
Major GC:老年区的垃圾回收
Full GC:收起整个java堆和方法区的垃圾回收,老年代和方法区空间不足时,就进行full gc
Mixed GC:G1 GC,收集整个新生代以及部分老年代的垃圾回收
方法区:
存放的是 类信息(类名,全类名,接口,父类信息),常量池(字符串常量池),静态变量,方法的信息
方法区在JVM启动的时候就被创建,方法区的大小决定了系统可以保存多少个类
方法区大小的设置:
JDK7以前:-XX:PermSize 设置方法区的大小(默认大小20.75M) -XX:MaxPermSize 设置方法区的最大可分配空间(默认大小是64M)
JDK8以后:-XX:MetaspaceSize 设置方法区的大小(默认大小21M) -XX:MaxMetaspaceSize设置方法区的最大可分配空间(默认没有限制)
当超出这个MetaspaceSize 的大小后,就会触发Full GC
方法区的垃圾回收主要回收的是常量池中废弃的常量和不再使用的类型
第一步:loading (加载)
第二步:链接
第三步:初始化
**什么是垃圾:**垃圾是指运行程序中没有任何指针指向的对象,这个对象就需要回收
垃圾回收的过程:
**垃圾标记阶段:**通过判断该对象是否还有引用是否指向他来判断他是否死亡,死亡的对象就可以被回收了
算法:引用计数算法和可达性分析算法 判断对象是否存活
引用计数器算法:对每个对象实例保存一个整型的引用计数器,用于记录对象被引用的情况,被引用+1,引用失效则-1,当计数器为0时,则该对象不再被使用,可以回收。
优点:效率高,回收没有延迟性
缺点:计数器存储增加存储开销,加法减法增加时间开销,致命严重的问题就是,使用计数器无法处理循环引用的情况
可达性分析算法:以根对象集合(GC Roots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达,解决了对象被循环引用的问题
GC Root包括以下几类元素:虚拟机栈中的引用对象,本地方法栈引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,被锁synchronized持有的对象。
finalize()方法:当垃圾回收器发现没有引用指向一个对象,即,垃圾回收前,总会先调用这个对象的finalize()方法。
这个方法呢提供了对象被销毁之前的自定义处理逻辑。用于对象被回收时释放资源。
由于此方法的存在,对象有三种状态:可触及的,可复活的,不可触及的
**垃圾清除阶段:**回收没有被标记的可达对象,垃圾回收。
标记清除算法:首先经过可达性分析算法从个对象集合遍历,把可达的对象进行标记,清除的时候在经过一次遍历,把没有标记的对象进行回收。 缺点是会造成碎片,空间不连续。
复制算法:原理是将内存区域分为两个部分,在经过可达性分析后,将标记的对象复制到另一个空的内存区域,把原来的那个区域进行垃圾回收,释放内存。比如,新生代的垃圾回收机制就用到了复制算法,新生区有两个to区和form区,专门接受Eden区垃圾回收后被标记的对象。
优点是解决了空间不连续问题,但是内存空间使用较大
标记压缩(整理)算法:先经过可达性分析算法,对对象进行标记,然后将所有的存活对象压缩或者整理到内存的一端,按顺序排放,之后清理边界外所有的空间。它适用于老年代的垃圾回收
System.gc()的理解:
调用这个方法会触发Full GC垃圾回收机制,同时对老年代和新生代进行垃圾回收。释放被丢弃对象所占用的内存。
这个方法只是声明,掉不掉用垃圾回收,还得看后台自动执行。无法保证对垃圾收集器的调用。
CMS垃圾回收器和G1垃圾回收器以及Parallel垃圾回收器:
**Parallel Scavenge回收器(吞吐量优先):**采用复制算法,并行回收
**Parallel Old回收器:**采用标记压缩算法,并行回收
**CMS垃圾回收器:**并发收集器,实现了垃圾收集线程与用户线程的同时工作,低延迟,缩短垃圾收集时的用户线程的停顿时间(减少了STW的时间)
CMS垃圾回收器采用的是 标记清除算法,只能用来回收老年代的垃圾回收
原理:初始标记,并发标记,重新标记,并发清理,重置线程。如下图所示:
初始标记:标记出根对象集合(GC Roots)能直接关联到的对象
并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长,但是不需要停顿用户线程,可以与垃圾收集线程一起并发的运行。
重新标记:修正并发标记期间,因用户线程运作而导致标记产生变动的那一部分对象的标记记录
并发清除:清理删除掉标记阶段判断的已死亡的对象,释放内存空间。
缺点:会产生内存碎片,导致吞吐量降低,程序变慢,无法处理浮动垃圾(并发标记的时候,新产生的垃圾) 优点:低延迟
**G1垃圾回收器:**是一个并行回收器,既保证了低延迟,也保证了高的吞吐量
优势:兼具并行与并发,分代收集,将堆空间分为若干个区域(Region),这些域中包含了逻辑上的年轻代和老年代,同时兼顾年轻代和老年代。
空间整合,内存的回收以region为基本单位,region之间是复制算法,但整体上可以看作标记压缩算法,避免了内存碎片。
可预测的停顿时间:由于G1只选取部分区域进行内存回收,这样就缩小了回收的范围,对于全局停顿情况发生也得到了很好的控制
每次根据允许的收集时间,优先回收价值最大的Region,提高了收集的效率
缺点:内存占用,程序运行时的额外负载都比cms要高
回收过程:主要包括三个过程
年轻代的GC:当年轻代的eden区用尽的时候就开始年轻代的回收,年轻代的收集是一个并行的独占是的收集,需要暂停所有应用线程,启动多线程执行回收,然后从年轻代的Eden区移动到survivor区间,或者老年区。采用复制算法。
老年代的并发标记过程:当堆内存使用达到一定值时(默认45%),开始老年代的并发标记过程。类似于cms的回收机制
混合回收:当越来越多的对象晋升到老年代的区域时,进入混合回收,老年代的回收不需要把整个老年代回收,一次之回收部分的老年代的region,他和你的设定的时间有关,你可以设定回收多大时间的,在限定的时间内回收优先级高(优先队列)的区域,有效的降低低延迟。
Full GC:尽量避免Full GC,当么有足够的晋升空间来存储晋升对象时会触发,并发处理过程完成之前空间耗尽。
G1垃圾回收器详细介绍:(比较复杂,都是概念和理解,大家可以去看尚硅谷关于G1垃圾回收器的详细介绍,在下只是把他讲的内容进行了一个自我的理解和整理)
通过相关的工具;比如Java JDK自带的Jconsole
1.直接增加内存
2.其次就是不同的情况不同的解决方法