深入java虚拟机(学习笔记)

一、什么是java虚拟机

java虚拟机是执行字节码文件(*.class)的虚拟机进程。

java源码(*.java)被编译器编译成java字节码文件(*.class)。然后将字节码文件由java虚拟机,解释成不同平台的机器码,利用机器码操作硬件和操作系统。

java内存结构图:

 

二、堆、栈、方法区概述区别

堆:

堆内存用于存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆又被分为两块区域:新生代、老年代

理解公式:堆=新生代+老年代(分别空间为1/3,2/3)

图片区域

新生代分为eden、from survivor、to survivor所占比例为:8:1:1。 之所以这么划分是为了更好的管理内存中的对象也可以说为垃圾回收机制打下坚固的基础。当我们new出一个新对象时,会被存放在eden区域,在eden区域Gc会非常频繁的被操作,会被复制算法快速回收,每当对象经历过一次Gc算法后仍然存活的对象会被存放在下一个from 里面,依次类推,每次经历Gc遍历后所存活的对象都会+1 当达到某个值时(默认15次),它将会被存放在老年代。

这里在换一个貌似不恰当的比方:堆就想一个公司,被分为了新员工称(eden),老员工(from), 中层管理(to),资深管理(老年代)。当你最开始面试成功后你将会成为公司的员工,这时单位的考核部门(Gc),将对每个员工进行业务考核,每次考核后存活下来的员工会晋级,当考核达到一定峰值时,就会晋升为资深管理(当然现实生活中不会有那么频繁的Gc机制)

老年代:存放的是经常被使用对象,默认次数为(15次)在老年代里面 Gc机制不会像新生代那样频繁的去执行。

整体来说Gc回收频率为:eden>from ||to >老年代 (新生代频率高于老年代)

栈:

Java栈是一块线程私有的空间,一个栈,一般由三部分组成:局部变量表、操作数据栈和帧数据区局部变量表:用于报错函数的参数及局部变量操作数栈:主要保存计算过程的中间结果,同时作为计算过程中的变量临时的存储空间。

帧数据区:除了局部变量表和操作数据栈以外,栈还需要一些数据来支持常量池的解析,这里帧数据区保存着访问常量池的指针,方便计程序访问常量池,另外当函数返回或出现异常时卖虚拟机子必须有一个异常处理表,方便发送异常的时候找到异常的代码,因此异常处理表也是帧数据区的一部分。

方法区:

 

Java方法区和堆一样,方法区是一块所有线程共享的内存区域,他保存系统的类信息。比如类的字段、方法、常量池等。方法区的大小决定系统可以保存多少个类。如果系统定义太多的类,导致方法区溢出。虚拟机同样会抛出内存溢出的错误。方法区可以理解为永久区。

扩展本地方法栈:

本地方法栈用于本地方法调用,java虚拟机允许java程序直接调用本地方法(去调用c语言接口)

 

三、虚拟机参数配置

堆的参数配置解释

-XX:+PrintGC

触发GC的时候打印相关日志

-XX:+UseSerialGC

串行回收

-XX:+PrintGCDetails

更详细的GC日志

-Xms

堆初始值

-Xmx

堆最大可用值

-Xmn 

新生代堆最大可用值

-XX:SurvivorRatio

来设置新代中eden空间和from/to空间的比例

-XX:SurvivorRatio

来设置新代中eden空间和from/to空间的比例.

设置最大内存:

参数: -Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags

设置新与老年代优化参数:

-Xmn    新生代大小,一般设为整个堆的1/3到1/4左右

-XX:SurvivorRatio    设置新生代中eden区和from/to空间的比例关系n/1

设置新生代比例参数:

参数: -Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

设置新老年代代参数

-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC

-XX:NewRatio=2

总结:不同的堆分布情况,对系统执行会产生一定的影响,在实际工作中,

应该根据系统的特点做出合理的配置,基本策略:尽可能将对象预留在新生代,

减少老年代的GC次数。

除了可以设置新生代的绝对大小(-Xmn),可以使用(-XX:NewRatio)设置新生代和老年

代的比例:-XX:NewRatio=老年代/新生代

内存溢出解决办法:设置堆内存大小

错误原因: java.lang.OutOfMemoryError: Java heap space

解决办法:设置堆内存大小 -Xms1m -Xmx70m -XX:+HeapDumpOnOutOfMemoryError

设置栈内存大小

 错误原因: java.lang.StackOverflowError

栈溢出 产生于递归调用,循环遍历是不会的,但是循环方法里面产生递归调用, 也会发生栈溢出。

解决办法:设置线程最大调用深度

-Xss5m 设置最大调用深度

Tomcat内存溢出catalina.sh 修改JVM堆内存大小
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=256m -XX:MaxPermSize=512m -XX:MaxNewSize=512m"

JVM参数调优总结

    在JVM启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希望达到一些目标:

  • GC的时间足够的小
  • GC的次数足够的少
  • 发生Full GC的周期足够的长

  前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。

 

   (1)针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
   (2)年轻代和年老代将根据默认的比例(1:2)分配堆内存,可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小

   (3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响

  • 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
  • 更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
  • 如何选择应该依赖应用程序对象生命周期的分布情况:如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:(A)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 (B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间

如果理解错误或者描述不准确之处还请留言之处。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值