JVM的结构

一、JVM的类执行机制

JVM是基于栈的体系结构来执行class字节码的。线程创建后,都会产生程序计数器(PC)和栈(Stack),程序计数器是存放下一条要执行的指令在方法内的偏移量,栈中是存放一个个栈帧,每个栈帧对应着每个方法的每次调用,而栈帧又是有局部变量区和操作数栈两部分组成,局部变量区用于存放方法中的局部变量和参数,操作数栈中用于存放方法执行过程中产生的中间结果,如下所示:



二、JVM内存的组成结构

JVM栈由堆、栈、本地方法栈、方法区等部分组成的,如下所示:


1、,所有通过new创建的对象的内存都在堆中分配,堆的大小可以通过-Xmx和-Xms来控制。堆被划分为新生代和旧生代,新生代又被进一步划分为Eden和Survivor区,最后Survivor由From Space和To Space组成,如下所示:


①、新生代:新建的对象都是用新生代分配内存,Eden空间不足的时候,会把存活的对象转移到Survivor中,新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例

②、旧生代:用于存放新生代中经过多次垃圾回收仍然存活的对象

③、持久带(Permanent Space)实现方法区,主要存放所有已加载的类信息,方法信息,常量池等等。可通过-XX:PermSize和-XX:MaxPermSize来指定持久带初始化值和最大值。Permanent Space并不等同于方法区,只不过是Hotspot JVM用Permanent Space来实现方法区而已,有些虚拟机没有Permanent Space而用其他机制来实现方法区


-Xmx:最大堆内存,如:-Xmx512m

-Xms:初始时堆内存,如:-Xms256m

-XX:MaxNewSize:最大年轻区内存

-XX:NewSize:初始时年轻区内存.通常为 Xmx 的 1/3 或 1/4,新生代 = Eden + 2 个 Survivor 空间,实际可用空间为 = Eden + 1 个 Survivor

-XX:MaxPermSize:最大持久带内存

-XX:PermSize:初始时持久带内存

-XX:+PrintGCDetails。打印 GC 信息

 -XX:NewRatio 新生代与老年代的比例,如 –XX:NewRatio=2,则新生代占整个堆空间的1/3,老年代占2/3

 -XX:SurvivorRatio 新生代中 Eden 与 Survivor 的比值,默认值为 8,即 Eden 占新生代空间的 8/10,另外两个 Survivor 各占 1/10

2、,每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果

-xss:设置每个线程的堆栈大小. JDK1.5+ 每个线程堆栈大小为 1M,一般来说如果栈不是很深的话, 1M 是绝对够用了的。

3、本地方法栈,用于支持native方法的执行,存储了每个native方法调用的状态

4、方法区,存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。JVM用上面所讲的持久带来存放方法区


三、垃圾回收几种基本回收策略

1、引用计数(Reference Counting):

比较古老的回收算法,原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数;垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题

2、标记-清除(Mark-Sweep):

 此算法执行分两阶段,第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片

3、复制(Copying):

 此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不会出现“碎片”问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间

4、标记-整理(Mark-Compact):

此算法结合了“标记-清除”和“复制”两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象“压缩”到堆的其中一块,按顺序排放。此算法避免了“标记-清除”的碎片问题,同时也避免了“复制”算法的空间问题


JVM分别对新生代和旧生代采用不同的垃圾回收机制,二者的各种GC机制是需要组合使用的,指定方式如下所示:


四、JVM内存调优

 首先需要注意的是在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。

对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。

特别要关注Full GC,因为它会对整个堆进行整理,导致Full GC一般由于以下几种情况:

1、旧生代空间不足:

调优时尽量让对象在新生代GC时被回收、让对象在新生代多存活一段时间和不要创建过大的对象及数组避免直接在旧生代创建对象 

2、Pemanet Generation空间不足:

增大Perm Gen空间,避免太多静态对象

统计得到的GC后晋升到旧生代的平均大小大于旧生代剩余空间

控制好新生代和旧生代的比例

3、System.gc()被显示调用:

垃圾回收不要手动触发,尽量依靠JVM自身的机制


调优手段主要是通过控制堆内存的各个部分的比例和GC策略来实现,下面来看看各部分比例不良设置会导致什么后果:

1、新生代设置过小:

    一是新生代GC次数非常频繁,增大系统消耗;

    二是导致大对象直接进入旧生代,占据了旧生代剩余空间,诱发Full GC

2、新生代设置过大:

    一是新生代设置过大会导致旧生代过小(堆总量一定),从而诱发Full GC;

    二是新生代GC耗时大幅度增加(一般说来新生代占整个堆1/3比较合适)

3、Survivor设置过小:

    导致对象从eden直接到达旧生代,降低了在新生代的存活时间

4、Survivor设置过大:

    导致eden过小,增加了GC频率


 通过-XX:MaxTenuringThreshold=n来控制新生代存活时间,尽量让对象在新生代被回收

另外,JVM也提供两种较为简单的GC策略的设置方式:

①、吞吐量优先

    JVM以吞吐量为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,来达到吞吐量指标。这个值可由-XX:GCTimeRatio=n来设置

②、暂停时间优先

    JVM以暂停时间为指标,自行选择相应的GC策略及控制新生代与旧生代的大小比例,尽量保证每次GC造成的应用停止时间都在指定的数值范围内完成。这个值可由-XX:MaxGCPauseRatio=n来设置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 一、前言 为什么要学习了解Java虚拟机 1.我们需要更加清楚的了解Java底层是如何运作的,有利于我们更深刻的学...
    晨曦_hero阅读 1,287评论 1 3
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,862评论 3 83
  • 内存溢出和内存泄漏的区别 内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,...
    Aimerwhy阅读 4,046评论 0 1
  • 注1:以下所提及线程,无特定说明的均默认指代“Java虚拟机线程”。 注2:注意避免混淆Stack、Heap和Ja...
    亨小利霍阅读 3,897评论 1 4
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 13,801评论 1 32