JVM老是开始于main()方法,方法必须是公有、静态、void、参数为String数组; 被初始化线程执行启动。html
public static void main(String[] args) {}
线程分为: 守护线程(daemon) 和 普通线程, main()的初始化线程一定不是守护线程。在setDaemon(true)时若是isAlive,则抛出IllegalThreadStateException。java
形式参数指在定义函数名后小括号里定义的args,用于接受来自调用函数的参数,做用域只在函数内有效;实际参数指在调用时传递给函数的真正的参数。形参的本质是一个名字,不占用内存空间;实参的本质是一个变量,已经占用内存空间。数组
在函数体内缓存
package com.noob.learn.netty; import lombok.AllArgsConstructor; import lombok.Data; public class Test { private static void function(String a, int b) { a = "100a"; b = 1000; } private static void function2(Arg arg) { arg.arg1 = "function2"; arg.arg2 = 100; } public static void main(String[] args) { String arg1 = "arg1"; int arg2 = 0; function(arg1, arg2); System.out.println(String.format("arg1=%s, arg2=%s", arg1, arg2)); Arg arg = new Arg("arg1", 0); function2(arg); System.out.println(arg.toString()); } } @Data @AllArgsConstructor class Arg { public String arg1; public int arg2; }
测试结果:安全
arg1=arg1, arg2=0 // 没变 Arg(arg1=function2, arg2=100) // 改变
存在一个主内存(Main Memory),储存ava中全部实例变量,对于全部线程都是共享的。每一个线程都有一个私有的工做内存(Local Memory), 存储了该线程以读/写共享变量的副本,(本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其余的硬件和编译器优化)工做内存由缓存 和堆栈 <理解为:栈>两部分组成:oracle
JMM决定一个线程对共享变量的写入什么时候对另外一个线程可见。根据 JVM 规范,JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分:app
每一个线程有一个私有的栈,随着线程的建立而建立,虚拟机栈描述的是Java方法执行的内存模型:每一个方法执行的时候都会建立一个栈帧(大小能够固定也能够动态扩展),用于存放局部变量表(基本数据类型和对象引用<句柄>),操做数栈,动态连接,方法出口等信息。< 对象自己不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象)或驻留于常规RAM(随机访问存储器)区域,可经过 “堆栈指针” 得到。堆栈指针若向下移,会建立新的内存;若向上移,则会释放那些内存。>less
每个方法从调用直到执行完成的过程都对应着一个栈帧在虚拟机中的入栈到出栈的过程。把内存分为堆内存和栈内存,其中的栈内存就指的是虚拟机栈的局部变量表部分。局部变量表存放了编译期能够知道的基本数据类型(boolean、byte、char、short、int、float、long、double),对象引用(多是一个指向对象起始地址的引用指针,也可能指向一个表明对象的句柄或者其余与此对象相关的位置),和返回后所指向的字节码的地址。其中64 位长度的long 和double 类型的数据会占用2个局部变量空间(Slot),其他的数据类型只占用1个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法须要在帧中分配多大的局部变量空间是彻底肯定的,在方法运行期间不会改变局部变量表的大小。当递归层次太深时,会引起java.lang.StackOverflowError,这是虚拟机栈抛出的异常。函数
还有另外一种错误,那就是当申请不到空间时,会抛出 OutOfMemoryError。这里有一个小细节须要注意,catch 捕获的是 Throwable,而不是 Exception。由于 StackOverflowError 和 OutOfMemoryError 都不属于 Exception 的子类测试
优势:存取速度比堆要快,仅次于寄存器。
缺点:存在栈中的数据大小与生存期必须是肯定的,缺少灵活性。
保存native方法进入区域的地址。
指令寄存器、地址寄存器、程序计数器。位于处理器内部,处理速度最快。寄存器是根据须要由编译器分配。
在CPU的寄存器中有一个PC寄存器,存放下一条指令地址,这里,虚拟机不使用CPU的程序计数器,本身在内存中设立一片区域来模拟CPU的程序计数器。只有一个程序计数器是不够的,当多个线程切换执行时,那就单个程序计数器就没办法了,虚拟机规范中指出,每一条线程都有一个独立的程序计数器。注意,Java虚拟机中的程序计数器指向正在执行的字节码地址,而不是下一条。
堆内存是 JVM 全部线程共享的部分,在虚拟机启动的时候就已经建立, 存放全部new出来的对象和数组。堆中的对象的由垃圾回收器负责回收,当申请不到空间时会抛出 OutOfMemoryError。
优势:能够动态地分配内存大小,生存期也没必要事先告诉编译器,由于它是在运行时动态分配内存的。
缺点:因为要在运行时动态分配内存,存取速度较慢。
保存虚拟机加载并解析类后的信息、常量、静态变量(JDK7中被移到Java堆),即时编译期编译后的代码(类方法)等数据。全部的线程共享一个方法区,因此访问方法区信息的方法必须是线程安全的。
虽然Java 虚拟机规范把方法区描述为堆的一个逻辑部分,可是它却有一个别名叫作Non-Heap(非堆),目的应该是与Java 堆区分开来。同时,因为类class是JVM实现的一部分,并非由应用建立的,因此又被认为是“非堆(non-heap)”内存。
《Java虚拟机规范》只是规定了有方法区这么个概念和它的做用,并无规定如何去实现它。那么,在不一样的 JVM 上方法区的实现确定是不一样的了。 同时大多数用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集扩展至方法区,或者说使用永久代来实现方法区。所以获得告终论,永久代是HotSpot的概念,方法区是Java虚拟机规范中的定义,是一种规范标准,而永久代是一种实现,其余的虚拟机实现并无永久带这一说法。在1.7以前在(JDK1.2 ~ JDK6)的实现中,HotSpot 使用永久代实现方法区,HotSpot 使用 GC分代来实现方法区内存回收.
官方文档说明:https://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html
In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application<字符字面量(字符串常量池)再也不分配在永久代中,代替的是分配来堆内存中的年轻代与年老代中>. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.<大部分应用对于堆的变化感知不多,可是有太多classes或须要大量使用动态添加String进常量池的方法的大应用感知强烈>
在JDK1.7中的HotASpot中,已经把本来放在方法区的字符串常量池移出。
从JDK7开始永久代的移除工做,贮存在永久代的一部分数据已经转移到了Java Heap或者是Native Heap,但永久代仍然存在于JDK7,并无彻底的移除:
随着JDK8的到来,JVM再也不有PermGen, 但类的元数据信息(metadata)还在,只不过再也不是存储在堆空间上,而是移动到叫作“Metaspace”的本地内存(Native memory)中。