深入了解JVM的底层原理

深入了解JVM的底层原理

引言:什么是JVM?
JVM在整个jdk(java 运行环境)中处于最底层,负责与操作系统的交互,用来屏蔽操作系统环境,提供一个完整的Java运行环境,因此也就虚拟计算机. 操作系统装入JVM是通过jdk中Java.exe来完成,通过下面4步来完成JVM环境.
1.创建JVM装载环境和配置
2.装载JVM.dll
3.初始化JVM.dll并挂界到JNIENV(JNI调用接口)实例
4.调用JNIEnv实例装载并处理class类。

一:JVM内存区域模型
这里写图片描述
1、方法区:
也称“永久代”,“非堆”,用于储存虚拟机加载的类信息,常量,静态变量,是各个线程共享的内存区域。

运行时常量池:方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息就是常量池,用于存放编译器生成的各种符号引用,这部分内容将在类加载后放到方法区的运行时常量池中。

2、虚拟机栈:
描述的是java方法执行的内存模型,每个方法被执行的时候,都会创建一个“栈帧”用于存储局部变量(包括参数),操作栈,方法出口等信息。每个方法被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。生命周期与线程相同,是线程私有的。

**局部变量表:**存放八种基本类型,对象引用,其中64位长度的long和double类型的数据会占用两个局部变量的空间,其余数据类型只占一个。局部变量表是在编译时完成分配的,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量是完全确定的,在运行期间不再改变。

3、本地方法栈:
与虚拟机栈基本类似,为Native方法(本地方法)服务。

4、堆:
也叫java堆,GC堆。是JVM中所管理的内存中最大的一块内存区域,是线程共享的,在JVM启动时创建。存放了对象的实例及数组(所有new的对象)。
这里写图片描述
Permanent Generation(方法区)主要用来放JVM自己的反射对象,比如类对象和方法对象等。
Heap = {Old + NEW = { Eden , from, to } }
JVM内存模型中分两大块,一块是 NEW Generation(新生代), 另一块是Old Generation(老年代). 在New Generation中,有一个叫Eden(伊甸园)的空间,主要是用来存放新生的对象,还有两个Survivor Spaces(from,to), 它们用来存放每次垃圾回收后存活下来的对象。在Old Generation中,主要存放应用程序中生命周期长的内存对象

5、程序计数器
是最小的一块内存,它的作用是当前线程所执行的字节码的行号指示器,在虚拟机的模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,异常处理,线程恢复等基础功能都需要依赖计数器完成。

6、直接内存:
直接内存并不是虚拟机内存中的一部分,也不是JVM规范中定义的内存区域。jdk1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,即本机内存,不会影响到对内存的大小。

二、垃圾收集机制
这里写图片描述
1、垃圾回收算法
1.1、Mark-Sweep(标记-清除)算法
是最基础的垃圾回收算法,其他算法都是基于这种思想。标记-清除算法分为“标记”,“清除”两个阶段:首先标记出需要回收的对象,标记完成后统一清除对象。
缺点:1:标记和清除的效率不高。
2:标记之后会产生大量不连续的内存碎片。
1.2、Copying(复制)算法
将可用内存分为两块,每次只用其中的一块,当这块内存用完以后,将还存活的对象复制到另一块上面,然后再把已经使用的内存空间一次清理掉。
优点:1:不会产生内存碎片。
2:只要移动堆顶的指针,按顺序分配内存即可,实现简单,运行高效。
缺点:内存缩小为原来的一半
1.3、Mark-Compact(标记-整理)算法
标记操作和”标记-清除“算法一样,后续操作变成不直接清理对象,而是在清理无用对象的时候完成让所有存活的对象都像一端移动,并更新对象的指针。
优点:不会产生内存碎片
缺点:在“标记-清除”基础上还要进行对象的移动,成本相对较高
1.4、Generational Collection(分代收集)算法(重点)
是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。
而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。
注意,在堆区之外还有一个代就是永久代(Permanet Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
2、垃圾收集器
垃圾收集算法是 内存回收的理论基础,而垃圾收集器就是内存回收的具体实现。
2.1、Parallel Scavenge
Parallel Scavenge收集器是一个新生代的多线程收集器(并行收集器),它在回收期间不需要暂停其他用户线程,其采用的是Copying算法,它主要是为了达到一个可控的吞吐量。
2.2、Parallel Old
Parallel Old是Parallel Scavenge收集器的老年代版本(并行收集器),使用多线程和Mark-Compact算法。
2.3、CMS
CMS(Current Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,它是一种并发收集器,采用的是Mark-Sweep算法。
2.4、G1
G1收集器是当今收集器技术发展最前沿的成果,它是一款面向服务端应用的收集器,它能充分利用多CPU、多核环境。因此它是一款并行与并发收集器,并且它能建立可预测的停顿时间模型。
3、判断是否回收的算法
1.引用计数器(jvm未使用)
2,可达性分析(根节点搜索)
这里写图片描述
三、对象内存的分配与回收
1、分配
1.1、大部分对象在分配时都是在Eden中
1.2、较大的对象直接分配到Old Generation中
2、回收(GC)
2.1、新生代GC(Minor GC):
发生在新生代的垃圾回收动作,因为大多数对象都是朝生暮死的,所以Minor GC非常频繁,回收速度也比较快。
2.2、老年代GC(Major GC/Full GC)
发生在老年代的GC,发生Full GC时,一般会伴随着一次Minor GC,Full GC的速度比较一般会比Minor GC慢10倍以上。

四、对象的访问和对象引用强度
1、对象的访问
对象的访问涉及到java栈,java堆,方法区三个内存区域。
1.1、句柄访问方式:
java堆中将划分出一块内存来作为句柄池,reference中存储的就是对象的句柄,而句柄中包含了对象的实例数据(实例)和类型数据(class信息)各自的具体地址信息。
这里写图片描述
1.2、指针访问方式
reference变量中直接存储的就是对象的地址,而java堆对象一部分存储了对象实例数据,另外一部分存储了对象类型数据的地址。
这里写图片描述
1.3、两种方式的对比
使用句柄访问方式最大好处就是reference中存储的是稳定的句柄地址,在对象移动时只需要改变句柄中的实例数据指针,而reference不需要改变。
使用指针访问方式最大好处就是速度快,它节省了一次指针定位的时间开销,就虚拟机而言,它使用的是第二种方式(直接指针访问)
2、对象的引用强度
2.1、强引用:
就是指在代码之中普遍存在的,类似:“Object objectRef = new Obejct”,这种 引用,只要强引用还存在,永远不会被GC清理。
2.2、软引用:
用来描述一些还有用,但并非必须存在的对象,当Jvm内存不足时(内存溢出之前) 会被回收,如果执行GC后,还是没有足够的空间,才会抛出内存溢出异常。
通过SoftReference类来实现软引用,SoftReference很适合用于实现缓存。另,当GC认为扫描的SoftReference不经常使用时,可会进行回收。可用softReference.get()获取
2.3、弱引用
弱引用也是用来描述一些还有用,但并非必须存在的对象,它的强度会被软引用弱 些,被弱引用关联的对象,只能生存到下一次GC前,当GC工作时,无论内存是否足够, 都会回收掉弱引用关联的对象。JDK通过WeakReference类来实现。
当获取时,可通过weakReference.get方法获取,可能返回null。
可传入一个ReferenceQueue对象到WeakReference构造,当引用对象被表示为可回收 时,isEnqueued返回true
2.4、虚引用
虚引用称为“幻影引用”,它是最弱的一种引用关系,一个对象是否有虚引用的存 在,完全不会对生存时间构成影响。为一个对象设置虚引用关联的唯一目的就是希望能 在这个对象被GC回收时收到一个系统通知。
可以通过PhantomReference类实现。值得注意的是:phantomReference.get方法永远返回null, 当user从内存中删除时,调用isEnqueued会返回true
五、内存调优****重点内容
原因:过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量
目的:减少Full GC次数,减少GC频率,尽量降低CG所导致的应用线程暂停时间。
手段:主要是针对内存管理方面的调优,包括控制各个代的大小,GC策略。
内存控制:
1、导致Full GC的原因
1.1、旧生代空间不足
调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要 创建过大的对象及数组避免直接在旧生代创建对象
1.2、Pemanet Generation空间不足
增大Perm Gen空间,避免太多静态对象 。
1.3、统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间
控制好新生代和旧生代的比例
1.4、System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠JVM自身的机制
2、控制堆内存的各个部分的比例和GC策略失调
2.1、新生代设置过小
一是新生代GC次数非常频繁,增大系统消耗;二是导致大对象直接进入旧生代, 占据了旧生代剩余空间,诱发Full GC
2.2、新生代设置过大
一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发Full GC;二是 新生代GC耗时大幅度增加。一般说来新生代占整个堆1/3比较合适
2.3、Survivor设置过小Survivor设置过小
导致对象从eden直接到达旧生代,降低了在新生代的存活时间
2.4、Survivor设置过大
导致eden过小,增加了GC频率
2.5、新生代存活时间太少
通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收
GC策略
1、吞吐量优先
JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置
2、暂停时间优先
JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置
JVM常见配置

1、堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:设置年轻代大小
-XX:NewRatio=n:设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:	3,年轻代占整个年轻代年老代和的1/4
-XX:SurvivorRatio=n:年轻代中Eden区与两个Survivor区的比值。注意Survivor区	有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5
-XX:MaxPermSize=n:设置持久代大小

2、收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行年老代收集器

3、垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename

4、并行收集器设置
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间

5、并发收集器设置
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器年轻代收集方式为并行收集时,使用的CPU	数。并行收集线程数。
注意:在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

六、JDK可视化监控工具
JConsole 在bin目录下,自动后自动搜索jvm进程,不需要指定。
有概述,内存,线程,类,VM摘要和Mbean六个页签。
概述:显示有关JVM的监测值
.
内存: 显示内存使用信息

注意垃圾回收次数、时间、以及partial GC和full GC

线程: 显示线程使用信息

类: 显示类装载信息

VM摘要:显示java VM信息

MBeans: 显示 MBeans.

七、类加载机制
1.类加载的过程
这里写图片描述
1.1、加载:查找并加载类的二进制数据
1.2、验证:确保被加载的类的正确性
1.3、准备:为类的静态变量分配内存,并将其初始化为默认值
1.4、解析:把类中的符号引用转换为直接引用
1.5、初始化,为类的静态变量赋予正确的初始值
1.6、使用:主动使用和被动使用
1.7、卸载:满足条件后,被GC回收
2、ClassLoader的加载原理
ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),当父亲已经加载了该类的时候,子ClassLoader不再加载,避免了重复加载。
双亲委派模型的工作工程:当一个类加载器收到一个类加载请求时,它首先不会先去加载这个类,而是把这个请求委托给父加载器,每一层的加载器都是如此,所以最终所有的加载请求都会传送到最上层的启动加载器。只有当父加载器反馈自己无法完成加载请求(它管理的范围之中没有这个类),子加载器才会尝试自己去加载。
这里写图片描述

最后补充一句很重要的知识点:当你的jvm出现问题时,你怎么定位到错误的起源:
通过jstack可以找到,具体怎么实现的,网上有很多,我就不说了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值