0
点赞
收藏
分享

微信扫一扫

JVM------探索内部机理

waaagh 2022-02-24 阅读 70
java后端

一.JVM内存模型

 

JVM内存模型包括:类加载子系统,运行时数据,执行引擎。JVM主要分析运行时数据,它包括方法区,堆,虚拟机栈,程序计数器,本地方法栈。

程序计数器(线程私有):

作用:记录下一条jvm指令的执行地址。程序计数器简单来说就是来给我们记录我们程序执行到第几行。

特点:它是线程私有的;不会出现内存溢出

虚拟机栈(线程私有):

每个线程运行时所需要的内存,称为虚拟机栈

每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存

每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

cpu过高时

1.用top定位哪个进程对cpu的占用过高

2.ps H -eo pid,tid,%cpu | grep 进程id (用ps命令进一步定位是哪个线程引起的cpu占用过高) 3.jstack 进程id 可以根据线程id 找到有问题的线程,进一步定位到问题代码的源码行号

本地方法栈(线程私有):和虚拟机栈类似,但是它的对象是本地方法,也是线程私有的

堆(线程共享):

通过 new 关键字,创建对象都会使用堆内存

特点:它是线程共享的,堆中对象都需要考虑线程安全的问题 有垃圾回收机制

堆内存溢出诊断:

1. jps 工具 查看当前系统中有哪些 java 进程

2. jmap 工具 查看堆内存占用情况 jmap - heap 进程id

3. jconsole 工具 图形界面的,多功能的监测工具,可以连续监测

方法区:jdk 1.6方法区通过永久代实现,由常量池,class,classloader组成

常用参数:

-XX:PermSize=N //⽅法区 (永久代) 初始⼤⼩
-XX:MaxPermSize=N //⽅法区 (永久代) 最⼤⼤⼩,超过这个值将会抛出 OutOfMemoryError 异
常:java.lang.OutOfMemoryError: PermGen

jdk 1.8方法区由元空间实现,存放在本地内存,由常量池,class,classloader组成

常用参数:

-XX:MetaspaceSize=N //设置 Metaspace 的初始(和最⼩⼤⼩)
-XX:MaxMetaspaceSize=N //设置 Metaspace 的最⼤⼤⼩
当你元空间溢出时会得到如下错误: java.lang.OutOfMemoryError: MetaSpace

为什么要将永久代 (PermGen) 替换为元空间 (MetaSpace) 呢?

1)整个永久代有⼀个 JVM 本身设置固定⼤小上限,无法进进调整,而元空间使用的是直接内存,受本机可用内存的限制,虽然元空间仍旧可能溢出,但是比原来出现的几率会更小

2)元空间⾥⾯存放的是类的元数据,这样加载多少类的元数据就不由 MaxPermSize 控制了, ⽽由系统的实际可⽤空间来控制,这样能加载的类就更多了

解释器:解释器是用来执行代码的,但是它不能直接执行,首先它要将二进制字节码指令给解释器,然后解释器转化成字节码文件,最后交给cpu执行。(二进制字节码指令--->解释器--->机器码--->cpu)

即时编译器:用来执行一些热点代码

二.说⼀下Java对象的创建过程?

. Step1:类加载检查

虚拟机遇到⼀条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

Step2:分配内存

在类加载检查通过后,接下来虚拟机将为新⽣对象分配内存。对象所需的内存⼤⼩在类加载完成 后便可确定,为对象分配空间的任务等同于把⼀块确定⼤⼩的内存从 Java 堆中划分出来。分配 ⽅式有 “指针碰撞” 和 “空闲列表” 两种,选择哪种分配⽅式由 Java 堆是否规整决定,⽽ Java 堆是否规整⼜由所采⽤的垃圾收集器是否带有压缩整理功能决定。

内存分配的两种方式:

Step3:初始化零值

内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这⼀步操 作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。 

Step4:设置对象头

初始化零值完成之后,虚拟机要对对象进⾏必要的设置,例如这个对象是哪个类的实例、如何才 能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头 中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

. Step5:执行init方法

在上面工作都完成之后,从虚拟机的视角来看,⼀个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以⼀般来说, 执行new 指令之后会接着执行方法,把对象按照程序员的意愿进行初始化,这样⼀个真 正可用的对象才算完全产生出来。

三.垃圾回收(判断GCroot的算法)

1)引用计数法:python中使用,有循环引用的问题

2)可达性分析算法;以GCroot对象为起点向下搜索,如果在这条链上则不能被回收,否则要被回收

哪些可以作为GCroot?

1,虚拟机栈中引用的对象(栈帧中的本地方法表)。

2,方法区中(1.8称为元空间)的类静态属性引用的对象,一般指被static修饰的对象,加载类的时候就加载到内存中。

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

4,本地方法栈中的JNI(native方法)引用的对象

四.垃圾回收算法

1)标记清除:速度快,但是会造成内存碎片

图中看出被GC Root引用的对象不能被回收(紫色部分),需要回收的是没有被GC Root引用的对象(灰色部分),这些垃圾会被标记,然后被清理,但是内存空间仍然在,只是把地址回收到一张表中下一次使用可查。

2)标记整理:速度慢,但是没有内存碎片

这种情况是存活较多的情况,把垃圾标记,然后在清理过程中会对存活的对象进行整理成连续的内存。

3)复制:需要占用双倍的内存空间,没有内存碎片

这种情况为存活较少的情况,清理时会把存活的对象从FROM复制到TO,然后进行垃圾回收,最后FROM和TO再次交换,完成复制。

五.分代回收(新生代采用复制算法,老年代采用标记整理算法)

1)对象首先分配在伊甸园区域

2)新生代空间不足时,触发 minor gc,伊甸园和 from 存活的对象使用 copy 复制到 to 中,存活的对象年龄加 1并且交换 from to

3)minor gc 会引发 stop the world,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行

4)当对象寿命超过阈值时,会晋升至老年代,最大寿命是15(4bit)

5)当老年代空间不足,会先尝试触发 minor gc,如果之后空间仍不足,那么触发 full gc,STW的时 间更长

六.说⼀下堆内存中对象的分配的基本策略

 1)对象优先在 eden 区分配

大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起⼀次 Minor GC.下面我们来进行实际测试以下。

2)大对象直接进入老年代

3)躲过15次GC之后进入老年代,可通过JVM参数“-XX:MaxTenuringThreshold”来设
置年龄,默认为15岁(长期存活的对象将进⼊老年代

15岁只是默认值,可以通过动态年龄判断来进行分配

动态年龄判断: Survivor区的对象年龄从小到大进行累加,当累加到X年龄时的总和大于50%(可以使用-XX:TargetSurvivorRatio=?来设置保留多少空闲空间,默认值是50),那么比X大的都会晋升到老年代;

 

 

 

举报

相关推荐

0 条评论