1.JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots "/"
为了解决引用计数法的循环引用问题,Java 使用了可达性算法。
跟踪收集器采用的为集中式的管理方式,全局记录对象之间的引用状态,执行时从一些列GC Roots的对象做为起点,从这些节点向下开始进行搜索所有的引用链,当一个对象到GC Roots 没有任何引用链时,则证明此对象是不可达的。
图中,对象Object6、Object7、Object8虽然互相引用,但他们的GC Roots是不可到达的,所以它们将会被判定为是可回收的对象。
哪些对象可以作为 GC Roots 的对象:
-
虚拟机栈(栈帧中的局部变量区,也叫局部变量表)中引用的对象
-
例如: GCRootDemo t1=new GCRootDemo(); 进行一次GC
-
-
-
private static GCRootDemo2 t2; 这类静态属性引用对象,也是GC的目标
-
-
方法去常量引用的对象
-
private static final GCRootDemo t3=new GCRootDemo3(); 常量对象的GC
-
-
同步锁的对象和反射得到的对象(不清楚)
2. JVM调优和参数配置?如何查看JVM系统默认值
JVM操作参数:
如何查看一个正在运行的Java程序,它的某个Jvm参数是否开启,具体值为多少?
jps -l 查看所有正在运行的进程
jinfo -flag 具体参数 java进程号
jinfo -flags java进程编号
盘点家底查看 JVM 默认值 下面三个参数 必须都背下来
查看初始默认值:-XX:+PrintFlagsInitial !!重要!!命令
查看修改更新:-XX:+PrintFlagsFinal
= 与 := 的区别是,一个是默认,一个是人为改变或者 jvm 加载时改变的参数
打印命令行参数(可以看默认垃圾回收器):-XX:+PrintCommandLineFlags
JVM的参数类型:
标配参数
- -version
- -help
X 参数(了解)
- -Xint :解释执行
- -Xcomp:第一次使用就编译成本地代码
- -Xmixed: 混合模式
XX 参数
-
Boolean 类型:【公式如右边】
-XX:+ 或者 - 某个属性值(+ 表示开启,- 表示关闭)
- -XX:+PrintGCDetails:打印 GC 收集细节
- -XX:-PrintGCDetails:不打印 GC 收集细节
- -XX:+UseSerialGC:使用了串行收集器
- -XX:-UseSerialGC:不使用了串行收集器
-
KV 设置类型:-XX:key=value
-
HelloGC.java ,如何查看当前运行程序的配置
jinfo.exe
public class HelloGC { public static void main (String[] args) throws InterruptedException { System.out.println("hello GC"); Thread.sleep(Integer.MAX_VALUE); } }
我们可以使用
jps -l
命令,查出进程 id在使用
jinfo -flag PrintGCDetails 15164
命令查看-XX:-PrintGCDetails #打印
可以看出默认是不打印 GC 收集细节
添加 JVM运行时参数,再次重复上述操作,
结果如下: -XX:+PringtGCDetails 表示启动了该参数
两个经典参数:-xms 和 - Xmx(如 -xms1024m)
- -xms 等价于 -XX:InitialHeapSize 初始化堆内存
- -Xmx 等价于 -XX:MaxHeapSize 最大堆内存
采用jinfo -flags 进程号
查看所有命令,注意是flags
3. JVM常用基本配置参数
快速复习 堆内存
- -xms
- 初始大小内存,默认为物理内存 1/64
- 等价于 -XX:InitialHeapSize
- -Xmx
- 最大分配内存,默认为物理内存的 1/4
- 等价于 -XX:MaxHeapSize
- -Xss
- 设置单个线程栈的大小,一般默认为 512-1024k
- 等价于 -XX:ThreadStackSize
- -Xmn
- 设置年轻代的大小
- 整个JVM内存大小=年轻代大小 + 年老代大小 + 持久代大小,持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。
- -XX:MetaspaceSize
- -XX:+PrintGCDetails
- -XX:SurvivorRatio
- -XX:NewRatio
- 配置年轻代和老年代在堆结构的占比
- 默认 -XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的 1/3
- -XX:MaxTenuringThreshold
4.强引用、软引用、弱引用、虚引用
在Java语言中,除了基本数据类型外,其他的都是指向各类对象的对象引用;Java中根据其生命周期的长短,将引用分为4类。
强引用
只要有人指着,死都不回收OOM也不回收
- 我们平常典型编码
Object obj = new Object()
中的 obj 就是强引用,通过关键字new创建的对象所关联的引用就是强引用。 - 当JVM内存空间不足,JVM宁愿抛出 OutOfMemoryError 运行时错误(OOM),使程序异常终止,也不会靠随意回收具有强引用的“存活”对象来解决内存不足的问题。
- 对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应强引用赋值为 null,就是可以被垃圾收集的了,具体回收时机还是要看垃圾收集策略。
代码Demo
public class StrongReferenceDemo {
public static void main (String[] args) {
Object o1=new Object();//强引用
Object o2;
o2=o1;
o1=null;
System.gc();
System.out.println(o2);
}
}
软引用
在内存充足的时候不回收,内存不够的时候回收
- 软引用通过SoftReference类实现, 软引用的生命周期比强引用短一些。
- 只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象:即 JVM 会确保在抛出 OutOfMemoryError 之前,清理软引用指向的对象。
- 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。后续,我们可以调用ReferenceQueue的poll()方法来检查是否有它所关心的对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。
- 应用场景:软引用通常用来实现内存敏感的缓存。如果还有空闲内存,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
代码验证Demo
package com.ybzn._01.jvm;
import java.lang.ref.softReference;
/**
* 软引用
* 在内存充足的时候不回收,内存不够的时候回收
* @author Hugo
* @time 2021/2/23
*/
public class SoftReferenceDemo {
/**
* 内存足够的时候,软引用
*/
public static void softRef_Memory_Enough(){
Object o1 =new Object();
SoftReference<Object> softReference =new SoftReference <>(o1);
System.out.println(o1);
System.out.println(softReference.get());
//开始折腾
o1=null;
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
public static void main (String[] args) {
// softRef_Memory_Enough();
softRef_Memory_NoEnough();
}
/**
* 内存不够 的时候软引用
* 运行时 参数:
* -xms5m -Xmx5m -XX:+PrintGCDetails
*/
private static void softRef_Memory_NoEnough () {
Object o1 =new Object();
SoftReference<Object> softReference =new SoftReference <>(o1);
System.out.println(o1);
System.out.println(softReference.get());
//开始折腾
o1=null;
try {
byte[] bytes=new byte[6000*1024*1024];
}catch (Exception e){
e.printstacktrace();
}finally {
System.out.println(o1);
System.out.println(softReference.get());
}
}
}
结果: 爆OOM的时候,会回收软引用
-
在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。由于垃圾回收器是一个优先级很低的线程,因此不一定会很快回收弱引用的对象。
-
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
-
应用场景:弱应用同样可用于内存敏感的缓存。
-
代码验证Demo
-
public class WeakReferenceDemo { public static void main(String[] args) { Object obj = new Object(); WeakReference<Object> weakReference = new WeakReference<>(obj); System.out.println(obj); System.out.println(weakReference.get()); obj = null; System.gc(); System.out.println("GC之后...."); System.out.println(obj); System.out.println(weakReference.get()); } }
-
软引用和弱引用的应用场景
·
此时可以采用软引用解决这个问题!
设计思路:
用一个HashMap来保存图片的路径和响应图片对象关联的软引用直接的映射关系,在内存不足的时候,JVM会自动回收这些缓存图片所占的空间,从而有效避免了OOM的问题
Map<String,WeakReference<Bitmap>> weakReferenceMap =new HashMap <>();
软引用—-WeakHashMap 进行GC清理的时候,会清理WeakHashMap中的对象
但是使用HashMap 进行GC清理的时候,哪怕key=null
也不会清理HashMap中的对象,因为他们是强引用
引用队列—-Demo.java
public class ReferenceQueueDemo {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<>(obj, referenceQueue);
System.out.println(obj);
System.out.println(weakReference.get());
System.out.println(weakReference);
obj = null;
System.gc();
Thread.sleep(500);
System.out.println("GC之后....");
System.out.println(obj);
System.out.println(weakReference.get());
System.out.println(weakReference);
}
}
会把该对象的包装类即weakReference
放入到ReferenceQueue
里面,我们可以从queue中获取到相应的对象信息,同时进行额外的处理。比如反向操作,数据清理等。
输出:
java.lang.Object@1540e19d
java.lang.Object@1540e19d
java.lang.ref.WeakReference@677327b6
GC之后....
null
null
java.lang.ref.WeakReference@677327b6
虚引用
-
幻象引用仅仅是提供了一种确保对象被
finalize
以后,做某些事情的机制。 -
虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
-
ReferenceQueue queue =new ReferenceQueue(); Phantomreference pr =new Phantomreference(object, queue);
-
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取一些程序行动。
5. 理解OOM
- java.lang.StackOverflowError
- java.lang.OutOfMemoryError : Java heap space
- new 一个很大对象
byte[] bytes=new byte[30*1024*1024]
- new 一个很大对象
- java.lang.OutOfMemoryError : GC overhead limit exceeded
- java.lang.OutOfMemoryError : Direct buffer memory
- 本地内存空间不够,这里主要用于NIO中的buffer缓冲里面实现的,有两种,一种是存放在JVM堆里面的,另外一种是存放在本地内存方法中的
配置参数:-xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
public class DirectBufferDemo {
public static void main(String[] args) {
System.out.println("maxDirectMemory : " + sun.misc.VM.maxDirectMemory() / (1024 * 1024) + "MB");
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}
maxDirectMemory : 5MB
[GC (System.gc()) [PSYoungGen: 1315K->464K(2560K)] 1315K->472K(9728K), 0.0008907 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 464K->0K(2560K)] [ParOldGen: 8K->359K(7168K)] 472K->359K(9728K), [Metaspace: 3037K->3037K(1056768K)], 0.0060466 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at com.cuzz.jvm.DirectBufferDemo.main(DirectBufferDemo.java:17)
Heap
PSYoungGen total 2560K, used 56K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 2%【"这里只用了很少的空间,但是任然爆内存了"】 used [0x00000000ffd00000,0x00000000ffd0e170,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 359K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 5% used [0x00000000ff600000,0x00000000ff659e28,0x00000000ffd00000)
Metaspace used 3068K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 336K, capacity 388K, committed 512K, reserved 1048576K
-
java.lang.OutOfMemoryError : unable to create new native thread
6.GC垃圾回收算法和垃圾收集器的关系?分别谈谈
常用的垃圾收集器
串行垃圾收集器(Serial)
并发垃圾收集器(CMS)
用户线程和垃圾收集线程同时执行(不一定并行,可能交替执行),不需要停顿用户线程,互联网公司大多数采用CMS垃圾收集器,适合于对响应时间有要求的场景
G1垃圾收集器
G1垃圾收集器将堆内存分割成不同的区域,然后并发对其进行垃圾回收。
Java 11 新出了另外一种ZGC
7.怎么查看服务器默认的垃圾回收器是那个? 生产上如何配置垃圾收集器?垃圾收集器的理解
-
Java 的 GC 回收的类型主要有:
-
垃圾收集器(上面年轻代,下面老年代)
-
参数说明
-
Server/Client 模式分别是什么意思
-
新生代
老年代
-
串行 GC (Serial Old/ Serial MSC)
-
并发标记清除 GC (CMS) 养老区
-
是一种以获取最短回收停顿时间为目标的收集器,适合应用在互联网站或者 B/S 系统的服务器上,这个类应用尤其重视服务器的响应速度,希望系统停顿时间最短 。 年轻代采用ParNew收集器
-
CMS 非常适合堆内存大、cpu 核数多的服务器端应用,也是 G1 出现之前大型应用首选收集器。
-
并发停顿比较少,并发指的是与用户线程一起执行。
-
过程
- 初始标记(initail mark):只是标记一下 GC Roots 能直接关联的对象,速度很快,需要暂停所有的工作线程
- 并发标记(concurrent mark 和用户线程一起):进行 GC Roots 的跟踪过程,和用户线程一起工作,不需要暂停工作线程。
- 重新标记(remark):为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。
- 并发清除(concurrent sweep 和用户线程一起):清除 GC 不可达对象,和用户线程一起工作,不需要暂停工作线程,基于标记结果,直接清除。由于耗时最长的并发标记和并发清除过程中,垃圾收集线程和用户线程可以一起并发工作,所以总体来看 CMS 收集器的内存回收和用户线程是一起并发地执行。
-
优缺点
-
由于并发进行, CMS 在收集与应用线程会同时增加对堆内存的占用,也就是说,CMS 必须要在老年代堆用尽之前完成垃圾回收,否者 CMS 回收失败,将触发担保机制,串行老年代收集器将会以 STW 的方式进行一次 GC,从而造成较大的停顿时间。
-
标记清除算法无法整理空间碎片,老年代空间会随着应用时长被逐渐耗尽,最后将不得不通过担保机制对堆内存进行压缩。CMS 也提供了参数 -XX:CMSFullGCsBeForeCompaction (默认0,即每次都进行内存整理) 来指定多少次 CMS 收集之后,进行一次压缩
-
垃圾收集器配置代码总结,配置新生代收集器,老年代收集器会自动配置上。
1558237229584
-
如何选择垃圾收集器
G1 垃圾收集器你了解吗?
-
以前收集器的特点
- 年轻代和老年代是各自独立且连续的内存块
- 年轻代收集器使用 eden + S0 + S1 进行复制算法
- 老年代收集必须扫描整个老年代区域
- 都是以尽可能的少而快速地执行 GC 为设计原则
G1 是什么
- G1 是一种面向服务端的垃圾收集器,应用在多核处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能的满足垃圾收集器的暂停时间要求。
- 像 CMS 收集器一样,能与应用程序线程并发执行,整理空闲空间更快,需要更多的时间来预测 GC 停顿时间,不希望牺牲大量的吞吐性能,不需要更大的 JAVA Heap。
- G1 收集器的设计目的是取代 CMS 收集器,同时与 CMS 相比,G1 垃圾收集器是一个有整理内存过程的垃圾收集器,不会产生很多内存碎片。G1 的 Stop The World 更可控,G1 在停顿上添加了预测机制,用户可以指定期望的停顿时间。
- G1 是在 2012 年才在 jdk.1.7u4 中可以呀用,在 jdk9 中将 G1 变成默认垃圾收集器来代替 CMS。它是以款面向服务应用的收集器。
- 主要改变是 Eden、Survivor 和 Tenured 等内存区域不再是连续的,而是变成了一个个大小一样的 region,每个 region 从 1M 到 32M 不等,一个 region 有可能属于 Eden、Survivor 或者 Tenured 内存区域。
G1的特点
- G1 能充分利用多 cpu、多核环境硬件优势,尽量缩短 STW。
- G1 整体采用标记-整理算法,局部是通过是通过复制算法,不会产生内存碎片。
- 宏观上看 G1 之中不在区分年轻代和老年代,被内存划分为多个独立的子区域。
- G1 收集器里面讲整个的内存区域混合在一起,但其本身依然在小范围内要进行年轻代和老年代的区分。保留了新生代和老年代,但她们不在是物理隔离,而是一部分 Region 的集合且不需要 Region 是连续的,也就是说依然会采用不同的 GC 方式来处理不同的区域。****
- G1 虽然也是分代收集器,但整个内存分区不存在物理上的年轻代和老年代的区别,也不需要完全独立的 Survivor to space 堆做复制准备。G1 只有逻辑上的分代概念,或者说每个分区都可能随 G1 的运行在不同代之间前后切换。
G1的常用参数
- -XX:+UseG1GC
- -XX:G1HeapRegionSize=n; 设置G1区域大侠你。值是2的幂次方,范围是1M-32M,目标是根据最小的Java堆来进行划分得出的
- -XX:MaxGCPauseMillis=n; 最大GC停顿时间,这个是软目标,JVM尽可能停顿小于该时间
- -XX:InitiatingHeapOccupancyPercent=n; 堆占用了多少的时候就触发GC,默认45
- -XX:ConcGCThreads=n; 并发GC的使用线程数
- -XX:G1ReservePercent=n; 设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险,默认值为10%
底层原理
Region 区域化垃圾收集器:最大好处是化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。
G1的内存结构和传统的内存空间划分有比较的不同。G1将内存划分成了多个大小相等的Region(默认是512K),Region逻辑上连续,物理内存地址不连续。同时每个Region被标记成E、S、O、H,分别表示Eden、Survivor、Old、Humongous。其中E、S属于年轻代,O与H属于老年代。
H表示Humongous。从字面上就可以理解表示大的对象(下面简称H对象)。当分配的对象大于等于Region大小的一半的时候就会被认为是巨型对象。H对象默认分配在老年代,可以防止GC的时候大对象的内存拷贝。通过如果发现堆内存容不下H对象的时候,会触发一次GC操作。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。