JAVA 之【JVM性能调优篇】

学海无涯 

如存在问题 或有更好建议 请联系 作者QQ:2940500

转载注明出处: CSDN 昵称:QSSSYH 链接:https://blog.csdn.net/weixin_42059737/article/details/87817351

==============================================================================

JVM:

Java虚拟机,java一次编译到处执行特性的支撑 (请忽略这段 废话)

JVM 分区:

1.堆

2.栈

3.本地方法栈

4.方法区

5.Pc寄存器

类加载器: 4个

 注意ClassLoader只负责class文件的加载 至于它是否可以运行 则是由Execution Engine负责

1.BootstarpClassLoader --------------加载的是/jre/lib/rt.jar

2.ExtensioinClassLoader----------------/jre/lib/ext/*.jar

3.ApplicationClassLoader-------------加载的是开发人员写的java文件

4.自定义ClassLoader(继承ClassLoade类来自定义ClassLoader)

 类加载的机制是双亲委派机制 沙箱原理(相互之间是隔开的) 

双亲委派机制原理:

当要加载一个类的时候 是交给ApplicationClassLoader来加载

而App类加载器得到请求后不会直接去加载而是向上交给ExtensioinClassLoader,

ExtensioinClassLoader得到请求后也不会直接去加载  而是再向上交给BootstarpClassLoader进行加

BootstarpClassLoader加载失败就会交给ExtensioinClassLoader

ExtensioinClassLoader加载失败 再让 ApplicationClassLoader加载

ApplicationClassLoader加载失败 再交给自定义类加载器加载

这个时候如果自定义类加载器还加载失败 就会抛出ClassNotFoundException异常  

我们是否可以自己写一个java.lang.String ?

答: 不可以

比如下图我们去自己写一个java.lang.String

由于双亲委派机制在调用用BootstarpClassLoader时会自动加载 rt.jar   而rt.jar里边已经有java.lang.String 这个类 所以就不会层层抛出异常 让ApplicationClassLoader 加载 java.lang.String  就导致说在String下找不到main方法(简而言之 控制台输出的java.lang.String是jdk自带的String 并非自己写的String方法)

这是网上找到的一张图  和我说的也是差不多 个人语言表达能力差 我表达有偏差的可以看图

Pc寄存器

主要注意的几点:

1 是线程私有的每个线程都有一个Pc寄存器

2. Pc寄存器是用来存储方法与方法之间的调用关系的 比如A方法调用了B方法 就会在pc寄存器的A方法里边保存B方法的地址 如果B方法调用了C方法  也会在B方法里保存C方法的地址  以此类推 同时也可以把Pc寄存器看做为指针

栈:

栈是有内存限制的!

栈顶(栈开口)   栈底(栈闭口)

栈里保存的内容:

1.八大基本类型的值都是存在栈里边 

2.对象(引用类型)的引用也是存在栈里边  栈的特点:先进后出 后进先出  分别称作为:进栈(压栈)和出栈(弹栈)

 3.方法

在Java中有一个StackOverflowError的异常 这个异常是栈溢出异常  可以通过方法的向未知方向的递归查看这个异常,同时也可以看出我们的方法也是保存在栈内存中的

本地方法栈 

本地方法栈 又称作为JNI(java native interface)

存储的是native修饰的方法接口 

众所周知java是不能直接调用底层硬件的 要对硬件信息的一些操作就要使用native 修饰 然后由c / c++来实现这个接口(举例:请查看JDK源代码:线程 , 获取时间等等底层相关代码);

方法区(又称为元结构信息)

 注意! 注意!! 注意!!!     方法区是线程共享的! 

1存放的是一些类的信息  

2.final修饰的常量 ,属性,和方法信息

3.静态变量 

堆  

 首先上一张 介绍堆内存必须有的图

堆内存 分为三个大部分

新生代 老年代 持久代 

新生代 又分为伊甸园区和S区

举个例子  当我们去new 一个对象的时候 这个对象的存储位置是:

JVM--堆内存中--新生代区--伊甸园区(但是如果创建过大的对象也有可能直接跳过伊甸园区被分配到S区或直接老年代区)

老年代 当对象达到一定GC次数还没有被释放 就会被放到老年代   (一般最多不超过15次)

存到老年代的对象一般由Full Gc进行回收

永久代

Jdk1.7 称作永久代 Jdk1.8之后称为元空间

Permanet Space存储的是已加载的类信息,方法信息,常量池等。Permanet Space 并不等同于方法区,只不过是 Hotspot JVM(jvm内存管理工具) 用Permanet Space来实现方法区而已,有些虚拟机没有 Permanet Space而用其他机制来实现方法区。

 Jdk1.8之后 元空间逻辑上属于堆内存 元空间的大小和物理内存大小有关 很少会出现OOM异常  但是注意如果元空间真的占用内存太大  就有可能会被操作系统的守护进程给杀死关闭掉   

对象在堆内存中 被分配区域的时机 

在了解对象所在区域变动时机之前   我们要先了解: GC 和Full GC

GC垃圾回收器是Java中的'的一个很重要的机制(垃圾回收机制)  Java和C语言有一个很大的区别就是Java程序员不需要去手动回收垃圾释放内存空间(不是不能)  C语言程序员需要手动释放内存空间 

GC一般用来回收新生代空间内存垃圾

Full GC 它的每次调用都会对整个堆内存进行整理 会占用非常多的系统资源 

对象在堆内存各个区域的变动时机:文笔比较差 这一段话正好网上有一段清晰描述的比较 引用一下

1. 创建新的对象

每当我们使用new创建一个对象时, 这个对象会被分配到新生代Eden区域:

2. 当Eden区域满时

当Eden区域内存被分配完时, 小GC程序被触发:

引用可达的对象会移到Survivor(幸存者)区域–S0, 然后清空Eden区域, 此时引用不可达的对象会直接删除, 内存回收, 如下:

3. Eden再次满时

当Eden区域再次分配完后, 小GC执行, 引用可达的对象会移到Survivor(幸存者)区域, 而引用不可达的对象会跟随Eden的清空而删除回收.

需要注意的是, 这次引用可达的对象移动到的是S1的幸存者区.

而且, S0区域也会执行小GC, 将其中还引用可达的对象移动到S1区, 且年龄+1. 然后清空S0, 回收其中引用不可达的对象.

此时, 所有引用可达的对象都在S1区, 且S1区的对象存在不同的年龄. 如下:

当Eden第三次满时, S0和S1的角色互换了:

依此循环.

4. 当Survivor区的对象年龄达到”老年线”时

上面1~3循环, Survivor区的对象年龄也会持续增长, 当其中某些对象年龄达到”老年线”, 例如8岁时, 它们会”晋升”到老年区.

如此1~4步重复, 大体流程是这样的

JVM 内存调优
首先需要注意的是在对 JVM 内存调优的时候不能只看操作系统级别 Java 进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为 GC 过后这个值是不会变化的,因此内存调优的时候要更多地使用 JDK 提供的内存查看工具,比如 JConsole 和 JavVisualVM。
对 JVM 内存的系统级的调优主要的目的是减少 GC 的频率和 Ful GC 的次数,过多的GC 和 Ful GC 是会占用很多的系统资源(主要是 CPU),影响系统的吞吐量。特别要关注 Ful GC,因为它会对整个堆进行整理,导致 Ful GC 一般由于以下几种情况:
1.旧生代空间不足
2.调优时尽量让对象在新生代 GC 时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象
3.Pemanet Genration 空间不足
4.增大 Perm Gen 空间,避免太多静态对象
5.统计得到的 GC 后晋升到旧生代的平均大小大于旧生代剩余空间
6.控制好新生代和旧生代的比例    

一般说来新生代占整个堆 1/3 比较合适

System.gc()被显示调用
垃圾回收不要手动触发,尽量依靠 JVM 自身的机制
调优手段主要是通过控制堆内存的各个部分的比例和 GC 策略来实现,下面来看各部分比例不良设置会导致什么后果
1)新生代设置过小
一是新生代 GC 次数非常频繁,增大系统消耗;二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发 Ful GC
2)新生代设置过大
一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发 Ful GC;

二是新生代 GC 耗时大幅度增加

3)Survior 设置过小
导致对象从 edn 直接到达旧生代,降低了在新生代的存活时间
4)Survior 设置过大导致 edn 过小,增加了 GC 频率

另外,通过-X:MaxTenuringThreshold=n 来控制新生代存活时间,尽量让对象在新生代被回收
由内存管理和垃圾回收可知新生代和旧生代都有多种 GC 策略和组合搭配,选择这些策略对于我们这些开发人员是个难题,JVM 提供两种较为简单的 GC 策略的设置方式
1)吞吐量优先
JVM 以吞吐量为指标,自行选择相应的 GC 策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-X:GCTimeRatio=n 来设置
2)暂停时间优先
JVM 以暂停时间为指标,自行选择相应的 GC 策略及控制新生代与旧生代的大小比例,尽量保证每次 GC 造成的应用停止时间都在指定的数值范围内完成。这个值可由-X:MaxGCP
auseRatio=n 来设置

JVM默认为物理内存的64分之1  

调优的目的:

降低GC 和Full GC的频率   每次GC都会占用很多资源 尤其Full GC 它的每次调用都会对整个堆内存进行整理 会占用非常多的系统资源 

Java代码查看堆内存空间:

Runtime.getRunTime.totalMemory()/1024/1024; 堆内存初始值    这样输出的内存大小单位为MB  

Runtime.getRunTime.maxMemory()/1024/1024;堆内存最大值  这样输出的内存大小单位为MB 

如果出现java.lang.OutOfMemoryError异常 说明Java虚拟机的堆内存不足

可以通过-Xms -Xmx来调整 

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


 

猜你喜欢

转载自blog.csdn.net/weixin_42059737/article/details/87817351