社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
Java虚拟机的基本结构:
类加载子系统负责从文件系统或者网络中加载Class信息,加载的类信息存放于一块称为方法区的内存空间,除了类的信息外,方法区还会存放运行时常量池信息,包括字符串字面量和数字常量(这部分常量信息是Class文件中常量池部分的内存映射).
Java 堆 在虚拟机启动的时候建立,它是Java程序最主要的内存工作区域.几乎所有的Java对象实例都存放在Java堆中.一般就是new
方法创建的对象实例.
直接内存: Java的NIO库允许Java程序使用直接内存,直接内存是在Java堆外的,直接向系统申请的内存区间,通常,访问直接内存的速度会优于Java堆,因此出于性能考虑,读写频繁的场合可以考虑直接内存,直接内存在Java堆外,不直接受限于Xmx
参数,但Java堆和直接内存的总和依然受限于操作系统能给出的最大内存
垃圾回收系统是Java虚拟机的重要组成部分,它可以对方法区,Java堆和直接内存进行回收,其中,java堆是垃圾收集器的重点.
每一个Java虚拟机线程都有一个私有的 Java栈 ,Java栈中保存着局部变量,方法参数,同时和Java方法的调用,返回相关
本地方法栈和Java栈非常类似,最大不同是Java栈用于Java方法的调用,本地方法栈用于本地方法调用
PC(Program Counter) 寄存器也是每个线程的私有空间, 任意时刻,Java线程都在执行一个方法,此方法称为当前方法,如果当前方法不是本地方法,则PC寄存器就会指向当前正在被执行的指令,如果当前方法是本地方法则PC寄存器的值就是undefined.
执行引擎 负责执行虚拟机的字节码.
线程私有数据区域生命周期与线程相同, 依赖用户线程的启动/结束 而 创建/销毁(在 Hotspot
VM 内, 每个线程都与操作系统的本地线程直接映射, 因此这部分内存区域的存/否跟随本地线程的
生/死对应)。
线程共享区域随虚拟机的启动/关闭而创建/销毁。
直接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的使用: 在 JDK 1.4 引入的 NIO 提
供了基于 Channel 与 Buffer 的 IO 方式, 它可以使用 Native 函数库直接分配堆外内存, 然后使用
DirectByteBuffer 对象作为这块内存的引用进行操作(详见: Java I/O 扩展), 这样就避免了在 Java
堆和 Native 堆中来回复制数据, 因此在一些场景中可以显著提高性能。
程序计数器(线程私有)
虚拟机栈 (线程私有)
本地方法区(线程私有)
堆(Heap-线程共享)-运行时数据区
创建的对象和数组都保存在Java堆内存中,也就是垃圾收集器进行垃圾收集的最重要的内存区域。
方法区/永久代(线程共享)
即我们常说的永久代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、静
态变量、即时编译器编译后的代码等数据. HotSpot VM把GC分代收集扩展至方法区, 即使用Java
堆的永久代来实现方法区, 这样 HotSpot 的垃圾收集器就可以像管理 Java 堆一样管理这部分内存,
而不必为方法区开发专门的内存管理器(永久带的内存回收的主要目标是针对常量池的回收和类型
的卸载, 因此收益一般很小)。
运行时常量池(Runtime Constant Pool) 是方法区的一部分。Class 文件中除了有类的版
本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。 Java 虚拟机对 Class 文件的每一部分(自然也包括常量池)的格式都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。
JVM 运行时内存
Java 堆从 GC 的角度还可以细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年
代。
在Java8中,永久代代已经被移除,被一个称为“元数据区”(元空间)的区域所取代,元空间
的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用
本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入 native
memory, 字符串池和类的静态变量放入 java 堆中,这样可以加载多少类的元数据就不再由
MaxPermSize 控制, 而由系统的实际可用空间来控制。
在HotSpot 虚拟机中,对象在内存中存储布局分为3块区域: 对象头(Header)、实例数据(Instance Data)、对齐填充(Padding)
Object obj=new Object();
Object obj这部分语义反映到Java栈的本地变量表中,作为一个reference类型数据出现,“new Object()”这部分语义将反映到Java堆中,形成一块存储了Object类型所有实例数据值(Instance Data,对象各个实例字段的数据)的结构化内存,这块内存长度是不固定的。另外,Java堆中还必须包含能查到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据存储在方法区里。
reference访问Java堆中的对象的具体位置的访问方式有不同的方式,主流的方式有两种:使用句柄和直接指针
使用句柄方式:Java堆会划分出一块内存作为句柄池,refrence中存储的就是对象的句柄地址,而句柄包含了对象实例数据和类型数据各自的具体地址信息,如图:
使用直接指针,reference中直接存储的就是对象地址。如图:
两种方式比较:
使用句柄最大好处是移动对象时,只需要改变句柄中的实例数据指针,而reference本身不需要被修改。
使用直接指针的最大好处是速度更快。Sun HotSpot 而言,它使用第二种方式进行对象访问的。
参数 | 含义 | 默认值 | |
---|---|---|---|
-Xms | 初始堆大小 | 物理内存的1/64(<1GB) | 默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制. |
-Xmx | 最大堆大小 | 物理内存的1/4(<1GB) | 默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制 |
-Xmn | 年轻代大小(1.4or lator) | 注意:此处的大小是(eden+ 2 survivor space).与jmap -heap中显示的New gen是不同的。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小. 增大年轻代后,将会减小年老代大小.此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8 |
|
-XX:SurvivorRatio | Eden区与Survivor区的大小比值 | 设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10 | |
-XX:PretenureSizeThreshold | 对象超过多大是直接在旧生代分配 | 0 | 单位字节 新生代采用Parallel Scavenge GC时无效另一种直接在旧生代分配的情况是大的数组对象,且数组中无外部引用对象. |
-XX:MaxTenuringThreshold | 垃圾最大年龄 | 如果设置为0的话,则年轻代对象不经过Survivor区,直接进入年老代. 对于年老代比较多的应用,可以提高效率.如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象再年轻代的存活 时间,增加在年轻代即被回收的概率 该参数只有在串行GC时才有效. |
辅助信息:
参数 | 含义 | 默认值 | |
---|---|---|---|
-XX:+PrintGC | 输出形式:[GC 118250K->113543K(130112K), 0.0094143 secs] [Full GC 121376K->10414K(130112K), 0.0650971 secs] |
||
-XX:+PrintGCDetails | 输出形式:[GC [DefNew: 8614K->781K(9088K), 0.0123035 secs] 118250K->113543K(130112K), 0.0124633 secs][GC [DefNew: 8614K->8614K(9088K), 0.0000665 secs][Tenured: 112761K->10414K(121024K), 0.0433488 secs] 121376K->10414K(130112K), 0.0436268 secs] |
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!