圆圆网络 手游攻略 新游动态 深入理解java虚拟机,下面哪种类型的文件可以在java虚拟机中运行

深入理解java虚拟机,下面哪种类型的文件可以在java虚拟机中运行

时间:2024-05-02 01:08:11 来源:头条 浏览:0

文章来源:https://blog.csdn.net/zwx900102/article/details/107876856

原作者:双狼

来源平台:CSDN

深入理解java虚拟机,下面哪种类型的文件可以在java虚拟机中运行

简介:JVM系列文章中除特别说明外,部分功能均基于Hot Spot虚拟机和JDK1.8版本进行描述。下图是JDK 的整体图,对于学Java 的人来说都很熟悉。

从上图可以看出,Java虚拟机位于最底层,所有的Java应用程序都是在JVM的基础上运行的,所以学习JVM对于任何想要深入了解Java的人来说都是必不可少的。 Java 的口号是“一次编写,随处运行”。这是由于JVM的存在,它有助于处理各个平台的兼容性问题,让你只要安装了相应系统的JDK就可以运行而不必担心其他问题。

什么是JVM? JVM 的正式名称为Java 虚拟机,是一种抽象计算机。就像真正的计算机一样,它有一组指令,并在运行时在不同的内存区域中进行操作。虚拟机有很多不同类型,每个供应商只要遵循虚拟机规范就提供不同的实现。目前,虚拟机一般指的是热点。 JVM 对Java 编程语言一无所知,只知道一种特定的二进制格式:类文件格式。类文件包含Java虚拟机指令(或字节码)和符号表,以及其他辅助信息。这意味着你编写的程序最终传递给JVM执行时会被编译成二进制格式。注意:Java虚拟机只识别二进制格式的文件,因此你可以在Java虚拟机上运行任何语言,例如Kotlin、Groovy等,只要编译后的格式满足你的要求。

运行一个Java程序从创建的.java文件到最终在JVM上执行的过程大致如下:

Java类编译并加载后,加载后检索到的数据被放置在运行时数据区中,以便程序执行时可以直接从JVM内存中读取相应的信息。

运行时数据区运行时数据区:运行时数据区。 Java虚拟机定义了程序执行期间使用的各种运行时数据区域。其中一些数据区域是在Java 虚拟机启动时创建的,并且仅在Java 虚拟机终止时才被销毁。这些区域由所有线程共享,这会产生线程安全问题。有一些数据区域是每个线程专用的,但是每个线程的私有数据区域在线程创建时创建,在线程终止时销毁,但是线程私有数据区域不存在安全问题。运行时数据区主要包括部分PC寄存器、堆、方法区、虚拟机栈和本地方法栈。

PC(Program Counter)寄存器(Program Counter) PC寄存器是每个线程专用的空间。 Java虚拟机可以支持多个线程同时执行。在任何给定时刻,处理器仅执行线程内的一条指令。另外,由于线程是随机的,操作系统会不断切换线程来执行不同的指令。因此,要在切换线程后返回到原来的执行位置,每个JVM线程必须有自己的PC(程序计数器)寄存器,独立地存储执行信息,允许从之前的位置继续执行,这是需要做的。每个Java 虚拟机线程在任何给定时间执行单个方法的代码(该线程的当前方法)。如果该方法不是本地方法,则pc寄存器记录当前正在执行的Java虚拟机指令的地址。如果线程当前正在执行的方法是本地方法,则Java虚拟机PC寄存器的值为Undefined。

堆堆是Java虚拟机管理的最大内存量,在虚拟机启动时创建,并由所有线程共享。堆是在虚拟机启动时创建的,用于存储所有对象实例和数组(某些特殊情况除外)。堆中的对象永远不会显式释放,必须由GC 自动回收。因此,GC主要回收堆中的对象实例。通常,当我们讨论垃圾收集时,我们主要指的是回收堆内存。堆可以驻留在物理上不连续的内存空间中,可以固定大小或动态扩展,并且最小和最大堆内存值可以通过参数-Xms 和Xmx 控制。堆中可能存在以下异常:

如果计算需要的堆多于自动存储管理系统提供的堆,则会抛出OutOfMemoryError 错误。在堆上模拟OutOfMemoryError 为了方便模拟,固定堆的大小并设置如下:

-Xms20m 选择-Xmx20m 并创建一个新的测试类来测试它。

包com.zwx.jvm.oom;导入java.util.ArrayList;导入java.util.List;public class Heap { public static void main(String[] args) { List list=new ArrayList(); while (true) { list.add(99999); } }}输出如下(下面的Java堆区显示堆区溢出):

线程“main”中出现异常java.lang.OutOfMemoryError: java.util.Arrays.copyOf(Arrays.java:3210)at java.util.Arrays.copyOf(Arrays.java:3181)123 Java 堆空间注意:堆太小如果我更改大小如上所示,将参数设置为2m,出现以下错误:

VMGC 初始化期间发生错误,该错误在VM 初始化完成之前触发。尝试增加NewSize的当前值153 方法区(Method Area) 方法区是每个线程共享的内存区域,在虚拟机启动时创建。包含每个类的结构,包括运行时常量池、属性和方法数据以及方法和构造函数代码,包括类和实例初始化以及接口初始化中使用的特殊方法。尽管方法区在逻辑上是堆的一部分,但它被赋予别名“Non-Heap”以区别于Java 堆。方法区域可以是固定大小,可以根据计算需要而增长,也可以在不需要更大的方法区域时缩小。方法区内存不需要是连续的。方法区中可能会出现以下异常:

如果方法区域的内存无法满足分配请求,则会抛出OutOfMemoryError。运行时常量池(Runtime Constant Pool) 运行时常量池是方法区的一部分,用于存储编译生成的文字和符号引用。类或接口的运行时常量池是在Java 虚拟机创建类或接口时构建的。

文字在计算机科学中,文字是用于表示源代码中固定值的符号。几乎所有计算机编程语言都有基本值的文字表示,例如整数、浮点数和字符串。 Java 中常用的文字是基本数据类型、常量或由Final 修饰的字符串。

String 字符串去哪了,值得拿出来单独解释一下。在jdk1.6及之前的版本中,Java字符串是放在方法区的运行时常量池中的,但是在jdk1.7和jdk1中,版本(jdk1.8及以后的版本因为我没有深入了解就不解释了) it).一旦检索出来,字符串常量池就被检索出来并放置在堆上。让我们用一个例子来展示其中的区别。

Package com.zwx;公共类演示{ public static void main(String[] args) { String str1=new String('lonely') + new String('wolf'); System.out.println(str1==str1. intern()); }}该语句的输出取决于JDK 版本:JDK1.6 打印false。

在JDK1.7 中输出true。

即使在JDK1.8 中也输出True。

jdk1.6及之前版本中的intern()方法:当调用String.intern()方法时,它首先会去常量池中查看当前字符串是否存在。如果不存在,将创建该字符串。方法区和new String('')方法创建的字符串都在堆中。返回false,因为两个字符串的地址不相等。在jdk1.7和1.8版本中,字符串常量池从方法区的运行时常量池移至堆内存,intern()方法也做了相应的改变。调用String.intern()方法时,首先检查常量池中是否存在。如果不存在,将创建一个常量,并且引用将指向堆。也就是说,两个字符串对象都指向堆中的对象,因此它们返回true。我们把上面的代码转换一下,构造一个字符串: String str1=new String('ja') + new String('va'); System.out.println(str1==str1.intern()); 此时,jdk1. 7、jdk1.8也返回false。《深入理解Java虚拟机》书中对这种差异的解释是Java字符串已经存在于常量池中,所以我个人猜测是JDK本身在初始化时需要使用Java字符串,因此创建了常量池。如果我的理解有误,请指正。谢谢。

new String("lonely") 创建多个对象。在上面的示例中,我们使用两个new String("lonely") 和new String("wolf") 添加对象。如果删除了new String() 语句之一,则jdk1.7 和jdk1.8 中返回的值实际上将为false 而不是true。这是为什么?见下文(假设字符串常量池开头没有字符串)。

仅执行一个new String("lonely") 就会创建两个对象,一个在堆上,一个在字符串常量池中。这时候就执行了String.intern()方法,对于字符串常量池的情况,String.intern()发现字符串常量池里有一个很长的字符串,所以直接返回。由于字符串存在,str1==str1.intern()将全部为假。因为一个在堆中,一个在字符串常量池中。

new String("lonely")+new String("wolf")创建了5个对象,3个在堆上,2个在字符串常量池中。此时执行String.intern()方法会发生什么情况呢?jdk1.7和jdk1.8检查字符串常量池,如果发现lonelywolf字符串不存在,则删除堆中指向的字符串它被创建并放置在字符串常量池中。

jdk1.6产生不同的执行结果是因为它重新创建了lonelywolf字符串并将其放入字符串常量池中而不是指向堆。注意:+号底层执行的是new StringBuild().append()语句,所以我们看下面的例子:

String s1=new StringBuilder('aa').toString();System.out.println(s1==s1.intern());String s2=new StringBuilder('aa').append('bb') .toString ();System.out.println(s2==s2.intern());//1.6返回false,1.7和1.8返回true1234.这在jdk1.6版本中都返回false,但是在jdk1.7和jdk1.8中一个返回false,另一个返回true。附加加法相当于上面的附加+号,原理是一样的。

符号引用符号引用以及类加载的工作原理将在下一篇文章中解释。我这里就不解释了。如果您有兴趣,请关注我,敬请关注JVM 系列的下一篇文章。

jdk 1.7和1.8之间实现方法区的区别Java虚拟机规范中规定了方法区,但没有关于实现方法的规定,因此虚拟机制造商可以通过多种方式实现方法区。对于HotSpot 虚拟机:

jdk1.7及之前版本中的方法区是以持久代格式实现的,方法区的大小和最大允许值可以通过参数-XX:PermSize和-XX:MaxPermSize来控制。

jdk1.8版本中删除了持久代,使用元空间来实现方法区,因此jdk1.8中持久代的参数-XX:PermSize和-XX:MaxPermSize被废弃,取而代之的是参数-XX:MetaspaceSize和-XX:MaxMetaspaceSize。元空间和持久代之间的主要区别在于,元空间不驻留在JVM 内存中,而是直接存储在本地内存中。 jdk1.8中设置-XX:PermSize和-XX:MaxPermSize时出现警告,如下:

Java HotSpot(TM) 64 位服务器VM 警告: 忽略选项PermSize1m;在8.0 中删除支持Java HotSpot(TM) 64 位服务器VM 警告: 忽略选项MaxPermSize1m;在8.0 模拟方法区中删除支持OutOfMemoryErrorjdk1.7 及更早版本中删除jdk1.7及之前版本,使用持久代来实现方法区,可以通过设置持久代参数来模拟内存溢出。将持久代设置为最大2M:

-XX:PermSize=2m -XX:MaxPermSize=2m 然后运行以下代码。

package com.zwx;import java.util.ArrayList;import java.util.List;public class demo { public static void main(String[] args) { List list=new ArrayList(); int i=0; while (true ){ list.add(String.valueOf(i++).intern()); } }} 最后报OOM错误:PermGen space(持久代溢出)。

初始化VMjava.lang.OutOfMemoryError: 时发生错误。 PermGen spaceat sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Launcher.java:141)at sun.misc.Launcher.(Launcher.java:71)at sun.misc.Launcher.(Launcher.java:57) jdk1.8 jdk1.8版本为永久代模拟方法不同,因为它已经被取消了。首先,我们引入asm字节码框架的依赖(前面介绍动态代理时,cglib动态代理也使用asm框架来生成字节码,所以我们可以直接通过cglib API生成)。

asm asm 3.3.1 创建工具类,生成class文件。

包com.zwx.jvm.oom;导入jdk.internal.org.objectweb.asm.ClassWriter;导入jdk.internal.org.objectweb.asm.MethodVisitor;导入org.objectweb.asm.Opcodes;导入java.util.ArrayList ;import java.util.List;public class MetaspaceUtil extends ClassLoader { public static List createClasses() { Listclasses=new ArrayList(); for (int i=0; i 10000000; ++i) { ClassWriter cw=new ClassWriter( 0 ); cw.visit(Opcodes.V1_1, Opcodes.ACC_PUBLIC, 'Class' + i, null, 'java/lang/Object', null); MethodVisitor mw=cw.visitMethod(Opcodes.ACC_PUBLIC, '', '( ) V', null, null); mw.visitVarInsn(Opcodes.ALOAD, 0); mw.visitMethodInsn(Opcodes.INVOKESPECIAL, 'java/lang/Object', '', '()V'); mw.visitInsn( Opcodes .RETURN); mw.visitMaxs(1, 1); mw.visitEnd(); MetaspaceUtil test=new MetaspaceUtil(); byte[] code=cw.toByteArray(); 类exampleClass=test.defineClass('Class' + i , code, 0, code.length);classes.add(exampleClass); } returnclasses; }}设置元空间的大小

-XX:MetaspaceSize=5M -XX:MaxMetaspaceSize=5M 1 接下来,运行测试类模拟。

package com.zwx.jvm.oom;import java.util.ArrayList;import java.util.List;public class MethodArea { public static void main(String[] args) { //jdk1.8 list list=new ArrayList() ; while(true){ list.addAll(MetaspaceUtil.createClasses()); } }}1234567891011121314 抛出以下异常OOM:Metaspace。

Java虚拟机栈(Java Virtual Machine Stack) 每个Java虚拟机线程都有一个私有的Java虚拟机栈,它是与该线程同时创建的。 Java虚拟机栈存储的是栈帧(Frame)。每个调用的方法都会生成一个堆栈帧,用于存储方法状态信息,例如局部变量、操作堆栈帧和方退出。当调用方法时,会生成一个堆栈帧并将其压入堆栈,当方法调用完成时,会从堆栈中弹出堆栈帧。下图展示了一个典型的调用流程。

Java虚拟机栈中可能出现两种异常:

如果运行线程所需的堆栈深度大于Java虚拟机的堆栈深度,则会抛出StackOverflowError。从上图可以看出,调用一个方法实际上就是一个入栈和出栈的过程,如果一直入栈而不弹出,很可能会出现异常(比如递归)。 Java虚拟机栈可以动态增长,但是如果扩展大小时无法申请到足够的内存,则会抛出OutOfMemoryError。大多数Java 虚拟机堆栈支持大小的动态增长或允许固定大小配置(取决于虚拟机实现,Java 虚拟机规范允许两者)。注意:当我们谈论JVM堆栈时,我们通常指的是Java虚拟机堆栈。

下面是一个简单的递归方法,在不脱离递归条件的情况下模拟堆栈中的StackOverflowError。

package com.zwx.jvm.oom;public class JMVStack { public static void main(String[] args) { test(); } static void test(){ test(); }}1234567891011 输出结果如下。

线程“main”中的异常java.lang.StackOverflowErrorat com.zwx.jvm.oom.JMVStack.test(JMVStack.java:15)at com.zwx.jvm.oom.JMVStack.test(JMVStack.java:15).1234Native 方法栈(本地方法栈) 本地方法栈与Java虚拟机栈类似,只不过本地方法栈包含了native方法。在某些虚拟机中,例如热点虚拟机,本地部署栈和Java虚拟机栈是结合在一起的,而不是分离的。本地方法堆栈可能引发以下异常:

如果线程执行所需的堆栈深度大于本机方法的堆栈深度,则会抛出StackOverflowError。如果本地方法栈可以动态扩展,但扩展时内存不足,则会抛出OutOfMemoryError。概述这篇文章主要介绍了JVM运行时数据区的结构以及每个区域存储了哪些数据,然后模拟了常见异常是如何产生的。当然,模拟异常的方法有很多种。各个区域的存在以及生成的内容可以在模拟时适当地生成。本文从整体上介绍了运行时数据空间,主要用于概念性理解。下一篇文章介绍类加载机制和双亲委托模式。它引入了类加载模式,同时还更新了运行时数据。该地区的详细介绍。

如果你目前是一名Java开发人员,但也在学习Java,并且在你的Java学习之旅中缺少一些基础的入门视频教程,请关注我01并私信我。获取最新2020年Java基础深入视频教程、学习手册、面试题、开发工具、PDF文档书籍教程以及免费截图:

关注我们并私信我们:01.即可获得以上材料。

标题:深入理解java虚拟机,下面哪种类型的文件可以在java虚拟机中运行
链接:https://yyuanw.com/news/xydt/13350.html
版权:文章转载自网络,如有侵权,请联系删除!
资讯推荐
更多
阴阳师4月22日更新内容:帝释天上线技能调整,红莲华冕活动来袭

阴阳师4月22日更新内容:帝释天上线技能调整,红莲华冕活动来袭[多图],阴阳师4月22日更新的内容有哪些?版本更新

2024-05-02
四川电视台经济频道如何培养孩子的学习习惯与方法直播在哪看?直播视频回放地址

四川电视台经济频道如何培养孩子的学习习惯与方法直播在哪看?直播视频回放地址[多图],2021四川电视台经济频

2024-05-02
湖北电视台生活频道如何培养孩子的学习兴趣直播回放在哪看?直播视频回放地址入口

湖北电视台生活频道如何培养孩子的学习兴趣直播回放在哪看?直播视频回放地址入口[多图],湖北电视台生活频道

2024-05-02
小森生活金币不够用怎么办?金币没了不够用解决方法

小森生活金币不够用怎么办?金币没了不够用解决方法[多图],小森生活金币突然就不够用的情况很多人都有,金币没

2024-05-02