JAVA虚拟机相关知识点总结

A. JVM内存管理

JVM主要把内存划分如下几个区域

  1. 方法区
  2. 堆区
  3. 本地方法栈
  4. 虚拟机栈
  5. 程序计数器

虚拟机

1. 方法区

方法区存放要加载的类的信息,静态变量,final常量,field和方法信息。

方法区也包含运行时常量池,用于存储编译器生成的字面常量,符号引用(用字符表示某个变量接口的位置)和翻译出来的直接引用(符号引用翻译出来的地址)。

这块区域是持久代,垃圾回收很少。

通过 --XX:PermSize 定义最小值 --XX:MaxPermSize 定义最大值

2. 堆区

堆区大小通过 -Xms 和 -Xmx控制

-Xms : JVM启动时申请的heap内存,默认物理内存的1/64
-Xmx : JVM可申请的最大heap内存, 默认物理内存的1/4

年轻代

对象在被创建时,内存首先在年轻代分配,当年轻代回收时出发Minor GC操作。

年轻代由Eden Space 和两块相同大小Survivor Space组成, 年轻代区域连续的,分配很快,回收也很快。

老年代

老年代用于存放年轻代中多次垃圾回收还存活的对象,如缓存对象。大对象也可直接在老年代分配内存,当老年代满了进行垃圾回收成为Major GC

3. 本地方法栈

本地方法栈用于支持native方法的执行,存储了每个native方法调用状态。和虚拟机栈运行机制一致,只不过虚拟机栈执行java方法,本地方法栈执行native方法。

4. 虚拟机栈

每个线程对应一个虚拟机栈,是线程私有的,分配十分高效。栈帧中存储了局部变量表,操作栈,动态链接和方法出口。
当线程调用的栈深度大于虚拟机允许的最大深度,抛出栈溢出异常。

5. 程序计数器

程序计数器用于指示当前线程所执行的字节码执行位置,如果执行的是java方法,计数器记录的是虚拟机字节码指令的地址,如果执行native方法,计数器的值为undefined。

6. java对象访问方式

Object objRef = new Object()

  1. Object objRef 表示本地引用, 存储在JVM本地变量表中。
  2. new Object() 存储在堆中。
  3. 堆中还记录查询Object的类型数据(接口,方法,field,对象类型),实际数据保存中方法区中。

有两种对象访问方式,一种是通过句柄访问。

句柄访问

另一种通过直接指针访问

直接指针访问

7. JVM 内存分配

JVM对象占用内存主要在堆上实现,因为堆是共享的需要加锁,导致对象开销比较大。
为了提升分配效率,在年轻代的Eden区,hotspot采用两种技术来加快内存分配,分别是bump the pointer和TLAB(Thread local allocation buffers)。

bump the pointer : 跟踪最后创建的对象,在对象创建时,只需检查最后一个对象后面是否有足够的内存。

TLAB : 为每个新创建的线程在新生代的Eden Space上分配一块独立的空间,成为TLAB。通过-XX:TLABWasteTargetPercent指定占用EDen space百分比,默认1%。一般优先在TLAB上分配,如果对象过大或TLAB用完,则在堆上分配。

8. 内存回收方式

垃圾回收

JVM通过GC来回收堆和方法区中的内存,要执行内存回收,我们需要确定哪些内存需要回收;确定什么时候需要执行GC以及如何执行GC。

引用计数器收集器

通过计数器记录对象是否被引用,当计数器为9,说明对象不再被使用,可进行回收。

跟踪收集器

跟踪收集器会全局记录引用的状态,根据一定条件触发,执行时需要扫描对象的引用关系,可能会造成应用程序暂停,主要有,复制,标记-清除,标记-压缩三种算法。

为什么要分代回收

一开始,GC采用标记清除压缩方式进行的,当对象分配较多,扫描和移动越来越耗时,造成内存回收越来越慢。

9. 垃圾收集器

垃圾回收器主要有串行,并行,和CMS(Concurrent Mark Sweep)收集器。

串行收集器,minor和major GC都是用一个线程进行垃圾回收。

并行收集器采用多线程方式回收,通过-XX:ParallelGCThreads=来控制并行的线程数量。

CMS收集器用于对暂停时间要求很高的场景,采用多线程并发来减少垃圾收集过程中的暂停,CMS收集器不会对存活的对象进行复制或移动。

B. JAVA 字节码

java字节码格式

1. 魔数

魔数是用来区分文件类型的一种标志。

2. 版本号

版本号分为主版本号和次版本号。

3. 常量池

常量池类表

常量池是Class文件中的资源仓库,在Class Name和Interfaces中都有涉及。

主要存储两大类常量:字面量和符号引用。

字面量如文本字符串,java中声明为final的常量池。

符号引用如类和接口的全局限定名,字段的名称和描述符,方法的名称和描述符。

4. Access_Flag 访问标志

Access_flag

5. 类索引,父类索引

类索引用于确定类的全限定名

父类索引用于确定类的父类的全限定名

6. 接口索引

接口有2 + n个字节,前两个字节表示的是接口数量,后面跟着n就是接口的表。

7. 字段表示集合

字段表用于描述类和接口中声明的变量,包含类级别变量以及实例变量,不包含方法局部变量。

8. 方法

方法表描述类中定义的方法。

方法中有方法表,Code表,变量表。

9。 Attribute

SourceFile 表示生成class文件的源码文件名称

https://github.com/zxh0/classpy

C. 类加载机制

JAVA 类加载过程主要为5个部分

加载 --> 验证 --> 准备 --> 解析 --> 初始化

加载:通过类的完全限定查找该类的字节码文件,并利用字节码创建一个class对象

验证:确保class字节流合法,包含4种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

准备:为类变量(static变量)分配内存并初始化,类变量会被分配到方法区中。

解析:将常量池符号引用替换为直接饮用过程,符号引用是一组符号描述的目标,直接引用是指向目标的指针。

初始化:类加载最后阶段,若该类有超类,对其进行初始化,

类加载器根据类的全限定名,读取该类的二进制字节流到JVM中

Bootstrap类加载器

bootstrap加载器加载JVM自身需要的类,将<JAVA_HOME>/lib下的核心库或-Xbootclasspath参数指定的jar包加载到内存,虚拟机是按照文件名来加载jar包的

Extension类加载器

Extension负责加载<JAVA_HOME>/lib/ext目录下或-Djava.ext.dir制定位路径中的类库

System类加载器

负责加载系统类路径java -classpath 或-Djavva.class.path

1. 双亲委派模式

原理

除了顶层的启动类加载器,其余的类加载器都应当有自己的父类加载器

委派模式原理

当需要加载类的时候,先把请求委托给父类加载器去执行,依次递归,如果父类可以完成加载,则成功返回,然后自加载器才会尝试自己去加载。

好处:避免类的重复加载,安全性高。

loadClass(String)

当类加载请求到来时,先从缓存中查找该类对象,没有则交给父类加载器加载,仍没有就用findClass方法加载

findClass(String)

findClass()在loadClass()中被调用,当loadClass()方法中父加载器加载失败后,会调用自己findClass()来进行类的加载。

defineClass(byte[] b, int off, int len)

defineClass()将byte字节流解析成JVM能够识别的Class对象,通过这个方法可以通过class文件实例化Class对象,defineClass通常与findClass一起使用

resolvClass(Class<?> c)

使类的Class对象创建同时也解析,

2. 类与类加载器

判断两个class对象是否为同一个类:类的完整类名必须一致,包括包名。加载这个类的ClassLoader必须相同。

在JVM中,如果两个类对象来源于同一个Class文件,被同个虚拟机加载,只要ClassLoader实例对象不同,这俩类也是不相等的。

因为不同的ClassLoader实例对象有不同的独立的类名称空间,所以加载的class会存在不同的类名称空间。

显示加载与隐试加载

显示加载是通过ClassLoader加载对象,隐式加载是不直接在代码中调用ClassLoader方法加载class对象。

3. 类加载器编写

实现自定义类加载器需要继承ClassLoader或者URLClassLoader,继承ClassLoader需要自己编写findClass方法。

当class文件不再ClassPath下,默认系统类加载器无法找到class,需要自定义额ClassLoader来加载;或者当class通过网络传输时;或需要实现热部署功能时。

热部署类加载器

热部署就是利用同一个class文件不同的类加载器中内存中创建出两个不同的class对象。我们可以直接调用findClass()方法,而不能调用loadClass方法,因为loadClass方法中调用了findLoadedClass检车了类是否已经被加载。

线程上下文类加载器

JAVA应该用中有很多服务提供者接口SPI,如JDNC,JNDI。这些SPI接口属于JAVA核心库,在rt.jar包,由Bootstrap类加载器加载,而第三方代码则被放在classpath路径下,因为SPI接口代码需要加载第三方实现类并调用其方法,但SPI核心接口类由引导类加载器加载,Bootstrap类加载器无法加载SPI实现类,这就需要contextClassLoader。

通过getContextClassLoader()和setContextClassLoader(ClassLoader cl)来获取和设置线程的上下文类加载器。初始的contextClassLoader时AppclassLoader,在线程中运行的代码可以通过此类加载器来加载类的资源。

使用线程类加载器破坏了双亲委派加载链模式,使程序可以逆向使用类加载器。外部实现类不能通过Bootstrap类加载器加载,就委托线程上下文加载器把jdbc.jar中实现类加载到内存中方便SPi 相关类调用。

D. JAVA解释执行过程

An interpreter is a software program that converts code from high level language to machine format.

JAVA编译器首先验证代码的正确性,然后把JAVA源代码转化成byte-code文件,与此同时计算常量的值以及缓存字符串。

JVM在bytecode层面上操作,JVM 把bytecode最终转变成machine code,然后最终在OS上执行。

bytecode和优化后的用户代码,以及java库和OS调用一起合作来执行JAVA程序。

现代JAVA会用JIT来直接产生native code,从而执行效率更高。

JVM在load java代码的时候会很慢,因为JVM不仅load jars和classes,也在做cc -O3操作。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值