1 什么是JVM
JVM是JavaVirtualMachine(Java捏造机)的缩写,JVM是一种用于盘算装备的规范,它是一个捏造出来的盘算机,是通过在实际的盘算机上仿真模仿各种盘算机功能来实现的。
2. JVM与利用体系
为什么要在步伐和利用体系中间添加一个JVM? Java是一门抽象程度特殊高的语言,提供了主动内存管理等一系列的特性。这些特性直接在利用体系上实现是不太大概的,以是就须要JVM举行一番转换。
从图中可以看到,有了JVM这个抽象层之后,Java就可以实现跨平台了。JVM只须要包管可以或许精确实验.class文件,就可以运行在诸如Linux、Windows、MacOS等平台上了。 而Java跨平台的意义在于一次编译,到处运行,可以或许做到这一点JVM功不可没。比如我们在Maven堆栈下载同一版本的jar包就可以到处运行,不须要在每个平台上再编译一次。 现在的一些JVM的扩展语言,比如Clojure、JRuby、Groovy等,编译到末了都是.class文件,Java语言的维护者,只须要控制好JVM这个剖析器,就可以将这些扩展语言无缝的运行在JVM之上了。
应用步伐、JVM、利用体系之间的关系
JVM上承开发语言,下接利用体系,它的中间接口就是字节码。
3. JVM、JRE、JDK的关系
JVM是Java步伐可以或许运行的焦点。但须要注意,JVM本身什么也干不了,你须要给它提供生产质料(.class文件)。 仅仅是JVM,是无法完成一次编译,到处运行的。它须要一个根本的类库,比如怎么利用文件、怎么毗连网络等。而Java体系很慷慨,会一次性将JVM运行所需的类库都转达给它。JVM尺度加上实现的一大堆根本类库,就构成了Java的运行时环境,也就是我们常说的JRE(JavaRuntimeEnvironment) 对于JDK来说,就更巨大了一些。除了JRE,JDK还提供了一些非常好用的小工具,比如javac、java、jar等。它是Java开发的焦点。 我们也可以看下JDK的全拼,JavaDevelopmentKit。JVM、JRE、JDK它们三者之间的关系,可以用一个包罗关系表现。
4. Java捏造机规范和Java语言规范的关系
左半部分是Java捏造机规范,着实就是为输入和实验字节码提供一个运行环境。右半部分是我们常说的Java语法规范,比如switch、for、泛型、lambda等干系的步伐,终极都会编译成字节码。而毗连左右两部分的桥梁依然是Java的字节码。 如果.class文件的规格是稳定,这两部分是可以独立举行优化的。但Java也会偶尔扩充一下.class文件的格式,增长一些字节码指令,以便支持更多的特性。 我们简单看一下一个Java步伐的实验过程,它到底是怎样运行起来的。
这里的Java步伐是文本格式的。比如下面这段HelloWorld.java,它遵照的就是Java语言规范。此中,我们调用了System.out等模块,也就是JRE里提供的类库。
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World!"); }}利用JDK的工具javac举行编译后,会产生HelloWorld的字节码。 我们不绝在说Java字节码是沟通JVM与Java步伐的桥梁,下面利用javap来稍微看一下字节码到底长什么样子。
0 getstatic#2<java/lang/System.out> //getstatic获取静态字段的值3 ldc#3<HelloWorld> //ldc常量池中的常量值入栈5 invokevirtual#4<java/io/PrintStream.println> //invokevirtual运行时方法绑定调用方法8 return //void函数返回Java捏造机接纳基于栈的架构,其指令由利用码和利用数构成。这些字节码指令,就叫作opcode。此中, getstatic、ldc、invokevirtual、return等,就是opcode,可以看到是比力轻易明白的。 JVM就是靠剖析这些opcode和利用数来完成步伐的实验的。当我们利用Java命令运行.class文件的时间,实际上就相当于启动了一个JVM历程。然后JVM会翻译这些字节码,它有两种实验方式。常见的就是表明实验,将opcode+利用数翻译成呆板代码;别的一种实验方式就是JIT,也就是我们常说的即时编译,它会在肯定条件下将字节码编译成呆板码之后再实验。
Java语言的一个非常紧张的特点就是与平台的无关性。而利用Java捏造机(JVM)是实现这一特点的关键。JVM是一种用于盘算装备的规范,它是一个捏造出来的盘算机,是通过在实际的盘算机上仿真模仿各种盘算机功能来实现的。之前市场上紧张有三种主流的JVM,
- Sun公司的HotSpot
- BEA公司的JRockit
- IBM公司的J9 JVM
厥后Sun公司和BEA公司都被oracle收购,而且oracle接纳Sun公司的HotSpot和BEA公司的JRockit两个JVM中英华退出了 jdk1.8 的JVM。
下图是JVM的结构,非常紧张,尤其是运行时地区。
运行时数据区
堆
JVM中最大的一块,紧张用来存放对象实例和数组,险些全部的对象实例都在这里分配内存。线程共享,内部会分别出多个线程私有的分配缓冲区(TLAB)。可以位于物理上不一连的空间,但是逻辑上要一连。Java堆(Java Heap)是Java捏造机所管理的内存中最大的一块。Java堆是被全部线程共享的内存地区,在捏造机启动的时间创建。
此内存地区的目标就是存储对象实例,险些全部的对象实例都在这里分配内存。从采取内存的角度看,由于当代垃圾网络器大部分都是基于分署理论计划的,以是Java堆中常常出现“新生代”、“老年代”、“永世代”、“Eden空间”、“From Survivor空间”、“To Survivor空间”等名词,这些地区分别仅仅是部分垃圾网络器的共同特性大概计划风格,而非某个Java捏造机的详细实现的固有内存结构,更不是《Java捏造机规范》里对Java堆的进一步分别。后边讲到的G1垃圾网络器就不是基于分署理论计划的。
Java堆是线程共享的,它的目标是存放对象实例。同时它也是GC所管理的紧张地区,因此常被称为GC堆。根据捏造机规范,Java堆可以存在物理上不一连的内存空间,就像磁盘空间逻辑上是一连的即可。它的内存巨细可以设置为固定巨细,也可以扩展。当前主流的捏造机如HotSpot都能按扩展实现(通过设置 -Xmx和-Xms,默认堆内存巨细为服务器内存的1/4),如果堆中没有内存完成实例分配,而且堆无法扩展,则会报OOM错误(OutOfMemoryError)。
2、捏造机栈
每个方法被实验的时间都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、利用栈、动态链接、方法出口等信息。每一个方法被调用直至实验完成的过程,就对应着一个栈帧在捏造机栈中从入栈到出栈的过程。线程私有,生命周期和线程划一。捏造机栈是每个Java方法的内存模子:每个方法被实验的时间都会创建一个"栈帧",用于存储局部变量表(包罗参数)、利用栈、方法出口等信息。每个方法被调用到实验完成的过程,就对应着一个栈帧在捏造机栈从入栈到出栈的过程。
寻常说的栈一样寻常指的是局部变量表部分。局部变量表存放了编译期可知的各种Java捏造机根本数据范例(boolean、byte、char、short、int、float、long、double)、对象引用(reference范例,它并不等同于对象本身,大概是一个指向对象起始地点的引用指针,也大概是指向一个代表对象的句柄大概其他与此对象干系的位置)和returnAddress范例(指向了一条字节码指令的地点),这些数据范例在局部变量表中以槽(slot)来表现,此中64位长度的long和double范例的数据会占用两个变量槽,别的的数据范例只占用一个。
局部变量表所须要的空间在编译期完身分配,当实验一个方法的时间,该方法须要在栈帧中分配多大的局部变量表的空间完满是可以确定的,因此在方法运行的期间不会改变局部变量表的巨细,这里说的“巨细”是指变量槽的数量,捏造机真正利用多大的内存空间来实现一个变量槽是由详细的捏造机实现自行决定的事变。
该地区就是我们常说的Java内存中的栈地区,该地区的局部变量表存储的是根本范例、对象的引用范例,在对象的引用范例中存储的是指向对象的堆空间的地点。
本地方法栈
本地方法栈(Native Method Stacks)与捏造机栈发挥的作用好坏常相似的,其区别不外是捏造机栈为捏造机实验Java方法(字节码)服务,本地方法栈为捏造机利用到的native方法分为,底层调用的是C大概C++的方法。
《Java捏造机规范》中对本地方法栈中方法利用的语言、利用方式与数据结构没有任何逼迫规定,因此详细的捏造机可以根据须要自由实现它,HotSpot捏造机直接就把本地方法栈和捏造机栈合二为一来利用,与捏造机栈一样本地方法栈也会在栈深度溢出大概栈扩展失败时分别抛出StackOverflowError和OutOfMemoryError非常。
方法区(非堆)
属于共享内存地区,存储已被捏造机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个抽象的概念,JDK7及之前被称为“永世代”,JDK8及以后被称为“元空间”,它用于存储捏造机加载的范例信息、常量、静态变量(JDK7及之前,JDK8及之后就把静态变量与Class对象放到了堆中)、即时编译器编译(JIT)后的代码等数据,是各个线程的共享内存地区。固然《Java捏造机规范》中把方法区形貌为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目标是与Java堆区分开来。
JDK8之前,很多人把方法区又称为永世代(Permanent Generation),或将两者等量齐观,本质上是不对等的,因为仅仅是当时的HotSpot捏造机计划团队把网络器的分代扩展至方法区,大概说利用永世代来实现方法区而已,如许使得HotSpot的垃圾网络器可以或许像管理Java堆一样管理这部分内存,省去了专门为方法区编写内存管理代码的工作。但是这种计划更会导致Java应用更轻易遇到内存溢出的问题(永世代有-XX:MaxPermSize的上限,纵然不设置也有默认巨细,而像J9和JRockit只要没有触遇到历程可用内存的上限,就不会有问题),在JDK6的时间HotSpot团队就有放弃方法区,逐步改为接纳本地内存(Native Memory)来实现方法区的计划,到了JDK7已经把本来放在永世代的字符串常量池、静态变量等移出,到了JDK8就完全废除了永世代的概念,改用JRockit、J9一样在本地内存中实现的元空间(Meta-space),把JDK7中永世代剩余的内容(紧张是范例信息)全部移到元空间中。
方法区存储每个类信息,如:
在JDK8之前的HotSpot JVM,存放这些“永世的”地区叫做“永世代(permanent generation)”。永世代是一片一连的堆空间,在JVM启动前通过在命令行设置参数-XX:MaxPermSize来设定永世代最大可分配的内存空间,默认巨细为64M(64位的JVM默认是85M)。
方法区或永生代干系参数设置
- -XXermSize=64MB最小尺寸,初始分配
- -XX:MaxPermSize=256最大答应分配尺寸
- 按需分配-XX:+CMSClassUnloadingEnabled
- -XX:+CMSPermGenSweepingEnabled设置垃圾不采取
- -server选项下默认MaxPermSize为64MB,-client选项下默认MaxPermSize为32MB
Java捏造机规范堆方法区限定非常的宽松,可选择不垃圾采取,以及不须要一连的内存和可扩展的巨细。这个地区紧张是针对于常量池的采取以及对范例的卸载,当方法区无法分配到充足的内存的时间也会报OOM。
常量池
Class文件常量池
以下利用实际代码及反编译Class文件解说
反编译命令:javap -verbose StringTest.class
public class StringTest { private static String s1 = "static"; public static void main(String[] args) { String hello1 = new String("hell") + new String("o"); String hello2 = new String("he") + new String("llo"); String hello3 = hello1.intern(); String hello4 = hello2.intern(); System.out.println(hello1 == hello3); System.out.println(hello1 == hello4); }}Classfile /E:/workspace/VariousCases/target/classes/cn/onenine/jvm/constantpool/StringTest.class
Last modified 2021-8-3; size 1299 bytes MD5 checksum 338bd0034155ec3bf8d608540a31761c Compiled from "StringTest.java"public class cn.onenine.jvm.constantpool.StringTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPERConstant pool: #1 = Class #2 // cn/onenine/jvm/constantpool/StringTest #2 = Utf8 cn/onenine/jvm/constantpool/StringTest #3 = Class #4 // java/lang/Object #4 = Utf8 java/lang/Object #5 = Utf8 s1 #6 = Utf8 Ljava/lang/String; #7 = Utf8 <clinit> #8 = Utf8 ()V #9 = Utf8 Code #10 = String #11 // static #11 = Utf8 static #12 = Fieldref #1.#13 // cn/onenine/jvm/constantpool/StringTest.s1java/lang/String; #13 = NameAndType #5:#6 // s1java/lang/String; #14 = Utf8 LineNumberTable #15 = Utf8 LocalVariableTable #16 = Utf8 <init> #17 = Methodref #3.#18 // java/lang/Object."<init>")V #18 = NameAndType #16:#8 // "<init>")V #19 = Utf8 this #20 = Utf8 Lcn/onenine/jvm/constantpool/StringTest; #21 = Utf8 main #22 = Utf8 ([Ljava/lang/String;)V #23 = Class #24 // java/lang/StringBuilder #24 = Utf8 java/lang/StringBuilder #25 = Class #26 // java/lang/String #26 = Utf8 java/lang/String #27 = String #28 // hell #28 = Utf8 hell #29 = Methodref #25.#30 // java/lang/String."<init>"Ljava/lang/String;)V #30 = NameAndType #16:#31 // "<init>"Ljava/lang/String;)V #31 = Utf8 (Ljava/lang/String;)V #32 = Methodref #25.#33 // java/lang/String.valueOfLjava/lang/Object;)Ljava/lang/String; #33 = NameAndType #34:#35 // valueOfLjava/lang/Object;)Ljava/lang/String; #34 = Utf8 valueOf #35 = Utf8 (Ljava/lang/Object;)Ljava/lang/String; #36 = Methodref #23.#30 // java/lang/StringBuilder."<init>"Ljava/lang/String;)V #37 = String #38 // o #38 = Utf8 o #39 = Methodref #23.#40 // java/lang/StringBuilder.appendLjava/lang/String;)Ljava/lang/StringBuilder; #40 = NameAndType #41:#42 // appendLjava/lang/String;)Ljava/lang/StringBuilder; #41 = Utf8 append #42 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #43 = Methodref #23.#44 // java/lang/StringBuilder.toString)Ljava/lang/String; #44 = NameAndType #45:#46 // toString:()Ljava/lang/String; #45 = Utf8 toString #46 = Utf8 ()Ljava/lang/String; #47 = String #48 // he #48 = Utf8 he #49 = String #50 // llo #50 = Utf8 llo #51 = Methodref #25.#52 // java/lang/String.intern:()Ljava/lang/String; #52 = NameAndType #53:#46 // intern:()Ljava/lang/String; #53 = Utf8 intern #54 = Fieldref #55.#57 // java/lang/System.outjava/io/PrintStream; #55 = Class #56 // java/lang/System #56 = Utf8 java/lang/System #57 = NameAndType #58:#59 // outjava/io/PrintStream; #58 = Utf8 out #59 = Utf8 Ljava/io/PrintStream; #60 = Methodref #61.#63 // java/io/PrintStream.println:(Z)V #61 = Class #62 // java/io/PrintStream #62 = Utf8 java/io/PrintStream #63 = NameAndType #64:#65 // println:(Z)V #64 = Utf8 println #65 = Utf8 (Z)V #66 = Utf8 args #67 = Utf8 [Ljava/lang/String; #68 = Utf8 hello1 #69 = Utf8 hello2 #70 = Utf8 hello3 #71 = Utf8 hello4 #72 = Utf8 StackMapTable #73 = Class #67 // "[Ljava/lang/String;" #74 = Utf8 SourceFile #75 = Utf8 StringTest.java{ static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #10 // String static 2: putstatic #12 // Field s1java/lang/String; 5: return LineNumberTable: line 11: 0 LocalVariableTable: Start Length Slot Name Signature public cn.onenine.jvm.constantpool.StringTest(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #17 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcn/onenine/jvm/constantpool/StringTest; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=5, locals=5, args_size=1 0: new #23 // class java/lang/StringBuilder 3: dup 4: new #25 // class java/lang/String 7: dup 8: ldc #27 // String hell 10: invokespecial #29 // Method java/lang/String."<init>":(Ljava/lang/String;)V 13: invokestatic #32 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 16: invokespecial #36 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 19: new #25 // class java/lang/String 22: dup 23: ldc #37 // String o 25: invokespecial #29 // Method java/lang/String."<init>":(Ljava/lang/String;)V 28: invokevirtual #39 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: invokevirtual #43 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 34: astore_1 35: new #23 // class java/lang/StringBuilder 38: dup 39: new #25 // class java/lang/String 42: dup 43: ldc #47 // String he 45: invokespecial #29 // Method java/lang/String."<init>":(Ljava/lang/String;)V 48: invokestatic #32 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String; 51: invokespecial #36 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 54: new #25 // class java/lang/String 57: dup 58: ldc #49 // String llo 60: invokespecial #29 // Method java/lang/String."<init>":(Ljava/lang/String;)V 63: invokevirtual #39 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 66: invokevirtual #43 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 69: astore_2 70: aload_1 71: invokevirtual #51 // Method java/lang/String.intern:()Ljava/lang/String; 74: astore_3 75: aload_2 76: invokevirtual #51 // Method java/lang/String.intern:()Ljava/lang/String; 79: astore 4 81: getstatic #54 // Field java/lang/System.outjava/io/PrintStream; 84: aload_1 85: aload_3 86: if_acmpne 93 89: iconst_1 90: goto 94 93: iconst_0 94: invokevirtual #60 // Method java/io/PrintStream.println:(Z)V 97: getstatic #54 // Field java/lang/System.outjava/io/PrintStream; 100: aload_1 101: aload 4 103: if_acmpne 110 106: iconst_1 107: goto 111 110: iconst_0 111: invokevirtual #60 // Method java/io/PrintStream.println:(Z)V 114: return LineNumberTable: line 13: 0 line 14: 35 line 15: 70 line 16: 75 line 17: 81 line 18: 97 line 20: 114 LocalVariableTable: Start Length Slot Name Signature 0 115 0 args [Ljava/lang/String; 35 80 1 hello1 Ljava/lang/String; 70 45 2 hello2 Ljava/lang/String; 75 40 3 hello3 Ljava/lang/String; 81 34 4 hello4 Ljava/lang/String; StackMapTable: number_of_entries = 4 frame_type = 255 /* full_frame */ offset_delta = 93 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream, int ] frame_type = 79 /* same_locals_1_stack_item */ stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream, int ]}SourceFile: "StringTest.java"
运行时常量池
运行时常量池是方法区的一部分,Class文件中除了有类的版本、字段、方法、接口等信息外,另有一项信息是常量表(Constant Pool Table),用于存放编译器天生的各种字面量和引用符号,这部分内容将在类加载后放到运行时常量池中。
运行时常量池相对于Class文件常量池的别的一个特性是具备动态性,Java语言并不要求常量肯定只有编译期才华产生,也就是说并非预置入Class文件中常量池的内容才华进入方法区运行时常量池,运行时期也可以将新的常量放入运行时常量池,如String#intern()方法。
既然运行时常量池是方法区的一部分,天然受到方法区的内存的限定,当常量池无法再申请到内存时,就会抛出OutOfMemoryErro非常。
全局字符串常量池
HotSpot VM里,纪录intered字符串的一个全局表叫做String Table,它本质上就是一个HashSet,是一个纯运行时的结构,而且是惰性维护的。
只存储对java.lang.String实例的引用,而不存储实际的String对象,根据这个引用可以找到实际的String对象。
直接内存
直接内存(Direct Memory)并不是捏造机运行时数据区的一部分,也不是Java捏造机规范中界说的内存地区,但是这部分内存也被频仍地利用,而且也大概导致OutOfMemoryError 非常出现。在JDK 1.4 中新到场了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以利用Native函数库直接分配堆外内存,然后通过一个存储在Java堆内里的DirectByteBuffer 对象作为这块内存的引用举行利用。如许能在一些场景中显著进步性能,因为克制了在Java 堆和Native 堆中来回复制数据。直接内存并不是捏造机运行时数据区的一部分,也不是《Java捏造机规范》中界说的内存地区,但是这部分内存也被频仍利用,而且也大概导致OOM。
本机内存直接内存分配不会受到Java堆巨细的限定,但是既然是内存,肯定会受到物理机内存的限定,当我们通过-Xmx设置堆的最大内存时,不要忘了另有直接内存,如果堆内存设置过大,将会导致直接内存不够用,导致动态扩展时发生OOM。
直接内存的容量巨细可以通过-XX:MaxDirectMemorySize参数来指定,如果不指定,则默认与Java堆的最大值(-Xmx)划一。
直接内存导致的OOM不会在Heap Dump文件中看到什么显着的非常,如果发现内存溢出后的Dump文件很小而步伐中又直接或间接利用了DirectMemory(范例的间接利用就是NIO),那就可以思量重点查抄一下直接内存方面的缘故起因了。
堆的分代网络
JVM有多种垃圾采取算法,此中现在在用最经典的就是分代网络算法,这里优先纪录下。
- 永世代(Perm):紧张生存class,method,field等对象,该空间巨细,取决于体系启动加载类的数量,一样寻常该地区内存溢出均是启动时溢出。java.lang.OutOfMemoryError: PermGen space
- 老年代(Old):一样寻常是颠末多次垃圾采取(GC)没有被采取掉的对象。
- 新生代(Eden):新创建的对象。
- 新生代(Survivor0):颠末垃圾采取(GC)后,没有被采取掉的对象。
- 新生代(Survivor1):同Survivor0类似,巨细空间也类似,同一时间Survivor0和Survivor1只有一个在用,一个为空。
Java堆是垃圾网络器管理的紧张地区,按照分代网络算法的分别,堆内存空间可以继承细分为年轻代,老年代。年轻代又可以分别为较大的Eden区,两个划一巨细的From Survivor,To Survivor区。默认的Eden区和Survivor区的巨细比例为8:1:1。在为新创建的对象分配内存的时间先将对象分配到Eden区和From Survivor区,在立刻采取时,会将Eden区和Survivor区还存活的对象复制到To Survivor区中,如果To Survivor区的巨细不能容纳存活的对象,会把存活的对象分配到老年区。总体来说,新创建的小对象会放在年轻代,年轻代的对象大多在下一次垃圾采取时被采取,老年代存储大的对象和存活时间长的对象。
附录:干系概念
1、根本范例和引用范例
JVM中,数据范例可以分为两类:根本范例和引用范例。根本范例的变量生存原始值,即:他代表的值就是数值本身;而引用范例的变量生存引用值。“引用值”代表了某个对象的引用,而不是对象本身,对象本身存放在这个引用值所表现的地点的位置。
2、堆和栈
栈是运行时的单位,而堆是存储的单位。在Java中一个线程就会相应有一个线程栈与之对应,这点很轻易明白,因为差别的线程实验逻辑有所差别,因此须要一个独立的线程栈。而堆则是全部线程共享的。栈因为是运行单位,因此内里存储的信息都是跟当前线程(或步伐)干系信息的。包罗局部变量、步伐运行状态、方法返回值等等;而堆只负责存储对象信息。
**栈代表了处置惩罚逻辑,而堆代表了数据。 **
堆中存的是对象,栈中存的是根本数据范例和堆中对象的引用。
在Java中,Main函数就是栈的起始点,也是步伐的起始点。
3、引用范例
对象引用范例分:强引用、软引用、弱引用和虚引用。
强引用:不会被采取。就是我们一样寻常声明对象是时捏造机天生的引用,强引用环境下,垃圾采取时须要严酷判断当前对象是否被强引用,如果被强引用,则不会被垃圾采取。
软引用:内存富足,则不采取;不敷则采取。软引用一样寻常被做为缓存来利用。与强引用的区别是,软引用在垃圾采取时,捏造机会根据当前体系的剩余内存来决定是否对软引用举行采取。如果剩余内存比力告急,则捏造机会采取软引用所引用的空间;如果剩余内存相对富裕,则不会举行采取。换句话说,捏造机在发生OutOfMemory时,肯定是没有软引用存在的。
弱引用:会被采取。弱引用与软引用类似,都是作为缓存来利用。但与软引用差别,弱引用在举行垃圾采取时,是肯定会被采取掉的,因此其生命周期只存在于一个垃圾采取周期内。 |