学习笔记:JVM的底层挖掘

JDK

jdk是JAVA开发工具包,包含了各种JAVA类库
JRE是JAVA运行时环境通过它才能将开发的程序发布到用户手中
JVM是JAVA虚拟机(JVM是个标准,它的实现有HotSpot虚拟机)所有程序编译为.class文件后才可以再虚拟机上运行
JDK包括JRE,JRE包括JVM

并行和并发的区别:

并发是指一个时间段内有两个及以上的程序执行
并行是指一个时刻内有两个及以上的程序执行

java跨平台:

windows平台使用的是Intel汇编,unix平台使用的是AT&T汇编,还有一种汇编是ARM,各个平台的底层汇编都不一样,因此很多语言,一段相同的代码无法在多个平台运行
JAVA中一段相同的代码,在不同的系统上使用其系统对应的JVM来运行,那么运行结果是相同的.

jvm即JAVA虚拟机年轻代对象经过15次GC后依然存活的话就放入老年代

jvm的内存区域:

线程共享的有堆,方法区,线程独占的有虚拟机栈,本地方法栈,程序计数器

堆:

存放引用类型的对象,分为年轻代,老年代
jdk1.7:年轻代,老年代,永久区 jdk1.8:eden,s0,s1,老年代,元空间(虚拟机中加载的类信息,即时编译后的代码,常量池,静态变量),由于永久代经常发生内存泄漏所以替换为元空间
年轻代占堆大小的1/3,老年代占堆大小的2/3,eden占年轻代的4/5,s0,s1各占年轻代的1/10

虚拟机栈:

虚拟机栈包括多个栈帧,类中各个方法对应栈中的一个栈帧,每个栈帧中包括方法中的局部变量,方法的出口,动态链接(符号引用在运行期间转化为直接引用,这种转化为动态链接),操作数栈(存放方法中的一些临时操作数)
(栈就是后进先出,因此被嵌套执行的方法(后运行的方法)先结束,再结束外面的方法)

堆和虚拟机栈的关系:

当有一个引用类型的局部变量时,它存放在堆当中,栈中存放的是该引用变量的引用(指针)
栈中存放的变量生命周期一旦结束就会被销毁,堆中存放的对象会被垃圾收集器不定时收集
栈的内存地址是连续的,堆的内存地址是不连续的

本地方发栈:

程序运行中可能会调用到一些用C/C++编写的native对象,调用到之后就将其放在本地方法栈里面

程序计数器:

用于记录当前线程在栈中程序执行到哪一步(以便于线程上下文切换后能找到正确的执行位置)
上下文切换:CPU为线程分配一个时间片,时间片结束会重新进入就绪状态,让其他线程执行,以此来保证每个线程都能运行到

方法区(元空间):

存放:即时编译后的代码(class文件),常量池,静态变量,虚拟机中加载的类的信息 (大小可以设为物理内存的 1/32)
(替换掉了1.7的永久代,因为永久代经常会发生内存不够用和内存泄漏的情况,元空间所占用的空间是在本机的内存中不在JVM内存中)

双亲委派机制:

当一个类加载器要加载一个类时,判断该类是否被加载过(加载过直接返回)会将该类交给其父加载器去完成,每一次都是如此,直到递归到最顶层,当父加载器无法完成这个请求时,子类才会尝试去加载。

作用:

防止重复加载同一个.class,保证.class的执行安全,保证核心的.class不能被篡改
如果我们不想使用双亲委派原则,则可以自定义一个类加载器,重写loadClass()方法(该方法就是启动双亲委派的方法)

JVM加载class文件的原理机制:

JAVA中所有类的加载都是由类加载子系统实现的,类加载器再装载到JVM中去运行。
类加载子系统(类的加载过程)的执行过程:加载,验证,准备,解析,初始化
类加载子系统: 启动类加载子系统:最顶层的类加载器(父类加载器)加载JAVA的一些核心类和jar包
扩展类加载子系统:加载JRE的扩展目录
应用类加载子系统:加载当前classPath下的jar包和类

jvm中的垃圾回收(GC)算法:

引用计数法:被引用到的对象,计数器会+1,计数器为0时,会认为该对象是垃圾对象被回收掉(由于无法解决循环引用问题,已被淘汰)

标记清除算法:分为两步,第一步为标记将未被根结点引用到的对象进行标记,第二步清除将标记到的对象给清除掉。问题:效率低每次标记对象时会进行STW,内存碎片化严重(老年代)

标记压缩算法:相较于标记清除算法,他在清除前会将标记好的对象压缩到一起,然后进行清除,他解决了内存碎片化严重的问题(老年代)

复制算法:分为to和form两个区域,当进行垃圾回收时,将form区非垃圾对象移到to区,在移动的过程中将垃圾清除掉,然后to和form区调换位置,保证to区为空(年轻代,因为年轻代的对象只有少数会存活下来
所以使用复制算法效率是最高的)

分代算法:根据不同的代选择不同的算法

哪些对象可以作为根结点对象?

native本地的对象
虚拟机栈中引用到的对象
方法区中常量引用的对象
方法区中类静态属性引用到的对象

jvm中的垃圾收集器:

串行垃圾收集器:它在进行GC垃圾回收时,会STW阻止用户线程的执行。(年轻代:复制,老年代:标记压缩)

并行垃圾收集器:它在进行GC垃圾回收时会启动多个垃圾回收的线程并行回收,但他也会STW阻塞用户线程的执行。(年轻代:复制,老年代:标记压缩)

cms垃圾收集器:它在进行GC垃圾回收时可以让垃圾回收的线程和用户线程并行执行,它采用的是标记清除算法。(年轻代:复制,老年代:标记清除)

G1垃圾收集器: 它取消了年轻代和老年代的划分,取而代之的是将整个堆内存划分为了若干区域(eden,survivor,old,humongous(专门用来存放巨型对象的超过50%计算))它在GC时采用的是类似于复制算
法的方式,解决了CMS的碎片化问题,且它可以指定GC的时间(大部分采用标记压缩,有时采用复制)

G1和CMS的比较

CMS分为:初始标记,并发标记,重新标记,并发清除

①:CMS收集器是获取最短回收停顿时间为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低收集停顿时间的目的(只有初始标记和重新标记会STW)。但是CMS收集器
对CPU资源非常敏感。在并发阶段,虽然不会导致用户线程停顿,但是会占用CPU资源而导致程序变慢,总吞吐量下降。

②:CMS仅作用于老年代,是基于标记清除算法,所以清理的过程中会有大量的空间碎片。

③:CMS收集器无法处理浮动垃圾,浮动垃圾:(由于CMS并发清理阶段用户线程还在运行,伴随程序的运行自然会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,CMS无法在本次收集中处理它们,只
好留待下一次GC时将其清理掉)。

④:G1是一款面向服务端应用的垃圾收集器,适用于多核处理器、大内存容量的服务端系统。G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短STW的停顿时间,它满足短
时间停顿的同时达到一个高的吞吐量。

⑤:从JDK 9开始,G1成为默认的垃圾回收器。当应用有以下任何一种特性时非常适合用G1:Full GC持续时间太长或者太频繁;对象的创建速率和存活率变动很大;应用不希望停顿时间长(长于0.5s甚至1s)。

⑥:G1将空间划分成很多区(Region),eden,survivor,old,Humongous区(Humongous专门用于巨型对象,当对象占用空间超过了分区的50%就是巨型对象了)
然后他们各自进行回收。堆比较大的时候可以采用,采用复制算法,碎片化问题不严重。整体上看属于标记整理算法,局部(region之间)属于复制算法。

⑦:G1 需要记忆集 (具体来说是卡表)来记录新生代和老年代之间的引用关系,这种数据结构在 G1 中需要占用大量的内存,可能达到整个堆内存容量的 20% 甚至更多。而且 G1 中维护记忆集的成本较高,带来了
更高的执行负载,影响效率。

所以 CMS 在小内存应用上的表现要优于 G1,而大内存应用上 G1 更有优势,大小内存的界限是6GB到8GB。

G1缺点:耗费空间耗费内存,to和form复制来复制去的

CMS缺点:耗费CPU资源,且会有内存碎片

jvm中常见的参数:

-xx:xms:初始堆大小

-xx:xmx:最大堆大小

-xx:xmn:年轻代大小

-xx:xss:栈大小

-xx:+printGCDetails:打印出GC的详情

-xx:metespaceSize:元空间大小

如何查看JVM中的参数?

-xx:+printflagsInitial:打印出jvm的初始参数
①-xx:+printflagsfinal:

②jps -l或者ps -ef|grep java 打印出程序的pid jinfo -flags pid : 打印出jvm当前设置的参数

-xx:+printcommandLineFlags:打印出jvm所用的垃圾收集器

如何进行JVM调优?(减少full gc的次数)

一:先查看服务器的性能:通过top或uptime查看整机的性能

二:查看程序中堆内存的信息:jmap命令将内存信息dump出来(jmap -dump:fromat=b file=文件存放的路径 )通过mat工具进行分析查看(或者直接用jdk自带的jvisualvm工具进行查看)是否有内存泄漏的情况,定位到程序处修改代码

三:查看JVM的参数信息:通过jInfo命令查看或直接打印出JVM的参数信息-xx+:printflagsInitial,查看JVM中垃圾收集器-xx+:printCommandlineflags,查看JVM最终参数-xx:+printflgsFinal

四:对JVM的GC情况做检查:通过-xx+printGCDetails打印出GC.log日志,用在线工具(https://gceasy.io)对其进行查看,查看fullGC的次数,若太多的话根据具体的业务情况可通过-xx:xmn来设置年轻代的大小来减少fullGc,也可以设置一些其他的参数信息。

五:查看线程的使用情况通过jstack命令来查看线程使用是否存在死锁。

线上JVM调优:

调优的目的:让用户体验更好,减少fullGC的次数,因为fullGC的时候会STW,STW时用户线程无法执行

一:FullGC太频繁
初始堆大小设定是3G,年轻代占1/3 1G,eden区占年轻代的8/10 800M,两个survivor区各占1/10 100M
代码中有个接口每次调用都会创建一个60M大小的对象,当eden区满了进行minorGC的时候,又创建了一个60M的对象(浮动对象),此时直接就放入survivor区了,由于它的大小大于survivor区的1/2
所以直接被存放到了老年代,以此递推最终老年代会被放满发生fullGc,因此可以将年轻代大小设置大一些,这样浮动对象就不会放到老年代,可以完美减少FullGc次数

二:元空间情况(元空间如果不指定大小,他会随着类的不断创建,虚拟机会耗尽所有可用的系统内存)
服务进程占用了容器的物理内存(16G)的百分之80
通过jmp命令将内存的使用情况jump出来(jmp jump:format-b file=要存放的位置 )后用mat工具进行分析发现是元空间占用内存太大了
查看配置的启动参数的时候,发现对永久代的大小作了限制(-XX:PermSize=256m -XX:MaxPermSize=512m),最后发现线上用的是1.8的环境,1.8当中永久代被元空间代替了
所以删除了永久代的配置,添加了元空间的配置(-XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m)最终问题得到了解决

讲讲什么情况下会出现内存溢出,内存泄漏?

内存泄漏:JAVA是自动进行垃圾回收的,可能会出现已过期的对象,JVM还误以为此对象没过期,没有回收,最终造成内存泄漏。
造成内存泄漏的方式:静态集合类,各种连接没有关闭(数据库,io,线程池)

内存溢出:内存不够用了,可能是由于内存泄漏,死循环,设定的内存太小,对象太大导致。

如何判断一个常量是废弃常量?

假如常量池中存在字符串“abc”,若“abc”没有被任何String对象引用,那么常量“abc”就是废弃常量

如何判断一个类是无用的类?

①:类中所有属性已被回收

②:该类的classLoader已被回收

③:该类的Class对象没有被引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guojunjiang12345

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值