Java核心技术之- JVM基本概念

目录

1. Class文件

1.1. 魔数

1.2. 版本号

1.3. 常量池

1.4. 访问标志

1.5. 类索引、父索引和接口索引

1.6. 字段表集合

1.7. 方法表集合

1.8. 属性表集合

1.9. 总结一下

2. 类加载器

2.1. 引导类加载器(Bootstrap ClassLoader)

2.2. 扩展类加载器(Extension ClassLoader)

2.3. 系统类加载器(System ClassLoader)

2.3. 类加载器工作过程

3. 运行时数据区

3.1. 方法区(Method Area)

3.2. 堆(Heap)

3.3. 栈(Stack)

3.4. 本地方法栈(Native Method Stack)

3.5. 程序计数器(Program Counter Register)

3.6. 总结

4. 垃圾收集器

4.1. Serial收集器

4.2. ParNew收集器

4.3. Parallel Scavenge收集器

4.4. CMS收集器

4.5. G1收集器

5. 执行引擎

5.1 JVM执行引擎的组成

5.2 JVM执行一段Java代码的过程

6. Native接口

6.1 JNI的主要用途

6.2 JNI的主要组成部分

6.3 JNI的开发步骤

7. 安全管理器

7.1 安全管理器主要安全控制

7.2 启用安全管理器的方式

7.3 请求操作权限


JVM(Java Virtual Machine)即Java虚拟机,是Java语言的运行环境。它是一个虚构出来的计算机,用于运行Java程序。JVM将Java字节码转换成机器语言,使Java程序在不同平台下运行。JVM的基本概念包括:

1. Class文件

编译 Java文件被转换为.class文件,包含JVM指令,这些指令被称为Java字节码。

Java的class文件是编译后的字节码文件,由JVM执行。class文件具有以下结构

1.1. 魔数

前4个字节为固定值0xCAFEBABE,用于确定这个文件是否为一个class文件。

1.2. 版本号

接下来的4个字节表示Class文件的版本号,用于兼容不同版本的JVM。

1.3. 常量池

存放编译期生成的各种字面量和符号引用,相当于class文件的资源仓库。

1.4. 访问标志

用于描述该Class文件trigram的访问权限及属性。

1.5. 类索引、父索引和接口索引

分别给出该Class引用的类名、父类名和接口名称。

1.6. 字段表集合

用于描述类内所定义的变量。包括字段名、类型和访问标志。

1.7. 方法表集合

用于描述类内的方法,包括方法名、返回值类型、参数类型、代码等。

1.8. 属性表集合

用于描述一些附加信息,比如注释、调试信息等。

1.9. 总结一下

- 魔数和版本号:确定是否为class文件以及兼容性。

- 常量池:包括数字、字符串、类名、字段名、方法名等常量,是解析class文件的基础。

- 访问标志:描述访问权限如public、private等。

- 类索引、父索引和接口索引:描述类的继承关系。

- 字段表:描述类的属性,包括属性名、类型和访问标志。

- 方法表:描述类的方法,包括方法名、返回值类型、参数列表、异常表、访问标志以及方法体(Java字节码)。

- 属性表:描述附加属性,如注解、调试信息等,不是必须的。

class文件由以上几部分构成,这就是Java程序在编译后代表的样子,存储在磁盘上。

当执行Java程序时,JVM会读取class文件,解析其中信息,然后执行其中的方法体部分,这就是Java代码运作的基础。理解class文件格式,有助于我们进一步理解JVM的工作机制,以及Java虚拟机是如何加载和执行我们的Java程序的。这也是成为一名合格的Java工程师必备的知识。

2. 类加载器

Java类加载器负责将class文件加载到JVM中,类加载器使用委托模型进行加载。

2.1. 引导类加载器(Bootstrap ClassLoader)

负责加载Java的核心库,如rt.jar、resources.jar、charsets.jar等。由C++实现,无法直接获取。

2.2. 扩展类加载器(Extension ClassLoader)

负责加载Java的扩展库,默认加载lib/ext目录中的jar包。

2.3. 系统类加载器(System ClassLoader)

负责加载Java应用的类路径(classpath)所指定的jar和类文件。一般加载环境变量CLASSPATH或-cp/-classpath选项指定的位置。

2.3. 类加载器工作过程

类加载器使用父委托模型进行加载:每个类加载器都有一个父类加载器,在加载某个类时,会先请求父类加载器进行加载,每个类加载器都会沿着父类链最终请求引导类加载器进行加载。

类加载器加载类的过程如下:

1. 首先,它查找该类是否已经被加载,如果被加载则直接返回。

2. 然后,它将请求父类加载器进行加载,直到引导类加载器。如果被加载则返回,否则继续下一步。

3. 最后,系统类加载器将查找CLASSPATH中是否存在该类,存在则将其读取进内存,并将其加载到JVM中。

类加载器采用上述加载规则有两个主要目的:

1. 避免类的重复加载。如果一个类被加载了两次,可能会产生问题。

2. 委托模式使得Java类按着父类链排序的反序进行加载。 父类先加载,子类后加载。 这样可以确保一个类在被子类引用前已经被加载。

类加载器在Java类加载和运行机制中起着核心作用。理解Java的类加载机制,有助于我们更好地设计程序结构,编写出更健壮的代码。同时,也能够在程序出现NoClassDefFoundError、ClassNotFoundException等异常时,更好地定位问题原因。

类加载器是Java程序动态加载类的关键,是JVM架构中最基础的一部分。

3. 运行时数据区

Java运行时数据区(Runtime Data Area)是JVM为每条运行线程分配的内存空间。它由多个部分组成,用于存储类信息、对象实例、方法信息等。主要有以下几个区:

3.1. 方法区(Method Area)

用于存储类信息、常量、静态变量等。对每个JVM运行实例只有一个方法区。

3.2. 堆(Heap)

用于存储new出来的对象实例,由垃圾回收器管理。对每个JVM运行实例只有一个堆。

3.3. 栈(Stack)

用于存储方法的调用栈。每个线程有一个栈,用于存储局部变量表、操作栈、动态链接、方法出口等信息。

3.4. 本地方法栈(Native Method Stack)

与本地方法相关,用于JNI。每个线程有一个本地方法栈。

3.5. 程序计数器(Program Counter Register)

每条线程都有一个PC寄存器,用于存储当前线程正在执行的JVM指令的地址。

3.6. 总结

方法区和堆是各个线程共享的,其余区域对每个线程都独立分配。这些区域的详细作用如下:

- 方法区: 用于存储Class信息、静态变量和常量等。可以由多个线程共享,且在JVM运行期只有一个方法区。

- : 用于存储对象实例,由GC回收管理。多个线程共享,且只有一个堆。

- : 用于存储方法的调用栈,包括局部变量表、操作栈、动态链接和方法出口等信息。每个线程有一个栈。

- 本地方法栈: 用于存储JNI相关信息。每个线程有一个本地方法栈。 

- 程序计数器: 当前线程所执行的字节码的行号指示器。每个线程都有一个PC寄存器。

理解Java运行时数据区的作用和功能,有助于我们分析调试程序,解决内存溢出等问题。这也是成为一名合格的Java工程师必备的知识。对JVM运行时数据区有一个清晰的认识,可以更好地设计程序并发现潜在问题。

4. 垃圾收集器

Java垃圾收集器负责回收Java堆中不再使用的对象,释放内存空间。Java提供了多种垃圾收集器,主要包括:

4.1. Serial收集器

单线程收集器,会暂停整个应用程序执行GC。适用于单CPU小型应用。

4.2. ParNew收集器

Serial收集器的多线程版本,适用于多CPU系统。

4.3. Parallel Scavenge收集器

同样多线程,吞吐量优先。新生代收集器。

4.4. CMS收集器

Concurrent Mark Sweep,并发标记清除。多线程,可进行内存碎片整理。老年代收集器。

4.5. G1收集器

Garbage First,混合收集器。可用于新生代和老年代,将堆分为多个独立的子区域进行收集。
 

上述收集器可以搭配使用:

- Serial + CMS:新生代Serial,老年代CMS,用于小内存应用。

- ParNew + CMS:新生代ParNew,老年代CMS,用于多CPU和大内存应用。

- Parallel Scavenge + CMS:新生代Parallel Scavenge,老年代CMS,吞吐量优先的方案。

- G1:可独立使用,全堆收集器,适用于大容量内存和多CPU的场景。

收集器 throughput(吞吐量)和 latency(延迟)之间需要权衡。两者的区别在于:

- Throughput: running time中的非GC时间/(GC时间 + 非GC时间)。吞吐量越高越好。
- Latency: GC暂停时间,应尽量短。 

CMS和G1是以Latency为主的收集器; Parallel Scavenge注重Throughput。

选择合理的垃圾收集器对系统性能至关重要。需要根据应用场景选择,考虑到程序堆大小、GC暂停时间要求和吞吐量需求等因素。

理解Java垃圾收集机制和收集器之间的区别,有助于我们根据实际应用场景选择最优的配置,调优程序的运行效率。这也是成为一名高级Java工程师必备的知识。

5. 执行引擎

负责执行指令,将Java字节码转化为机器语言。使用栈式架构,通过PC寄存器获取指令,然后执行。

5.1 JVM执行引擎的组成

1. 类加载器(ClassLoader):负责将class文件加载到JVM中,并将其转换成JVM可以识别的格式。

2. 方法区(Method Area):存储已加载的类信息,常量,静态变量等。

3. 堆(Heap):用于存储对象实例,是GC的主要区域。

4. 栈(Stack):用于存储方法调用的信息,本地变量等。每个线程都有自己的栈空间。

5. 程序计数器(Program Counter Register):用于存储正在执行的方法的地址,并指向下一条指令。

6. 寄存器(Registers):用于缓存局部变量和计算结果。

7. 垃圾收集器(Garbage Collector):用于回收垃圾对象,释放堆空间。

5.2 JVM执行一段Java代码的过程

1. 类加载器将class文件加载到方法区中,生成Class对象。

2. JVM创建对象实例并存放在堆中。

3. 每个线程在自己的栈空间中创建一个栈帧用于存储方法调用的信息。

4. 程序计数器记录当前执行到的字节码指令地址。

5. 解释器依次执行每条字节码指令:

 - 访问常量池和方法区
 - 操作堆中的对象
 - 访问寄存器和栈帧
 - 更新程序计数器

6. GC线程定期执行垃圾收集,回收垃圾对象,释放堆空间。

7. 执行引擎重复上述流程,直到所有的线程执行结束。

这就是JVM执行Java程序的整体过程,通过各个组件的配合完成代码的解释和执行。

6. Native接口

JNI(Java Native Interface),将Java程序和本地应用程序连接,是Java调用Native方法的机制。它允许Java代码和Native代码(如C/C++)相互调用。

6.1 JNI的主要用途

1. 与平台相关的功能:像注册事件处理等。
2. 已有的库的再利用:比如 need 执行复杂的数字处理,可以调用C/C++的科学计算库。
3. 性能关键部分:将关键部分用Native语言实现,可以获得更好的性能。

6.2 JNI的主要组成部分

1. Native方法:在Java代码中使用native关键字声明,实现在Native代码中。

2. JNI框架:包含jni.h头文件,各种JNI函数等。用于在Native代码中调用Java方法,访问Java对象等。

3. JNI接口:JNIEnv接口,代表JNI接口指针,用于调用JNI函数。每个Native线程都有自己的JNIEnv指针。

4. Java VM接口:JNI_CreateJavaVM函数用于创建JVM,获得JNIEnv接口指针。

6.3 JNI的开发步骤

1. 在Java中声明Native方法,使用native关键字。

2. 使用javah工具生成Native方法的头文件。

3. 实现Native方法,调用JNI函数。

4. 动态加载Native库,调用System.loadLibrary。

5. 编译Native库,生成so文件或dll文件。

6. 在Java程序运行时,会调用System.loadLibrary加载Native库,然后执行Native方法。

JNI是Java调用Native功能的重要机制,但影响Java的可移植性,使用时需慎重考虑。仅在必要时使用JNI,性能关键情况或需要使用平台特性时使用。

7. 安全管理器

JVM安全管理器(SecurityManager)是Java安全机制的基石,它限制Java代码执行潜在危险的操作。

用于实现Java的安全机制,防止恶意程序干扰Java程序或访问机密数据。

当一个Java程序启动时,默认情况下是没有安全管理器的。如果需要启用安全机制,需要设置一个安全管理器。

7.1 安全管理器主要安全控制

1. 文件访问:限制读写文件,目录列表等操作。

2. 网络连接:限制发起网络连接,监听端口等。

3. 类加载:限制加载类,限制package访问等。

4. 系统属性访问:限制获取,设置系统属性。

5. AWT访问:限制窗口关闭,鼠标键盘操作等。

6. 包访问权限:限制跨域的package访问。

安全管理器通过检查权限来控制这些敏感操作。开发人员需要显式地请求所需的权限,安全管理器才会授予执行权限。

7.2 启用安全管理器的方式

1. 在命令行启动Java程序时,指定-Djava.security.manager选项。

2. 在代码中调用System.setSecurityManager()方法设置。

7.3 请求操作权限

1. 如果操作在安全管理器检查之前执行,则无需权限(如在main方法前)。

2. 调用SecurityManager的checkXXX()方法来请求权限,如果权限允许,check方法返回正常,否则抛出SecurityException。

3. 权限可以在policy文件中配置,也可以在代码中调用Policy.setPolicy方法设置。

policy文件格式如下:

 grant {
     permission java.io.FilePermission "myfile", "read,write";
 };


这是一个非常简单但功能强大的安全框架。Java领先于其它语言之处在于,它从一开始就考虑到了安全问题,并提供面向开发者的简单易用的安全框架。

安全管理器为Java应用提供了不错的安全性保护,我们开发安全敏感的Java应用时,应该积极使用它。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

撸码猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值