JVM+GC

JVM+GC

1.JVM

JVM:
java Virtual Machine java 虚拟机

JDK:
Java Develpment Kit java 开发工具
JRE:
Java Runtime Environment java运行时环境

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XmWDriIN-1629621934552)(image/image.png)]

2.JVM中共享区,和gc root

1、堆区和⽅法区是所有线程共享的,栈、本地⽅法栈、程序计数器是每个线程独有的

2、什么是gc root,JVM在进⾏垃圾回收时,需要找到“垃圾”对象,也就是没有被引⽤的对象,但是直接找“垃圾”对象是⽐较耗时的,所以反过来,先找“⾮垃圾”对象,也就是正常对象,那么就需要从某些“根”开始去找,根据这些“根”的引⽤路径找到正常对象,⽽这些“根”有⼀个特征,就是它只会引⽤其他对象,⽽不会被其他对象引⽤,例如:栈中的本地变量、⽅法区中的静态变量、本地⽅法栈中的变量、正在运⾏的线程等可以作为gc root。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KRORMKrT-1629621934553)(image/image_1.png)]

3.GC如何判断对象可以被回收

引用计数法:

每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收

可达性分析法:

从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC
Roots 没有任何引用链相连时,则证明此对象是不可用的,那么虚拟机就判断是可回收对象。

引用计数法,可能会出现A 引用了 B,B 又引用了 A,这时候就算他们都不再使用了,但因为相互引用 计数器=1 永远无法被回收。

GC Roots的对象 有:

虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即一般说的Native方法)引用的对象

可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会。对象被系统宣告死亡至少要经历两次标记过程:第一次是经过可达性分析发现没有与GC Roots相连接的引用链,第二次是在由虚拟机自动建立的Finalizer队列中判断是否需要执行finalize()方法。
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象
的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”
每个对象只能触发一次finalize()方法
由于finalize()方法运行代价高昂,不确定性大,无法保证各个对象的调用顺序,不推荐大家使用,建议
遗忘它。

4.JAVA运行原理

首先源文件(java)通过编译器编译成class文件,class文件是字节码文件,然后在通过JVM中的解释器将字节码文件生成对应的可执行文件,通过jvm交给linux或windows等系统。所以java即时编译语言也是解释性语言。
详细的解释 :首先通过编译器编译,将源程序编译形成class文件,由于不同平台JVM提供相同接口,故即便是不同平台下将java编译成class文件,但通过相同接口的JVM进行解释,均可将该环境下的字节码解释形成该平台下的可执行的java文件,同时,由于不同操作系统的JVM提供的均相同接口,不同平台的编译器则只需要面对该JVM接口进行编译,这些都决定了java语言具有良好的跨平台性、移植性。

5.GC的分类

按照回收区域分为两种大类型:部分收集(Partial GC),一种是整堆收集(Full GC)

部分收集: 不是完整收集整个Java堆的垃圾收集。其中又分为:

  1. 新生代收集(Minor GC / Young GC):只是新生代(Eden\S0,S1)的垃圾收集

  2. 老年代收集(Major GC/ Old GC):只是老年代的垃圾收集

  3. 混合收集(Mixed GC):收集整个新生代及部分老年代的垃圾收集,目前只有G1 GC 有这种行为

整堆收集(Full GC ):收集整个java堆和方法区的垃圾收集。1.

目前只有CMS GC 会有单独收集老年代的行为,注意很多时候 Major GC会和FullGC 混淆使用,需要具体分辨是老年代回收还是整堆回收。

6.哪些情况会触发FULL GC

老年代空间不足;方法区空间不足;显示调用System.gc();Minor GC进入老年代的平均大小 大于老年代的可用内存;大对象直接进入老年代,而老年代的可用空间不足;

7.JVM 垃圾回收机制,GC发生在JVM哪部分,有几种GC,他们的算法是什么

JVM垃圾回收是发生在heap(堆)中

GC是什么(分代收集算法)

    次数上频繁收集Young区,Minor GC;次数上较少收集Old区 Full GC;

GC的算法主要有:

①引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

引用计数法的问题:引用和去引用伴随加法和减法,影响性能。 主流的java虚拟机并没有选用引用计数法来管理内存,就是因为它很难解决对象之间循环引用的问题。

②标记清除:标记清除算法是现代垃圾回收算法的思想基础,分两个阶段:标记阶段和清除阶段。在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。它的做法是当堆中的有效内存空间(available memory)被耗尽的时候,就会停止整个程序(也被成为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。

标记清除算法的缺点:首先,它的缺点就是效率比较低(递归与全堆对象遍历),导致停止程序的时间比较长;其次,则是这种方式清理出来的空闲内存是不连续的,这点不难理解,我们的死亡对象都是随即的出现在内存的各个角落的,现在把它们清除之后,内存的布局自然会乱七八糟。而为了应付这一点,JVM就不得不维持一个内存的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。

通俗来讲,就是会产生内存碎片,扫描两次,第一次标记,第二次清除

③标记压缩:标记-压缩算法适合用于存活对象较多的场合,如老年代。它在标记-清除算法的基础上做了一些优化。和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记;但之后,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端;之后,清理边界外所有的空间。标记:它的第一个阶段与标记/清除算法是一模一样的,均是遍历GC Roots,然后将存活的对象标记;整理:移动所有存活的对象,且按照内存地址次序依次排列,然后将末端内存地址以后的内存全部回收。因此,第二阶段才称为整理阶段。

标记压缩优点:标记/整理算法不仅可以弥补标记/清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内存减半的高额代价。

标记压缩缺点:效率也不高,不仅要标记所有存活对象,还要整理所有存活对象的引用地址。从效率上来说,标记/整理算法要低于复制算法。

④复制算法:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

    复制算法缺点:空间的浪费,复制算法使得每次都只对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。只是这种算法的代价是将内存缩小为原来的一半,这个太要命了。所以从以上描述不难看出,复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%内存的浪费。

    年轻代就使用的复制算法,回收较为频繁;老年代就使用的标记清除或者标记清除与标记压缩的混合实现。

    标记-清除-压缩,减少移动成本

8.JVM核心区域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HyYIKSLo-1629621934554)(image/image_2.png)]

package com.spark.jvm;
/**
* 从JVM调用的角度分析java程序堆内存空间的使用:
* 当JVM进程启动的时候,会从类加载路径中找到包含main方法的入口类HelloJVM
* 找到HelloJVM会直接读取该文件中的二进制数据,并且把该类的信息放到运行时的Method内存区域中。
* 然后会定位到HelloJVM中的main方法的字节码中,并开始执行Main方法中的指令
* 此时会创建Student实例对象,并且使用student来引用该对象(或者说给该对象命名),其内幕如下:
* 第一步:JVM会直接到Method区域中去查找Student类的信息,此时发现没有Student类,就通过类加载器加载该Student类文件;
* 第二步:在JVM的Method区域中加载并找到了Student类之后会在Heap区域中为Student实例对象分配内存,
* 并且在Student的实例对象中持有指向方法区域中的Student类的引用(内存地址);
* 第三步:JVM实例化完成后会在当前线程中为Stack中的reference建立实际的应用关系,此时会赋值给student
* 接下来就是调用方法
* 在JVM中方法的调用一定是属于线程的行为,也就是说方法调用本身会发生在线程的方法调用栈:
* 线程的方法调用栈(Method Stack Frames),每一个方法的调用就是方法调用栈中的一个Frame,
* 该Frame包含了方法的参数,局部变量,临时数据等 student.sayHello();
*/
public class HelloJVM {
//在JVM运行的时候会通过反射的方式到Method区域找到入口方法main
public static void main(String[] args) {//main方法也是放在Method方法区域中的
/**
* student(小写的)是放在主线程中的Stack区域中的
* Student对象实例是放在所有线程共享的Heap区域中的
*/
Student student = new Student("spark");
/**
* 首先会通过student指针(或句柄)(指针就直接指向堆中的对象,句柄表明有一个中间的,student指向句柄,句柄指向对象)
* 找Student对象,当找到该对象后会通过对象内部指向方法区域中的指针来调用具体的方法去执行任务
*/
student.sayHello();
}
}
 
class Student {
// name本身作为成员是放在stack区域的但是name指向的String对象是放在Heap中
private String name;
public Student(String name) {
this.name = name;
}
//sayHello这个方法是放在方法区中的
public void sayHello() {
System.out.println("Hello, this is " + this.name);
}
}

9.JVM三大性能调优参数:-Xms –Xmx –Xss

Xms –Xmx是对堆的性能调优参数,一般两个设置是一样的,如果不一样,当Heap不够用,会发生内存抖动。一般都调大这两个参数,并且两个大小一样。

-Xss是对每一个线程栈的性能调优参数,影响堆栈调用的深度

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值