jvm内存划分,作用都是什么啊?

2020-11-17 19:01发布

11条回答
是你的小甜心呀
2楼 · 2020-11-19 17:26

1 、程序计数器 ( 寄存器 )

当前线程所执行的字节码行号指示器

字节码解释器工作依赖计数器控制完成

通过执行线程行号记录,让线程轮流切换各条线程之间计数器互不影响

线程私有,生命周期与线程相同,随 JVM 启动而生, JVM 关闭而死

线程执行 Java 方法时,记录其正在执行的虚拟机字节码指令地址

线程执行 Nativan 方法时,计数器记录为空( Undefined

唯一在 Java 虚拟机规范中没有规定任何 OutOfMemoryError 情况区域

在这其中,很多不理解的没关系,我们学过多线程,有两个线程,其中一个线程可以暂停使用,让其他线程运行,然后等自己获得 cpu 资源时,又能从暂停的地方开始运行,那么为什么能够记住暂停的位置的,这就依靠了程序计数器,通过这个例子,大概了解一下程序计数器的功能。

 

2 、本地方法栈

不知道大家看过源码没有,看过的都应该知道,很多的算法或者一个功能的实现,都被 java 封装到了本地方法中,程序直接通过调用本地的方法就行了,本地方法栈就是用来存放这种方法的,实现该功能的代码可能是 C 也可能是 C++, 反正不一定就是 java 实现的。

上面两个不是我们所要学习的重点,接下来三个才是重点。

3 、虚拟机栈

这个大家都应该有所了解,现在来细讲它,虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用来存放存储局部变量表、操作数表、动态连接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。这个话怎么理解呢?比如执行一个类 ( 类中有 main 方法 ) 时,执行到 main 方法,就会把为 main 方法创建一个栈帧,然后在加到虚拟机栈中,栈帧中会存放这 main 方法中的各种局部变量,对象引用等东西


一朵大红花
3楼 · 2020-11-17 21:04

程序计数器(Program Counter):它占据的空间是比较小的,主要是描述线程执行字节码第一行执行完了之后,下一行字节码在哪。

本地方法栈:主要用于处理本地方法。

堆(Heap):这是JVM所管理最大的内存空间,在Java当中我们都是通用引用来操作对象的,而对象本身是位于堆上面,而引用则是位于虚拟机栈上面,所以引用本身是个变量,所以在Java中一定是通过引用来获取到这个对象然后去操纵它。

虚拟机栈:每一个方法在执行的过程中都会生成一个栈帧。

方法区(Method Area):存储元信息。我们对于垃圾收集器可能经常会听到一个叫永久代(Permanent Generation)的概念,所以永久代当然就是很少会被回收,所以会将方法区称为永久代,但是从JDK1.8开始,已经彻底废弃了永久代了,使用元空间(Meta Space)代替。

苏酒儿
4楼 · 2020-11-17 21:13

JVM内存区域分为五个部分,分别是堆,方法区,虚拟机栈,本地方法栈,程序计数器。

堆:存储对象的区域

栈:方法执行和存储临时变量的区域

方法区:类加载的信息 常量池 静态成员

本地方法栈: 执行本地方法

程序计数器: 判断程序到底执行哪条指令

kitidog2016
5楼 · 2020-11-18 09:12

1、程序计数器(寄存器)           

 当前线程所执行的字节码行号指示器

 字节码解释器工作依赖计数器控制完成

   通过执行线程行号记录,让线程轮流切换各条线程之间计数器互不影响

   线程私有,生命周期与线程相同,随JVM启动而生,JVM关闭而死

 线程执行Java方法时,记录其正在执行的虚拟机字节码指令地址

 线程执行Nativan方法时,计数器记录为空(Undefined)

   唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域

   在这其中,很多不理解的没关系,我们学过多线程,有两个线程,其中一个线程可以暂停使用,让其他线程运行,然后等自己获得cpu资源时,又能从暂停的地方开始运行,那么为什么能够记住暂停的位置的,这就依靠了程序计数器, 通过这个例子,大概了解一下程序计数器的功能。

 2、本地方法栈

   不知道大家看过源码没有,看过的都应该知道,很多的算法或者一个功能的实现,都被java封装到了本地方法中,程序直接通过调用本地的方法就行了,本地方法栈就是用来存放这种方法的,实现该功能的代码可能是C也可能是C++,反正不一定就是java实现的。

 上面两个不是我们所要学习的重点,接下来三个才是重点。

3、虚拟机栈

   这个大家都应该有所了解,现在来细讲它,虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用来存放存储局部变量表、操作数表、动态连接、方法出口等信息,每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。    这个话怎么理解呢?比如执行一个类(类中有main方法)时,执行到main方法,就会把为main方法创建一个栈帧,然后在加到虚拟机栈中,栈帧中会存放这main方法中的各种局部变量,对象引用等东西。如图

           

   当在main方法中调用别的方法时,就会有另一个方法的栈帧入虚拟机栈,当该方法调用完了之后,弹栈,然后main方法处于栈顶,就继续执行,直到结束,然后main方法栈帧也弹栈,程序就结束了。总之虚拟机栈中就是有很多个栈帧的入栈出栈,栈帧中存放的都市一些变量名等东西,所以我们平常说栈中存放的是一些局部变量,因为局部变量就是在方法中。也就是在栈帧中,就是这样说过来的。

   以上说的三个都是线程不共享的,也就是这部分内存,每个线程独有,不会让别的线程访问到,接下来的两个就是线程共享了,也就会出现线程安全问题。

动态链接(或指向运行时常量池的方法引用)

  • 每一个栈帧内部都包含一个指向运行时常量池中该栈帧所属方法的引用。包含这个一引用的目的就是为了支持当前方法的代码能够实现动态链接。比如:invokedynamic指令

  • 在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里,比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用

                 

public class DynamicLinkingTest {int num = 10;public void methodA(){System.out.println("DynamicLinkingTest.methodA");}public void methodB(){System.out.println("DynamicLinkingTest.methodB");methodA();num++;}public static void main(String[] args) {}}Constant pool:#1 = Methodref          #9.#27         // java/lang/Object."":()V#2 = Fieldref           #8.#28         // com/jvm/bytecode/DynamicLinkingTest.num:I#3 = Fieldref           #29.#30        // java/lang/System.out:Ljava/io/PrintStream;#4 = String             #31            // DynamicLinkingTest.methodA#5 = Methodref          #32.#33        // java/io/PrintStream.println:(Ljava/lang/String;)V#6 = String             #34            // DynamicLinkingTest.methodB#7 = Methodref          #8.#35         // com/jvm/bytecode/DynamicLinkingTest.methodA:()V#8 = Class              #36            // com/jvm/bytecode/DynamicLinkingTest#9 = Class              #37            // java/lang/Object#10 = Utf8               num#11 = Utf8               I#12 = Utf8               #13 = Utf8               ()V#14 = Utf8               Code#15 = Utf8               LineNumberTable#16 = Utf8               LocalVariableTable#17 = Utf8               this#18 = Utf8               Lcom/jvm/bytecode/DynamicLinkingTest;#19 = Utf8               methodA#20 = Utf8               methodB#21 = Utf8               main#22 = Utf8               ([Ljava/lang/String;)V#23 = Utf8               args#24 = Utf8               [Ljava/lang/String;#25 = Utf8               SourceFile#26 = Utf8               DynamicLinkingTest.java#27 = NameAndType        #12:#13        // "":()V#28 = NameAndType        #10:#11        // num:I#29 = Class              #38            // java/lang/System#30 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;#31 = Utf8               DynamicLinkingTest.methodA#32 = Class              #41            // java/io/PrintStream#33 = NameAndType        #42:#43        // println:(Ljava/lang/String;)V#34 = Utf8               DynamicLinkingTest.methodB#35 = NameAndType        #19:#13        // methodA:()V#36 = Utf8               com/jvm/bytecode/DynamicLinkingTest#37 = Utf8               java/lang/Object#38 = Utf8               java/lang/System#39 = Utf8               out#40 = Utf8               Ljava/io/PrintStream;#41 = Utf8               java/io/PrintStream#42 = Utf8               println#43 = Utf8               (Ljava/lang/String;)V{int num;descriptor: Iflags:public com.jvm.bytecode.DynamicLinkingTest();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1                  // Method java/lang/Object."":()V4: aload_05: bipush        107: putfield      #2                  // Field num:I10: returnLineNumberTable:line 3: 0line 5: 4LocalVariableTable:Start  Length  Slot  Name   Signature0      11     0  this   Lcom/jvm/bytecode/DynamicLinkingTest;public void methodA();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #4                  // String DynamicLinkingTest.methodA5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 8: 0line 9: 8LocalVariableTable:Start  Length  Slot  Name   Signature0       9     0  this   Lcom/jvm/bytecode/DynamicLinkingTest;public void methodB();descriptor: ()Vflags: ACC_PUBLICCode:stack=3, locals=1, args_size=10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #6                  // String DynamicLinkingTest.methodB5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: aload_09: invokevirtual #7                  // Method methodA:()V12: aload_013: dup14: getfield      #2                  // Field num:I17: iconst_118: iadd19: putfield      #2                  // Field num:I22: returnLineNumberTable:line 12: 0line 13: 8line 14: 12line 15: 22LocalVariableTable:Start  Length  Slot  Name   Signature0      23     0  this   Lcom/jvm/bytecode/DynamicLinkingTest;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=0, locals=1, args_size=10: returnLineNumberTable:line 19: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       1     0  args   [Ljava/lang/String;}

方法调用

在jvm中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关

  • 静态链接:当一个字节码文件被装载进jvm内部时,如果被调用的目标方法在编译期可知,且运行期保持不变时,这种情况下将调用方法的符号引用转为直接引用的过程称之为静态链接。

  • 动态链接:如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称为动态链接

 

4、堆

   所有线程共享的一块内存区域。Java虚拟机所管理的内存中最大的一块,因为该内存区域的唯一目的就是存放对象实例。几乎所有的对象实例度在这里分配内存,也就是通常我们说的new对象,该对象就会在堆中开辟一块内存来存放对象中的一些信息,比如属性呀什么的。同时堆也是垃圾收集器管理的主要区域。因此很多时候被称为"GC堆",虚拟机的垃圾回收机制等下一篇文章来讲解。 在上一点讲的栈中存放的局部引用变量所指向的大多数度会在堆中存放。

5、方法区和其中的运行时常量池

   和堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、和编译器编译后的代码(也就是存储字节码文件。.class)等数据,这里可以看到常量也会在方法区中,是因为方法区中有一个运行时常量池,为什么叫运行时常量池,因为在编译后期生成的是各种字面量(字面量的意思就是值,比如int i=3,这个3就是字面量的意思)和符号引用,这些是存放在一个叫做常量池(这个常量池是在字节码文件中)的地方,当类加载进入方法区时,就会把该常量池中的内容放入运行时常量池中。这里要注意,运行时常量池和常量池,不要搞混淆了,字节码文件中也有常量池,在后面的章节会详细讲解这个东西。现在只需要知道方法区中有一个运行时常量池,就是用来存放常量的。还有一点,运行时常量池不一定就一定要从字节码常量池中拿取常量,可能在程序运行期间将新的常量放入池中,比如String.intern()方法,这个方法的作用就是:先从方法区的运行时常量池中查找看是否有该值,如果有,则返回该值的引用,如果没有,那么就会将该值加入运行时常量池中。   

二、练习。画内存图。

     平常分析中用到的最多还是堆、虚拟机栈和方法区。

     例如:看下面这段程序,然后画出内存分析图        

  

 最主要是看我的分析过程,这个图由于要显示出动态弹栈画不了,所以只能够那样画一下了。

  1、首先运行程序,Demo1_car.java就会变为Demo1_car.class,将Demo1_car.class加入方法区,检查是否字节码文件常量池中是否有常量值,如果有,那么就加入运行时常量池

  2、遇到main方法,创建一个栈帧,入虚拟机栈,然后开始运行main方法中的程序

  3、Car c1 = new Car(); 第一次遇到Car这个类,所以将Car.java编译为Car.class文件,然后加入方法区,跟第一步一样。然后new Car()。就在堆中创建一块区域,用于存放创建出来的实例对象,地址为0X001.其中有两个属性值 color和num。默认值是null 和 0

  4、然后通过c1这个引用变量去设置color和num的值,

  5、调用run方法,然后会创建一个栈帧,用来装run方法中的局部变量的,入虚拟机栈,run方法中就打印了一句话,结束之后,该栈帧出虚拟机栈。又只剩下main方法这个栈帧了

  6、接着又创建了一个Car对象,所以又在堆中开辟了一块内存,之后就是跟之前的步骤一样了。

 

  这样就分析结束了,在脑袋中就应该有一个大概的认识对堆、虚拟机栈、和方法区。注意这个方法区的名字,并不是就单单装方法的,能装很多东西。

  这个只是一个简单的分析,可以再讲具体一点,1、创建对象,在堆中开辟内存时是如何分配内存的?2、对象引用是如何找到我们在堆中的对象实例的?通过这两个问题来加深我们的理解。

 1、创建对象,在堆中开辟内存时是如何分配内存的?

       两种方式:指针碰撞和空闲列表。我们具体使用的哪一种,就要看我们虚拟机中使用的是什么了。

       指针碰撞:假设Java堆中内存是绝对规整的,所有用过的内存度放一边,空闲的内存放另一边,中间放着一个指针作为分界点的指示器,所分配内存就仅仅是把哪个指针向空闲空间那边挪动一段与对象大小相等的举例,这种分配方案就叫指针碰撞

       空闲列表:有一个列表,其中记录中哪些内存块有用,在分配的时候从列表中找到一块足够大的空间划分给对象实例,然后更新列表中的记录。这就叫做空闲列表

 2、对象引用是如何找到我们在堆中的对象实例的?     

       这个问题也可以称为对象的访问定位问题,也有两种方式。句柄访问和直接指针访问。 画两张图就明白了。

       句柄访问:Java堆中会划分出一块内存来作为句柄池,引用变量中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息

              

 解释图:在栈中有一个引用变量指向句柄池中一个句柄的地址,这个句柄又包含了两个地址,一个对象实例数据,一个是对象类型数据(这个在方法区中,因为类字节码文件就放在方法区中),

      

 直接指针访问:引用变量中存储的就直接是对象地址了,如图所示

            

 解释:在堆中就不会分句柄池了,直接指向了对象的地址,对象中包含了对象类型数据的地址。

 

区别:这两种各有各的优势,

   使用句柄来访问的最大好处就是引用变量中存储的是稳定的句柄地址,对象被移动(在垃圾收集时移动对象是很普通的行为)时就会改变句柄中实力数据指针,但是引用变量所指向的地址不用改变。

   而使用直接指针访问方式最大的好处就是速度更快,节省了一次指针定位的时间开销,但是在对象被移动时,又需要改变引用变量的地址。在我们上面分析的例子中,就是使用的直接指针访问的方式。


哆啦公
6楼 · 2020-11-18 09:24

把JVM看成是个中间层就可以,不止是分配,还有线程、网络连接等,最终在底层都要靠操作系统来搞。

Java语言的设计思想,本来就是对C语言这种可以直接进行操作系统调用的语言的一种简化。引入了一个隔离层,让jvm来当个中介,以简化应用开发。让程序员集中精力于实现业务逻辑。


aijingda
7楼 · 2020-11-18 09:29

JVM内存空间主要有以下几部分

1、虚拟栈

  每个方法执行都生成一个独有的栈针(Stack Frame),栈针用于存储方法执行相关的内容,比如操作数栈、局部变量表、方法返回地址等。

2、程序计数器(Program Counter)

3、本地方法栈(就是native方法,其是用C++或C实现的),主要用于处理本地方法,其中Oracle的HotSpot虚拟机就是将虚拟机栈与本地方法栈合二为一了,它并没有严格的区分这两方面的内容,因为虚拟机栈与本地方法栈的数据结构都是类似的。

4、堆(Head):new出来的对象是存在堆中,对象操作就是通过操作局部变量表的变量(引用)来实现的,例如以下代码

Object a = new Object(); 

  a在这里是一个引用(变量),其是位于虚拟栈中的局部变量表中局部变量,这个Object实例是位于堆中的,要操作这个对象可以通过操作a变量来实现。

 

  指向实例对象有两种实现方式

  1)对象引用实例数据(如成员变量),引用元数据(meta,如class)

  2)对象存储实例数据(如成员变量),引用元数据(meta,如class)

  具体如下图所示↓

  下载.png

  这两种实现方式都有一个指针指向方法区的元数据(类型),HotSpot虚拟机采用就是第二种,主要有这样考虑,当对象移动了,指针也会变,采用第一种方式,就会导致指向实例对象的指针随着垃圾回收或压缩操作而不断变化,变化是相当高的,而第二种就不会存在这种问题。

4、方法区(Method Area):用于存储元信息,例如一些常量、静态变量还有类本身固有的信息 。

  在一些文章中经常提到永久代(Permanent Generation),永久代的数据很少被回收,这个在jdk1.7之前,jvm也是这么做的,基于这个考虑部分人把方法区称为永久代,其实这两者不是完全等价的,对于永久代来说,因为涉及到常量池相关的一些概念,有可能出现内存溢出的可能性,所有从jdk8开始,再也没有永久代这部分内存了,而使用元空间(meta space)代替。

5、运行时常量池:方法区的一部分内容,编译class时生成的一些字面量、符号引用啥,这些内容在进行加载完之后都会进入方法区的运行时常量池中存放。

6、直接内存(Direct Memory)也就是堆外内存,这个内存并不是由jvm直接管理的,而是由操作系统来管理的。对直接内存的回收、释放成本是比较高的,所有对于netty来说,开辟的直接内存都交给Direct Byte Buffer来操作。


魏魏姐
8楼 · 2020-11-18 09:54

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干不同的数据区域,这些区域都有各自的用途以及创建和销毁的时间。Java虚拟机所管理的内存将会包括以下几个运行时数据区域,如下图(图片来源网上)所示:


不吃鱼的猫
9楼 · 2020-11-18 10:35

堆:存储对象的区域,栈:方法执行和存储临时变量的区域,方法区:类加载的信息常量池静态成员,本地方法栈:执行本地方法,程序计数器:判断程序到底执行哪条指令


相关问题推荐

  • 回答 2

    Statement的execute(String query)方法用来执行任意的SQL查询,如果查询的结果是一个ResultSet,这个方法就返回true。如果结果不是ResultSet,比如insert或者update查询,它就会返回false。我们可以通过它的getResultSet方法来获取ResultSet,或者通过getUpda...

  • 回答 22

    忙的时候项目期肯定要加班 但是每天加班应该还不至于

  • 回答 108
    已采纳

    虽然Java人才越来越多,但是人才缺口也是很大的,我国对JAVA工程师的需求是所有软件工程师当中需求大的,达到全部需求量的60%-70%,所以Java市场在短时间内不可能饱和。其次,Java市场不断变化,人才需求也会不断增加。马云说过,未来的制造业要的不是石油,...

  • 回答 5
    已采纳

    工信部证书含金量较高。工信部是国务院的下属结构,具有发放资质、证书的资格。其所发放的证书具有较强的权威性,在全国范围内收到认可,含金量通常都比较高。 工信部证书,其含义也就是工信部颁发并承认的某项技能证书,是具有法律效力的,并且是国家认可的...

  • 回答 70
    已采纳

    学Java好不好找工作?看学完Java后能做些什么吧。一、大数据技术Hadoop以及其他大数据处理技术都是用Java或者其他,例如Apache的基于Java 的 HBase和Accumulo以及ElasticSearchas。但是Java在此领域并未占太大空间,但只要Hadoop和ElasticSearchas能够成长壮...

  • 回答 16
    已采纳

    就是java的基础知识啊,比如Java 集合框架;Java 多线程;线程的五种状态;Java 虚拟机;MySQL (InnoDB);Spring 相关;计算机网络;MQ 消息队列诸如此类

  • 回答 12

    #{}和${}这两个语法是为了动态传递参数而存在的,是Mybatis实现动态SQL的基础,总体上他们的作用是一致的(为了动态传参),但是在编译过程、是否自动加单引号、安全性、使用场景等方面有很多不同,下面详细比较两者间的区别:1.#{} 是 占位符 :动态解析 ...

  • 回答 62

    没问题的,专科学历也能学习Java开发的,主要看自己感不感兴趣,只要认真学,市面上的培训机构不少都是零基础课程,能跟得上,或是自己先找些资料学习一下。

  • 回答 4

    1、反射对单例模式的破坏采用反射的方式另辟蹊径实例了该类,导致程序中会存在不止一个实例。解决方案其思想就是采用一个全局变量,来标记是否已经实例化过了,如果已经实例化过了,第 二次实例化的时候,抛出异常2、clone()对单例模式的破坏当需要实现单例的...

  • 回答 5

     优点: 一、实例控制  单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。 二、灵活性  因为类控制了实例化过程,所以类可以灵活更改实例化过程。 缺点: 一、开销  虽然数量很少,但如果每次对象请求引用时都要...

  • 回答 4

    这个主要是看你数组的长度是多少, 比如之前写过的一个程序有个数组存的是各个客户端的ip地址:string clientIp[4]={XXX, xxx, xxx, xxx};这个时候如果想把hash值对应到上面四个地址的话,就应该对4取余,这个时候p就应该为4...

  • 回答 6

     哈希表的大小 · 关键字的分布情况 · 记录的查找频率 1.直接寻址法:取关键字或关键字的某个线性函数值为散列地址。即H(key)=key或H(key) = a·key + b,其中a和b为常数(这种散列函数叫做自身函数)。...

  • 回答 6

    哈希表的大小取决于一组质数,原因是在hash函数中,你要用这些质数来做模运算(%)。而分析发现,如果不是用质数来做模运算的话,很多生活中的数据分布,会集中在某些点上。所以这里最后采用了质数做模的除数。 因为用质数做了模的除数,自然存储空间的大小也用质数了...

  • 回答 2

    是啊,哈希函数的设计至关重要,好的哈希函数会尽可能地保证计算简单和散列地址分布均匀,但是,我们需要清楚的是,数组是一块连续的固定长度的内存空间

  • 回答 3

     解码查表优化算法,seo优化

  • 回答 5

    1.对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。2.哈希值就是这个元素的位置。3.如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就...

没有解决我的问题,去提问