一、JVM内存结构
为了让程序运行起来,虚拟机如何分配内存?
1.1 程序计数器
该内存是给 线程 使用的:每个线程有独立的计数器,其作用是记录下一条jvm的指令地址;是内存区中唯一不存在内存溢出问题的。如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是本地方法,这个计数器值应为空。
1.2 虚拟机栈
该内存是给 线程 使用的,其作用是让线程独立运行。栈是一个动态的存储区域。
每个线程运行的方法由:局部变量、参数和返回值类型组成。这些组成部分在被以栈帧的形式封装并压入栈中;每个线程运行只有一个活动栈帧,即只有一个方法正在运行。
-
局部变量表:是一组变量
值
的存储空间。用于存放方法参数和方法内部定义的局部变量。在class文件的方法的code属性确定其分配的变量表的最大容量;容量以容量槽(slot)为最小单位,且可以复用。jvm规范推荐的容量槽大小为32位。jvm是通过局部变量表的的索引定位的,索引值从0-max。0的位置默认是用于传递方法所属对象实例的引用,在方法中可以使用"this"进行访问这个隐含的参数值。
操作数栈:当一个方法刚刚开始执行时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取数据,就是出栈和入栈操作。java虚拟机的解释执行引擎被称为“基于 栈的执行引擎”,栈即操作数栈。
动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中动态连接。字节码文件中的常量池中有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号引用一部分在类加载阶段或者第一次使用的时候转换为直接引用,这种转化被称为静态解析(这部符号符合Java语言“编译期可知,运行期不可变”要求的方法有静态方法、私有方法、构造方法、父类方法和final修饰的方法)。另外一部分将在每一次运行期间都转化为直接引用,这部分称为动态连接(动态调用的形式是分派:静态单(多)分派-重载、动态单(多)分派-重写)。
-Xss1024k:设置线程栈占用内存大小。默认值:不同的操作系统平台,其默认值不同,具体看官网说明。
2.3 本地方法栈
调用由C/C++实现的方法。
2.4 堆
堆主要存储的内容:静态变量(JDK 1.8前放在方法区,JDK 1.8后存放在堆中反射的class对象中)、对象、数组。线程共享区域,存在线程安全问题。
栈属于线程方法执行的区域,所以栈就像客栈一样,属于临时区域,对象占用的内存空间随着出栈而释放;而堆相对于栈,一旦被new创建出来,对象的销毁需要垃圾回收机制的介入。
堆内存的诊断工具:
- jps: 查看当前系统有哪些java进程。 在linux中ps是查看进程的。
- jstat:虚拟机统计信息监视工具。
- jinfo:java配置信息工具,实时查看和调整虚拟机各项参数。
- jmap:内存映像工具,生成堆转储快照文件。还可以查询finalize执行队列、java堆和方法区的详细信息。
- jhat:虚拟机堆转储快照分析工具。
- jstack:java堆栈跟踪工具,jdk5后Thread.getAllStackTraces()可以实现其大部分功能。
- jconsole:图形化的内存检测工具。
- jvisualvm:可视化的虚拟机工具。
- JHSDB:基于服务性代理的调试工具。
堆的常用设置:
-XX:+PrintFlagsInitial :查看所有的参数默认初始值
-XX:+PrintFlagsFinal :查看左右参数的的最终值,修改后的值
-Xms:初始化堆空间内存(默认为物理内存的1/64)
-Xmx:最大堆空间内存(默认为物理内存的1/4)(建议最大最小值一致,较少系统动态调整空间二浪费性能)
-Xmn:设置新生代的大小。(初始值及最大值)
-XX:NewRatio:配置新生代与老年代在堆结构中的占比默认1:2
-XX:SurvivorRatio:设置神等待中Eden和S0/S1空间的比例(默认为8,但是默认开启动态调整,所有看空间比例可能不是8)
-XX:MaxTenuringThreshold:设置新生代垃圾的最大年龄(默认/最大值为15,超出后转移到老年代)
-XX:+PrintGCDetails:输出详细的GC处理日志
-XX:PrintGc 或 -verbose:gc :打印GC简要信息
-XX:HandlePromotionFailure:是否设置空间分配担保(JDK1.7之后失效,认为是true)
2.5 方法区
虚拟机规范建议:方法区逻辑上应是 堆 的组成部分,在虚拟机启动时候创建。
方法区在逻辑上属于堆,所以Java 虚拟机在一开始的时候,为了重复使用垃圾回收的代码,在堆中使用“永久代”来实现方法区。但是方法区中存放的数据很多容易造成内存溢出,所以在jdk8后将方法区的大部分(除了“串池”)实现放入元数据中。
方法区的规范实现方式不同:
- jdk6是通过永久代实现:方法区还在jvm的内存中。
- jdk8是通过元空间实现:方法区大部分数据已经不在jvm的内存中 。
运行时常量池:当类被加载,常量池(就是一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型和字面量。是存储在字节码文件中)中的数据就会放入内存中的运行时常量池,并把里面的符号地址变为真实地址。
StringTable:是运行时常量池的重要组成部分,是哈希表结构,是存放运行时字符串包括类名、方法名等。 jdk8时候已经从元空间抽离到堆内存中。
在1.8 中intern将某个字符串放入StringTable中时,如果StringTable中没有对应的字符串,则会把该字符串放入StringTable,并把StringTable中的对象返回。
在1.6 中intern将某个字符串放入StringTable中时,如果StringTable中没有对应的字符串,则会把该字符串复制一份放入StringTable,并把StringTable中的对象返回。
当操作大量重复字符串的时候,考虑使用intern将字符串放入“串池”中,由于“串池”是哈希表结构,所以会自动去重,可以有效降低内存的占用。
2.6 OS系统内存
不受JVM的垃圾回收管理(是由JVM的Unsafe对象管理的),常见于NIO操作时,数据缓冲区,分配和回收成本高,但读写效率高。
内存溢出会抛出Direct Buffer Memory
。