Java基础学习——Jvm知识

1.JVM的位置

在这里插入图片描述

2.JVM体系结构

在这里插入图片描述
完整版:在这里插入图片描述

3.类加载器

作用:加载Class文件 new Student();
在这里插入图片描述

  1. 虚拟机自带的加载器
  2. 启动类(根)加载器(无法被用户获得通过用getParent 会显示null
  3. 扩展类加载器
  4. 应用程序加载器
public class Person {
    public static void main(String[] args) {
        //new在栈中 实例在堆中
        Person person1 = new Person();
        Person person2 = new Person();
        Person person3 = new Person();
        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
        System.out.println(person3.hashCode());

        Class<? extends Person> aClass = person1.getClass();
        System.out.println(aClass);

        ClassLoader classLoader = aClass.getClassLoader();
        //应用程序加载器 
        System.out.println(classLoader);
        ClassLoader parent = classLoader.getParent();
        //扩展类加载器  \jre\lib\ext.jar
        System.out.println(parent);
        //根加载器无法显示 rt.jar
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
    }
}

输出:
在这里插入图片描述

双亲委派机制

在这里插入图片描述
当一个例如我们缩写的Person.Clss这样一个类要被加载的时候,jvm不会先去加载我们定义的类,而是先检查AppClassLoader是否加载过,如果没有加载过,在询问上层的ExtrClassLoader是否加载过,如果没有加载过,询问上层的BootStrapClassLoader因为已经是跟加载器,所以如果未被加载就考虑自己是否能加载,如果不能就下沉的子加载中去,直到底层,如果都不能加载就抛出ClassNotFoundException异常。

双亲委派机制的好处:

  1. 可以保证安全,防止自己写的同名的类加载
  2. 减少子类加载器的多次加载,减少资源浪费

沙箱安全机制

将java代码限定在jvm特定的允许范围中,并且严格限制代码对本地系统资源的访问。

类加载器的生命周期

  1. 加载:将.class文件从磁盘读到内存

  2. 链接:
    验证:验证字节码文件的正确性
    准备:给类的静态变量分配内存,并赋予默认值
    解析:类装载器装入类所引用的其他所有类

  3. 初始化:为类的静态变量赋予正确的初始化值,执行静态代码块

  4. 使用

  5. 卸载

4.Native方法

public class Native {
    public static void main(String[] args) {

    }


    /**
     * native
     * 凡是带了native关键字的,说明java的作用范围打不到了,回去调底层c语言的库
     * 会进入本地方法栈
     * 调用本地方法本地接口 JNI
     * JNI作用:扩展java 的使用,融合不同的编程语言为java所用 最初:C、C++
     * 在内存区域中专门开辟一块标记区域:Native Method Stack 登记 Native方法
     * 在最终执行的时候,加载本地方法库中的方法通过JNI
     * 
    * */
    private native void start0();
}

5.PC寄存器

6.方法区

被所有线程共享,所有字段和方法字节码,以及一些特殊方法,如构造函数,接口代码也在此定义,简单说,所有定义的方法的信息都保存在该区域,此区域属于共享区间
静态变量、常量、类信息(构造方法、接口定义)、运行时的常量池存在方法区中,但是 实例变量存在堆内存中和方法区无关
Method Area 方法区是Java虚拟机规范 中定义的运行时数据区域之一,它与堆(heap)一样在线程之间共享。Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
JDK7 之前(永久代)用于存储已被虚拟机加载的类信息、常量、字符串常量、类静态变量、即时编译器编译后的代码等数据。每当一个类初次被加的时候,它的元数据都会被放到永久代中。永久代大小有限制,如果加载的类太多,很可能导致永久代内存溢出,即 java.lang.OutOfMemoryError:PermGen。JDK8 彻底将永久代移除出 HotSpot JVM,将其原有的数据迁移至 Java Heap 或 NativeHeap(Metaspace),取代它的是另一个内存区域被称为元空间(Metaspace)
元空间(Metaspace):元空间是方法区的在 HotSpot JVM 中的实现,方法区主要用于存储类信息、常量池、方法数据、方法代码、符号引用等。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。
不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。可以通过 -XX:MetaspaceSize-XX:MaxMetaspaceSize 配置内存大小。如果Metaspace的空间占用达到了设定的最大值,那么就会触发GC来收集死亡对象和类的加载器

7.栈

Java栈的组成元素—栈帧
栈帧是一种用于帮助虚拟机执行方法调用与方法执行的数据结构。他是独立于线程的,一个线程有自己的一个栈帧。封装了方法的局部变量表、动态链接信息、方法的返回地址以及操作数栈等信息。

第一个方法从调用开始到执行完成,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

当一个方法A被调用时就产生了一个栈帧F1,并被压入到栈中,A方法又调用了B方法,于是产生了栈帧F2也被压入栈中,B方法又调用了C方法,于是产生栈帧F3也被压入栈中…执行完毕后,先弹出F3,然后弹出F2,在弹出F1…
遵循 “先进后出” / “后进先出” 的原则。
在这里插入图片描述
栈+堆+方法区:交互关系
在这里插入图片描述

8.HotSpot和堆

一个jvm只有一个堆内存,堆内存的大小是可以调节的。
类加载器读取了类文件后,一般会把什么东西放到类中?类,方法,常量,变量~保存我们所有引用类型的真实对象。
堆内存还细分为三个区域:

  • 新生区(伊甸园区)
  • 养老区
  • 永久区
    在这里插入图片描述
    在这里插入图片描述
    GC垃圾回收主要是在 新生区和养老区,又分为 轻GC 和 重GC,如果内存不够,或者存在死循环,就会
    导致 java.lang.OutOfMemoryError: Java heap space
    jdk8以后永久存储区改名叫元空间

新生区

  • 类:诞生和成长的地方,甚至死亡
  • 伊甸园,所有对象都是在伊甸园区中new出来的
  • 幸存者区(0,1)

永久区

常驻内存的,用来存放JDK自身携带的Class对象。Interface元数据,存储的是java运行时的一些环境
一个启动类,加载了大量的第三方jar包,Tomcat部署过多的应用,大量动态生成的反射类不断被加载。知道内存满会产生OOM.
Jdk1.6之前: 有永久代,常量池1.6在方法区
Jdk1.7: 有永久代,但是已经逐步 “去永久代”,常量池1.7在堆
Jdk1.8及之后:无永久代,常量池1.8在元空间

在这里插入图片描述
默认情况下:分配的总内存是电脑内存的1/4,而初始化内存是:1/64
OOM解决:

-Xms1024m -Xms1024m -XX:+PrintGCDetails

  1. 尝试夸大内存看结果
  2. 分析内存,看一下哪个地方出错

9.GC

JVM在进行GC,并不是对三个区域统一回收,大部分时候,回收都是新生代

  1. 伊甸区
  2. 幸存区(from,to)
  3. 老年区

两种GC:轻GC(Scavenge GC),重GC(Full GC)

9.1轻GC(Scavenge GC)

轻GC(Scavenge GC):一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

9.2重GC(Full GC)

对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于Full GC的调节。有如下原因可能导致Full GC:

  1. 年老代(Tenured)被写满;
  2. 持久代(Perm)被写满;
  3. System.gc()被显示调用;
  4. 上一次GC之后Heap的各域分配策略动态变化;

10.GC常用算法

复制算法

在这里插入图片描述

Minor GC 会把Eden中的所有活的对象都移到Survivor区域中,如果Survivor区中放不下,那么剩下的活的对象就被移动到Old generation中,也就是说,一旦收集后,Eden就是变成空的了
当对象在Eden(包括一个Survivor区域,这里假设是From区域)出生后,在经过一次Minor GC后,如果对象还存活,并且能够被另外一块Survivor区域所容纳 (上面已经假设为from区域,这里应为to区域,即to区域有足够的内存空间来存储Eden 和 From 区域中存活的对象),则使用复制算法将这些仍然还活着的对象复制到另外一块Survivor区域(即 to 区域)中,然后清理所使用过的Eden 以及Survivor区域(即form区域),并且将这些对象的年龄设置为1,以后对象在Survivor区,每熬过一次MinorGC,就将这个对象的年龄 + 1,当这个对象的年龄达到某一个值的时候(默认是15岁,通过- XX:MaxTenuringThreshold 设定参数)这些对象就会成为老年代。
-XX:MaxTenuringThreshold默认为15

原理解释
年轻代中的GC,主要是复制算法(Copying)
HotSpot JVM 把年轻代分为了三部分:一个 Eden 区 和 2 个Survivor区(from区 和 to区)。默认比例为 8:1:1,一般情况下,新创建的对象都会被分配到Eden区(一些大对象特殊处理),这些对象经过第一次Minor GC后,如果仍然存活,将会被移到Survivor区,对象在Survivor中每熬过一次Minor GC ,年龄就会增加1岁,当它的年龄增加到一定程度时,就会被移动到年老代中,因为年轻代中的对象基本上都是朝生夕死,所以在年轻代的垃圾回收算法使用的是复制算法!复制算法的思想就是将内存分为两块,每次只用其中一块,当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片!
在这里插入图片描述
在GC开始的时候,对象只会在Eden区和名为 “From” 的Survivor区,Survivor区“TO” 是空的,紧接着进行GC,Eden区中所有存活的对象都会被复制到 “To” , 而在 “From” 区中,仍存活的对象会根据他们的年龄值来决定去向。年龄达到一定值 的对象会被移动到老年代中,没有达到阈值的对象会被复制到 “To区域”,经过这次GC后,Eden区和From区已经被清空,这个时候, “From” 和 “To” 会交换他们的角色,也就是新的 “To“ 就是GC前的”From“ , 新的 ”From“ 就是上次GC前的 ”To“。不管怎样,都会保证名为To 的Survicor区域是空的。 Minor GC会一直重复这样的过程。直到 To 区 被填满 , ”To “ 区被填满之后,会将所有的对象移动到老年代中。

因为Eden区对象一般存活率较低,一般的,使用两块10%的内存作为空闲和活动区域,而另外80%的内存,则是用来给新建对象分配内存的。一旦发生GC,将10%的from活动区间与另外80%中存活的Eden对象转移到10%的to空闲区域,接下来,将之前的90%的内存,全部释放,以此类推;
好处:没有内存碎片,坏处:浪费内存空间

劣势:
复制算法它的缺点也是相当明显的。
1、他浪费了一半的内存,这太要命了
2、如果对象的存活率很高,我们可以极端一点,假设是100%存活,那么我们需要将所有对象都复制一遍,并将所有引用地址重置一遍。复制这一工作所花费的时间,在对象存活率达到一定程度时,将会变的不可忽视,所以从以上描述不难看出。复制算法要想使用,最起码对象的存活率要非常低才行,而且最重要的是,我们必须要克服50%的内存浪费。

引用计数器

每个对象有一个引用计数器,当对象被引用一次则计数器加1,当对象引用失效一次,则计数器减1,对于计数器为0的对象意味着是垃圾对象,可以被GC回收。
目前虚拟机基本都是采用可达性算法,从GC Roots 作为起点开始搜索,那么整个连通图中的对象边都是活对象,对于GC Roots 无法到达的对象变成了垃圾回收对象,随时可被GC回收。

标记清除法

说明:老年代一般是由标记清除或者是标记清除与标记整理的混合实现
回收时,对需要存活的对象进行标记;
回收不是绿色的对象

在这里插入图片描述

当堆中的有效内存空间被耗尽的时候,就会停止整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。
标记:从引用根节点开始标记所有被引用的对象,标记的过程其实就是遍历所有的GC Roots ,然后将所有GC Roots 可达的对象,标记为存活的对象。
清除: 遍历整个堆,把未标记的对象清除。

缺点:这个算法需要暂停整个应用,会产生内存碎片。

用通俗的话解释一下 标记/清除算法,就是当程序运行期间,若可以使用的内存被耗尽的时候,GC线程就会被触发并将程序暂停,随后将依旧存活的对象标记一遍,最终再将堆中所有没被标记的对象全部清除掉,接下来便让程序恢复运行。

劣势:

  1. 首先、它的缺点就是效率比较低(递归与全堆对象遍历),而且在进行GC的时候,需要停止应用程序,这会导致用户体验非常差劲
  2. 其次,有内存碎片主要的缺点则是这种方式清理出来的空闲内存是不连续的,这点不难理解,而为了应付这一点,JVM就不得不维持一个内存空间的空闲列表,这又是一种开销。而且在分配数组对象的时候,寻找连续的内存空间会不太好找。

标记压缩法

标记整理说明:老年代一般是由标记清除或者是标记清除与标记整理的混合实现
在这里插入图片描述
在整理压缩阶段,不再对标记的对象作回收,而是通过所有存活对象都像一端移动,然后直接清除边界以外的内存。可以看到,标记的存活对象将会被整理,按照内存地址依次排列,而未被标记的内存会被清理掉,如此一来,当我们需要给新对象分配内存时,JVM只需要持有一个内存的起始地址即可,这比维护一个空闲列表显然少了许多开销。标记、整理算法不仅可以弥补 标记、清除算法当中,内存区域分散的缺点,也消除了复制算法当中,内
存减半的高额代价;

标记清除压缩算法

在这里插入图片描述

总结

内存效率:复制算法 > 标记清除算法 > 标记整理算法 (时间复杂度)
内存整齐度:复制算法 = 标记整理算法 > 标记清除算法
内存利用率:标记整理算法 = 标记清除算法 > 复制算法

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值