JVM学习笔记

JVM

JDK, JRE , JVM:

JDK(Java Development Kit),包括了Java运行环境JRE,一堆Java工具(编译器javac/java/jdb等)和Java基础的类库(即Java API 和java运行时基本类库rt.jar(runtime jar) 。

其中rt.jar包含所有核心Java 运行环境的已编译calss文件,其中包含引导类(bootstrap classes,即来自Core Java API的所有类)。

JRE(Java Runtime Envirnment)又包含JVM(Java Virtual Machine)java虚拟机。

光有JVM还不能完成.class字节码文件的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib。 (jre里有运行.class的java.exe)。 所以JVM+lib=JRE。

总体来说就是,我们利用JDK(调用JAVA API)开发了属于我们自己的JAVA程序后,通过JDK中的编译程序(javac)将我们的文本java文件编译成JAVA字节码,在JRE上运行这些JAVA字节码,JVM解析这些字节码,映射到CPU指令集或OS的系统调用。

JVM内部架构:(runtime data area)(jdk8 Hotspot版)

在这里插入图片描述

runtime data area按属性可以分为以下两类:

  • 一类属于进程(生命周期与进程相同,线程共享),
  • 另一类属于线程(生命周期与线程相同,线程私有)。

属于进程的有堆(Heap)和方法区(Method Area)。

属于线程的有程序计数寄存器(Program Counter Register),Java栈(Java Virtual Machine Stack)以及本地方法栈(Native Method Stack)。

需要注意的是,堆中并不全是线程共享,本地线程分配缓存(简称TLAB 全写Thread Local Allocation Buffer)是线程私有的,只不过在默认情况下占堆的1%。

JVM栈:

栈解决程序的运行问题,描述的是 Java 方法执行的内存模型。每个方法在执行时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。

每一个方法从调用直至执行结束,就对应着一个栈帧从虚拟机栈中入栈到出栈的过程。

其中局部变量表:存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference 类型)和 returnAddress 类型(指向了一条字节码指令的地址)

PC程序计数器:

存储的是下一条指令的地址,执行引擎(Execution Engine)根据PC寄存器将指令转化为机器语言。对于每个线程来说,需要执行的下一条指令的地址是不同的,在CPU快速切换的过程中,线程需要保存下一条指令的位置以便于下次CPU再次切换回来时继续执行,因此PC寄存器需要线程私有。

本地方法区native method area:

被native修饰的方法会存放到本地方法区中,在运行时调用本地方法接口JNI(java native Interface)去调用其他语言(一般是C,C++)去实现。(现在一般很少用到,主要是前期java发展需要借助C以及C++)

JVM参数调优针对的是进程区域

Meta Space元空间:

在jdk8中,元空间开始替代了方法区Method Area及堆中的Perm Gen永久代。并且元空间并不在虚拟机中,而是使用本地内存。

元空间被所有线程共享,存放了方法的定义信息,包括静态常量池(*.class文件中的常量池,包含了类信息如构造方法,接口定义等)及运行时常量池(编译期间生成的字面量、符号引用)。

然而注意字符串常量池以及类的实例化对象是在堆heap中的。

Heap堆:

垃圾回收主要针对堆Heap。存放了字符串常量池,数组以及类的实例化对象。

GC:Gabage Collection

垃圾收集是针对堆的。主要有两种形式:手工、自动,垃圾回收可以有效的防止OOM,(out of memory)内存泄露,有效的使用可以使用的内存。

手工:
调用的是System类中的gc()方法,此方法实际上调用的是Runtime类中的gc()方法,当一个对象被回收之前将调用类中的finlalize()方法,此方法为 Object类所提供,表示对象回收前的收尾工作。即使出现了异常,也不影响程序的执行。但是Java语言规范并不保证手动GC一定会执行。

自动:
对于GC来说,当程序员创建对象时,GC就开始监控这个对象的地址、大小以及使用情况。有两种标记方法:
1)引用计数法(reference counting):给对象添加一个引用计数器,每当有地方引用它时,计数器加1;当引用失效时,计数器减1。引用计数法实现简单,判定效率高,但是它很难解决对象之间的互相或循环引用(引用环问题)的问题

2)所以通常使用第二种,可达性算法(根搜索算法GC roots)采用有向图的方式记录和管理堆(heap)中的所有对象。通过这种方式确定哪些对象是"可达的",哪些对象是"不可达的"。当GC确定一些对象为"不可达"时(比如一个对象没有指向它的引用或者其赋值为null),GC就有责任回收这些内存空间。垃圾回收机以低优先级运行

GC的四种方法:
标记-清除法(Mark-Sweep)
:在标记(可达性算法标记)完成后统一回收所有被标记的对象。它是最基础的算法,后续算法都是基于它的不足而改进,主要不足有:效率问题,标记和清除效率都不高;另外一个是空间问题,标记清除后会产生大量不连续的内存碎片

复制算法(copying):在新生代Young Gen中将内存划分为一块较大的Eden空间(所有新建对象都诞生在这)和两块较小的Survivor空间(一个命名为from,一个为to,会互相交换),每次只使用Eden和其中一块Survivor。当回收时,将Eden和使用的Survivor中还存活的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚刚使用过的Survivor空间。Hotspot默认Eden/Survivor/Survivor=8:1:1,当然我们没法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时(超过10%的对象存活),需要依赖其他内存进行分配担保(这里指老年代Tenured Gen),放不下的存活对象将进入老年代。(一般新生代中的对象默认经过15次GC后还存活也会进入老年代)

标记-紧凑法(mark-compact):将存活对象往一端移动,按照内存地址一次排序,然后将末端边界之外内存直接清理(一般用于老年代)

分代收集算法(gennerational collecting):JVM在实际垃圾回收中实际使用的是分代收集算法:根据对象存活周期的不同将内存划分为:新生代和老年代。
在新生代每次都只有少量对象存活,选用复制算法;
老年代中因为对象存活率高、没有额外空间对它进行分配担保,老年代就必须使用标记-紧凑法或标记-清除法进行回收。

所以GC有两种,年轻代的Minor GC,另外一个就是老年代的Full GC;新对象创建时如果eden伊甸园空间不足会触发Minor GC,如果此时老年代的内存空间不足会触发Full GC。

双亲委派机制:(Parents Delegation Model)

首先要知道 JVM中提供了三层的ClassLoader:

Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),其子加载器有ExtClassLoader和APPClassLoader。

ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。

AppClassLoader:主要负责加载应用程序的主函数类

一个类加载的过程如下图:

当一个Hello.class这样的文件要被加载时。不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了。如果没有,那么会拿到父加载器ExtClassLoader,然后调用父加载器的loadClass方法。父类中同理也会先检查自己是否已经加载过,如果没有再往上。注意这个类似递归的过程,直到到达Bootstrap classLoader之前,都是在检查是否加载过,并不会选择自己去加载。直到BootstrapClassLoader,已经没有父加载器了,这时候开始考虑自己是否能加载了,如果自己无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException。这种机制从一定程度上防止了危险代码的植入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值