JVM学习笔记

jvm垃圾回收的时候如何确定是垃圾?是否知道什么是GCROOT,以及什么对象可以作为GCRoot。

如何确定是垃圾:简单说内存中不被使用的空间就是垃圾

1、引用指针法:

是通过引用计数器来进行判断的,如果有地方引用它计数器加一,如果引用失效就减一。这样会产生一个问题就是循环引用的问题,如果对象之间循环引用就不会被回收。

2、GCRoot可达性分析:

通过一系列GCRoot对象作为起点,开始向下搜索如果一个对象到GCRoot没有任何引用链相连的话就称为这个对象不可达,就是垃圾对象。

什么对象可以作为GCRoot对象:

虚拟机栈(栈帧中的本地变量表)中引用的对象。

方法区中的类静态属性引用的对象。

方法区中常量引用的对象。

本地方法栈中native方法引用的对象。

你说过你坐过jvm调优和jvm参数配置,请问如何盘点查看jvm系统默认值。

jvm参数类型:

1、标配参数:

从java语言开始到现在都存在的参数(jdk各版本不会变化)

java -version

java -help

java -showversion

2、X参数:(了解)

java -Xint (解释执行)

java -Xcomp (第一次使用就编译成本地代码)

java -Xmixed (混合模式,先编译后执行)

例如:

image.png
3、XX参数:(重点)

Boolean类型:

-XX:+或者-某个属性,+代表开启这个属性,-代表关闭这个属性。

jps -l //查看后台正在运行的java程序

jinfo -flag + 属性名 + 进程编号

jinfo -flag PrintGCDetails 13632 //是否打印GC收集细节。

例如:
在这里插入图片描述

image.png

kv键值对类型:

image.png

MaxTenuringThreshold //经过几次GC才能进入老年代。

例如:

查看某个参数的默认值

jinfo -flag MetaspaceSize + 进程编号 //查看元空间默认值大小。

如何配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IB0evKBI-1601111719348)(http://www.ztinfo.xyz/upload/2020/1/image-576848b4eb114620a3b3b200f24802f9.png)]

再次查看:

jps -l //查看进程编号

jinfo -flag MetaspaceSize + 进程编号

如何查看当前运行程序的配置

jinfo -flags +进程编号

image.png

题外话

-Xmx //最大堆内存 配置-Xmx1024m

-Xms //初始堆内存 配置-Xms1024m

算X参数还是XX参数?(XX参数)

总结:调参其实就是Boolean参数是否开启,还有就是kv键值对从多大调到多大。

查看jvm初始化参数(重点)

java -XX:+PrintFlagsInitial //zzz

image.png

查看修改过的jvm参数

java -XX:+PrintFlagsFinal

主要区分:=和=的区别

:=代表人工修改过的

=代表默认参数值

打印出命令行参数值

java -XX:+PrintCommandLineFlags

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fGp0pYrJ-1601111719351)(http://www.ztinfo.xyz/upload/2020/1/image-95e8c387f832443487f52bb445c60eb3.png)]

你用过jvm常用基本配置参数有哪些

-Xms 初始堆内存大小一般默认为物理内存1/64

-Xmx 最大堆内存大小一般默认为物理内存1/4

-Xss 单个线程初始话栈内存空间一般512k-1024k

如图:
image.png

-Xss初始栈内存分析(栈管运行,堆管存储)

什么也没配置在windows环境下查看后台java进程发现初始栈内存为0

image.png
在命令行配置-Xss1024k再次运行

image.png
image.png
查看oracle官方文档

https://docs.oracle.com/javase/8/docs/index.html
在linux(64)环境下默认1024k

windows环境依赖于虚拟内存。

image.png

-Xmn 设置堆中的新生代大小。

-XX:MetaspaceSize

设置元空间大小,元空间本质和永久代相似,都是对jvm规范中方法区的实现。不过元空间与永久代最大的区别在于,元空间不存在虚拟机中,而是使用本地内存。因此默认情况下元空间的大小仅受本地内存限制。

-XX:+PrintGCDetails

-XX:SurvivorRatio

设置新生代中Eden和S0/S1空间的比例

默认-XX:SurvivorRatio=8, Eden:S0:S1=8:1:1

假如:-XX:SurvivorRatio=4, Eden:S0:S1=4:1:1

-XX:NewRatio

设置新生代和老年代占比

默认-XX:NewRatio=2新生代占1老年代占2,新生代占整个堆内存1/3

假如:-XX:NewRatio=4,新生代占1老年代占4,新生代占整个堆内存1/5

-XX:MaxTenuringThreshould=15

设置垃圾的最大年龄,默认15次会到老年代。

堆内存图

在这里插入图片描述

经典示例演示:

-Xms128m -Xmx4096m -Xss1024k -XX:MetaSpaceSize=512m -XX:+PrintCommentLineFlags -XX:+PrintGCDetails -XX:+UserSerialGC

什么也没配置:

配置之后:

GC详细信息分析

-XX:+PrintGCDetails(打印GC详细信息)

默认没有发生GC垃圾回收控制台打印出如下图:

打印出堆内存的详细信息:

当我们故意配置

-xms10m -xmx10m -XX:+PrintGCDetails

在代码中故意new一个大对象

Byte [] b = new Byte[5010241024];

控制台打印如下图:

在这里插入图片描述

GC和FullGC

GC主要在新生代,FullGC主要在老年代

GC图解:

image.png
FullGC图解:

强引用,软引用,弱引用,虚引用分别是什么?

架构图:

image.png

强引用(Reference)

当内存不足时,jvm开始回收内存,对于强引用的对象,就算出现OOM异常也不会对该对象回收,把一个对象赋值给一个引用变量,这个引用变量就是强引用。我们平时用的最多的就是强引用。

如图:

软引用(SoftReference)

当系统内存够用时,发生GC也不会回收,当系统内存不够用时,发生GC会回收。

1、内存够用演示(发生GC也不会被回收)

2、内存不够用演示(发生GC会回收内存)

设置堆内存大小,堆最大内存大小,打印GC详细信息

-Xms=5m -Xmx=5m -XX:+PrintGCDetails

弱引用(WeakReference)

只要发生GC就会回收对象。

软引用和弱引用的应用场景

假如有一个应用需要读取大量的本地图片,

如果每次读取都从硬盘中读取,会严重影响系统性能。

如果全部读取到内存会报OOM异常内存溢出,会把内存占满。

设计思路:

如果用HashMap来保存图片路径和相应图片对象软引用之间的映射关系,在内存不足时会触发GC来回收这些软引用对象。从而有效的避免OOM问题。

WeakHashMap

当key设置为null,发生GC会把WeakHashMap中的元素进行回收。

例如:

虚引用(PhantomReference)fan in tai mu

如果一个对象仅持有虚引用,就和没有任何引用一样,随时都有可能被回收。不能单独使用必须结合引用队列(ReferenceQueue)来使用,在进行GC回收后虚引用对象会被放到引用队列里边,完成对象最后的操作。和Finalize()方法一样。

常见的OOM异常(jvm异常属于错误)

1、java.lang.StackOverflowError //栈内存溢出

一般错误原因使用了递归算法,方法中调用自己。

2、java.lang.OutOfMemoryError:java heap space //堆内存溢出

一般错误原因是因为创建了大对象。

3、GC overhead limit exceeded //GC回收时间过长,我们都知道GC是个单线程,如果频繁GC会让我们的系统变慢,cpu百分之98的资源都用于GC,回收的内存并不理想,频繁创建对象。

4、Direct buffer memory //直接内存挂了。

出现原因主要是写NIO程序经常用到字节流(byteBuffer)来读取和写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的IO方式。

byteBuffer它可以使用native函数库直接分配堆外内存,然后通过一个存储在堆里边的DirectByteBuffer对象作为这块内存的引用对象,进行操作,这样就避免了java堆和native堆中来回复制数据。

ByteBuffer.allocate(分配),分配jvm堆内存属于GC管辖范围,需要native堆拷贝数据到jvm堆浪费时间。

ByteBuffer.allocateDirect(直接分配),分配本地内存,不属于GC管辖,不用复制数据。

如果不断的分配本地内存,DirectByteBuffer对象就不会被回收,虽然java堆中内存很足,但是直接内存已经满了,程序直接崩溃。

两者的对比图(自己理解的不知道对不对)

5、unable to create new native thread //不能够再创建新的线程了。

出现原因,你的应用创建了太多的线程,超过系统的承载能力。默认Linux系统不是root用户最多能创建1024个线程。

解决办法:

1、想办法降低自己程序中创建线程的数量。

2、更改系统配置,允许创建最大线程数。

ulimit -u //查看本用户允许创建的最大线程数。

vi /etc/security/limits.d/90-nproc.conf //更改配置如图所示

image.png
6、Metaspace //元空间溢出

元空间主要存放jvm加载类的信息、常量池、静态变量、即时编译后的代码。
主要原因是通过反射,不断向元空间加载类信息。

GC垃圾回收算法和GC垃圾回收器分别是什么?

GC垃圾回收算法:

1、引用计数算法

2、复制算法

3、标记算法

4、标记整理清除算法

GC垃圾回收器:

1、串行垃圾回收器(Serial)

只有一个线程进行垃圾回收,并且会阻断用户线程。

2、并行垃圾和回收器(Parallel)

多个线程进行垃圾回收,也会阻断用户线程,适用于科学计算弱交互场景

3、并发标记清除垃圾回收器(CMS)

大多数互联网公司默认的垃圾回收器,垃圾回收线程和用户线程同时执行,适应于对响应时间有要求的场景,会产生内存碎片。

4、G1垃圾回收器(G1)

将堆内存分成不同的区域,并发对其进行垃圾回收。

在这里插入图片描述

新生代主要用的垃圾回收器

1、串行垃圾回收器(Serial),自动激活老年代Serial Old

一般用于单个应用程序,单核的服务器。

开启:-XX:+UseSerialGC

2、并行垃圾收集器(ParNew)Parallel new Generation(代)

在新生代用并行收集器,默认老年代会自动激活串行垃圾回收器(Serial Old)

不推荐使用了(jdk8)

开启:-XX:+UserParNewGC

3、并行清除(Parallel Scavenge)

在新生代配置并行清除,新生代,老年代都会启动多个线程来收集。

吞吐量大,吞吐量=(程序运行时间-GC回收时间)/程序运行时间。多线程GC耗时短,吞吐量就大。

开启:-XX:+UseParallelGC

新生代和老年代对应垃圾回收器图解:

image.png

CMS并发标记清除垃圾回收器详解(用在老年代)

开启:-XX:UseConcMarkSweepGC 开启之后新生代会默认打开ParNew并行垃圾收集器。

CMS回收器分为4步

1、初始标记(会阻断用户线程)

2、并发标记(与用户线程同时进行)

3、重新标记(会阻断用户线程),第2步有可能产生新的垃圾对象。

4、并发清除(和用户线程一起进行)

优点:并发收集停顿低

缺点:产生内存碎片,并发对cpu的压力也比较大。

CMS垃圾回收器一定要在老年代堆内存用尽之前完成垃圾回收,否则就会导致CMS回收器失败转成Serial old(老年代的串行垃圾回收器),造成停顿时间长。

总结

如何选择垃圾回收器

image.png

G1垃圾回收器简介

开启:-XX:+UseG1GC

回顾以前三大收集器的特点:

1、年轻代和老年代都是独立且连续的内存块。

2、年轻代收集使用单一的eden+S0+S1进行复制算法。

3、老年代收集必须扫描整个老年代区域内存。

4、都是以快速执行GC为设计原则。

G1是什么?

G1是一种服务端的垃圾回收器,应用在多处理器和大容量内存环境中。在实现高吞吐量的同时,尽可能满足垃圾收集暂停的时间。

G1主要改变Eden、Survivor和Tenured(老年代)等区域不在是连续的,而是变成一个一个大小一样的region,每个region大小1~32M,一个region有可能属于Eden、Survivor和Tenured内存区域。

G1特性:

1、像CMS收集器一样,能与应用线程同时执行。

2、整理空闲空间更快。

3、可以预测GC的停顿时间。

4、不需要更大的java heap(堆)

G1与CMS相比优势?

1、G1不会产生内存碎片

2、用户可以指定GC的挺顿时间单位毫秒。

G1的原理

最大的好处就是不区分新生代,老年代,整个内存化整为零,避免全内存扫描,只需要按照区域来进行扫描即可。

核心思想是将整个堆内存区域分成大小相同的子区域(Region),在JVM启动时会自动设置这些区域大小,启动时可以通过参数-XX:G1HeapRegionSize=n可指定分区大小(1~32M,且必须是2的幂次方)默认将整个堆内存划分成2048个分区,也就是能够支撑的最大内存32M*2048=65536M(64G)

如图所示:

G1垃圾回收器,虽然把堆内存划分成一个一个小内存,但还是分代收集器(还有Eden、Survivor、Old)。

这些Region一部分包含新生代,新生代的垃圾收集仍然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者幸存区(Survivor)se vai v。

在拷贝的同时也完成了堆的压缩,内存小,操作快,不会产生内存碎片。

在G1中,还有一种特殊的区域,叫(Humongous)巨型区,在G1中如果一个对象占分区容量的50%以上,G1收集器就会认为这是一个巨型对象,这些巨型对象默认会被直接分配到老年代,但是如果它是一个短期存活的巨型对象,就会对垃圾回收器造成负面影响,为了解决这个问题,G1划分了一个Humongous,他专门来存放巨型对象,G1会寻找连续的H区进行存储,为了能找到连续的H区,有时候要启动Full GC,在老年代中执行。

常用参数设置

-XX:+UseG1GC //使用G1垃圾回收器

-XX:G1HeapRegionSize = n //设置Region区域大小,值是2的幂次方,大小1~32M

-XX:MaxGCPauseMillis=n //最大GC停顿时间,jvm尽可能会小于这个时间(但不保证)

-XX:InitiatingHeapOccupancyPercent=n //堆占用了多少就触发GC默认是45

-XX:ConcGCThreads=n //并发GC使用的线程数

jvmGC配合SpringBoot微服务使用

在本地可以配置SpringBoot入口函数配置Jvm参数。

在Linux服务器上java -jar xxxx.war 启动一个.jsp集中式项目。

如果启动时候想加入jvm参数。

java -server jvm参数 -jar xxx.war

例如:

java -server -Xms1024M -Xmx1024m -XX:+UseG1GC -jar xxx.war

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值