3.1JVM

编译过程

编译时期-语法糖

如泛型:

  • 泛型只会在Java源码中存在,编译过后会被替换为原来的原生类型(Raw Type,也称为裸类型)了。这个过程也被称为:泛型擦除

有了泛型这颗语法糖以后:

  • 代码更加简洁【不用强制转换】
  • 程序更加健壮【只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常】
  • 可读性和稳定性【在编写集合的时候,就限定了类型】

JVM实现跨平台

我们通过 javac.exe编译器编译我们的 .java源代码文件生成出 .class文件。

.class文件很明显是不能直接运行的,(C语言编译cpp后生成exe文件直接运行)这些 .class文件是交由JVM来解析运行。

  • JVM是运行在操作系统之上的,每个操作系统的指令是不同的,而JDK是区分操作系统的,只要你的本地系统装了JDK,这个JDK就是能够和当前系统兼容的。
  • 而class字节码运行在JVM之上,所以不用关心class字节码是在哪个操作系统编译的,只要符合JVM规范,那么,这个字节码文件就是可运行的。
  • 所以Java就做到了跨平台—>一次编译,到处运行。

类加载器

类的加载时机

虚拟机规范则是严格规定了有且只有5种情况必须立即对类进行“初始化”(class文件加载到JVM中):

  • 创建类的实例(new 的方式)。访问某个类或接口的静态变量,或者对该静态变量赋值,调用类的静态方法
  • 反射的方式
  • 初始化某个类的子类,则其父类也会被初始化
  • Java虚拟机启动时被标明为启动类的类,直接使用java.exe命令来运行某个主类(包含main方法的那个类)
  • 当使用JDK1.7的动态语言支持时(…)

Java类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类(像是基类)完全加载到jvm中,至于其他类,则在需要的时候才加载。这当然就是为了节省内存开销

类加载到JVM

class文件通过类的加载器装载到jvm中。

Java默认有三种类加载器:启动类加载器(Bootstarp ClassLoader)、扩展类加载器(ExtClassLoader)、应用类加载器(APPClassLoader)

各个加载器的工作责任:

  • Bootstrap ClassLoader:负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

  • Extension ClassLoader:负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/ext/*.jar或-Djava.ext.dirs指定目录下的jar包

  • App ClassLoader:负责记载classpath中指定的jar包及目录中class

工作过程:

  • 1、当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。
  • 2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。
  • 3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;
  • 4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载
  • 5、如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException

类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载

双亲委派模型如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上

补充破坏双亲委派机制的实例

好处:防止内存中出现多份同样的字节码(安全性角度)

类加载详细过程
  1. 加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
  2. 连接,连接又包含三块内容:验证、准备、解析。
    • 验证,文件格式、元数据、字节码、符号引用验证;
    • 准备,为类的静态变量分配内存,并将其初始化为默认值;
    • 解析,把类中的符号引用转换为直接引用
  3. 初始化,为类的静态变量赋予正确的初始值。
JIT(just-in-time compiler)即时编译器

解析过程:

  1. JVM加载了class文件后,对Java字节码重新编译优化,生成机器码,让CPU直接执行。(这样编出来的代码效率会更高)

  2. 编译也是要花费时间的,一般对热点代码做编译非热点代码直接解析就好了。

热点代码:多次调用的方法、多次执行的循环体。

热点探测(检测是否为热点代码)热点探测有两种方式:

  1. 采样

  2. 计数器

目前HotSpot使用的是计数器的方式,它为每个方法准备了两类计数器:

  • 在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。
  1. 方法调用计数器(Invocation Counter)

  2. 回边计数器(Back EdgeCounter)。

JVM内存结构

堆:存放对象实例,几乎所有的对象实例都在这里分配内存

虚拟机栈:虚拟机栈描述的是Java方法执行的内存结构,每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息

本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务。

方法区:存储已被虚拟机加载的类元数据信息(元空间)

程序计数器:当前线程所执行的字节码的行号指示器

常量池

编译期可以确定值的字符串,也就是常量字符串 ,jvm 会将其存入字符串常量池

!!!待补充

GC垃圾回收

JVM使用准确式GC。JVM是使用一组称为OopMap的数据结构,来存储所有的对象引用(这样就不用遍历整个内存去查找了,空间换时间)。 并且不会将所有的指令都生成OopMap,只会在安全点上生成OopMap,在安全区域上开始GC。

判断那些对象"死去":

  1. 引用计数法–>难以解决对象之间的循环引用的问题
  2. 可达性分析算法–>主流的JVM采用的是这种方式

回收的算法:

  1. 标记-清除算法
  2. 复制算法
  3. 标记-整理算法
  4. 分代收集算法

上面所讲的垃圾收集算法只能算是方法论,落地实现的是垃圾收集器

  1. Serial收集器
  2. ParNew收集器
  3. Parallel Scavenge收集器
  4. Serial Old收集器
  5. Parallel Old收集器
  6. CMS收集器
  7. G1收集器

上面这些收集器大部分是可以互相组合使用

!!!GC的内容还需要进一步补充知识

JVM参数与调优

很多做过JavaWeb项目(ssh/ssm)这样的同学可能都会遇到过OutOfMemory这样的错误。一般解决起来也很方便,在启动的时候加个参数就行了。

内存的分配的大小啊,使用哪个收集器啊,这些都可以由我们根据需求,现实情况来指定

JVM内存结构补充知识

方法区的变化

jdk1.7之前的PermGen(永久代),替换成Metaspace(元空间)

  • 原本永久代存储的数据:符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap
  • Metaspace(元空间)存储的是类的元数据信息(metadata)
  • 元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存
  • 替换的好处:一、字符串存在永久代中,容易出现性能问题和内存溢出。二、永久代会为 GC 带来不必要的复杂度,并且回收效率偏低
内存溢出、内存泄露

泄露原因:对象是可达的(一直被引用),但是对象不会被使用。

溢出原因:

  1. 内存泄露导致堆栈内存不断增大,从而引发内存溢出。

  2. 大量的jar,class文件加载,装载类的空间不够,溢出

  3. 操作大量的对象导致堆内存空间已经用满了,溢出

  4. nio直接操作内存,内存过大导致溢出

解决溢出:

  1. 查看程序是否存在内存泄漏的问题
  2. 设置参数加大空间
  3. 代码中是否存在死循环或循环产生过多重复的对象实体、
  4. 查看是否使用了nio直接操作内存。
线程栈

JVM规范让每个Java线程拥有自己的独立的JVM栈,也就是Java方法的调用栈。

间已经用满了,溢出

  1. nio直接操作内存,内存过大导致溢出

解决溢出:

  1. 查看程序是否存在内存泄漏的问题
  2. 设置参数加大空间
  3. 代码中是否存在死循环或循环产生过多重复的对象实体、
  4. 查看是否使用了nio直接操作内存。
线程栈

JVM规范让每个Java线程拥有自己的独立的JVM栈,也就是Java方法的调用栈。

线程运行过程中,只有一个栈帧是处于活跃状态,称为“当前活跃栈帧”,当前活动栈帧始终是虚拟机栈的栈顶元素

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值