JVM学习笔记

JVM学习笔记

一、简介

1.定义

Java程序的运行环境(java二进制字节码的运行环境)
JRE:由JVM+基础类库
JDK:JVM+基础类库+编译工具
JAVASE:JDK+工具
运行区的划分:

  • 方法区
  • 虚拟机栈
  • 本地方法栈
  • 程序计数器
2.好处

①编写之后,可以随处运行。
②自动内存管理,提供垃圾回收机制。
③数组下标越界检查,会提供异常提示。

二、内存结构

1. 程序计数器

①jvm指令流程:java代码拿到jvm指令会交给解释器来转换成机器码,机器码再转交给CPU处理。
②计数器的作用是记住下一条jvm执行命令的执行地址,如果没有计数器就不知道下面执行哪条命令。
③计数器是通过寄存器来实现的,寄存器是cpu里读取速度最快的单元。
④计数器的特点是线程私有的,即多线程切换时计数器和线程是一组绑定的,每个线程都有自己的线程计数器。
⑤程序计数器是不会存在内存溢出的。

2.虚拟机栈

①栈:线程运行时需要的内存空间,称为虚拟机栈。
②一个栈是由多个栈帧组成,每个方法运行时需要的内存就是栈帧,就是每个方法运行时需要的内存。
③栈帧可存放参数、局部变量、返回地址。一个栈内可存放多个栈帧,如:方法1调用方法2方法2调用方法3,这时栈内会存入三个栈帧,当方法三调用结束后将其释放,当方法二调用结束后将其释放,类推。
④栈的大小不能改善运行速度,反而栈占用的内存越大,线程就越少。比如物理内存为500M,如设定每个线程的大小为1M,那么可拥有500个线程同时运行,但当每个栈内存设置了2M,那么只能有250个线程可以使用。
⑤如果一个变量是方法私有的是线程安全的,如果变量是方法共享的(static)则要考虑线程安全。
⑥线程安全问题:

  • 如果方法内局部变量没有将其当做返回值返回,则是线程安全的。
  • 如果是局部变量引用的是对象,并离开了该使用方法的作用范围,需要考虑线程安全问题。

⑦栈内存溢出:java.lang.StackOverflowError

  • 栈帧过多会导致栈内存溢出,如方法递归调用。
  • 栈帧过大导致栈内存溢出。

⑧线程运行诊断:

  • 定位方法:(Linux方式)
    top定位哪个进程占用cpu过高
    ps -H -eo pid,tid %cpu |gerp 占用过高的进程id
    jstack 占用过高的线程id 查询出的结果中nid对应的线程id就是问题所在(需要将线程id转换为16进制比对)
3.堆

①是线程共享的,堆里的对象都需要考虑线程安全的问题,堆里提供垃圾回收机制,当堆中不在引用的对象将当成垃圾回收掉,释放空闲的内存。
②堆内存溢出:java.lang.OutOfMemoryError:Java heap space
③堆内存诊断:

  • 在main方法执行时将Terminal点开输入:
    jps查看有哪些端口,找到测试的main方法端口
    jmap -heap 端口号,查看堆内存占用情况
    jconsole 可视化页面查看占用情况,选择要监控的方法
    jvirsualvm 跟jconsole用法类似
4.方法区

①方法区是一块所有线程共享的内存区域,一般存放系统信息。如果系统中定义了很多的类,也可以导致方法区内存溢出,方法区又为永久区。jdk1.8以前称为永久代空间,1.8之后称为元空间其意思一样。
②常量池:就是一张表,虚拟机的指令根据这张表要查找和执行的类名、 方法名、参数等信息。所谓运行常量池就是*.class文件,当类被加载时,它的常量池信息也会放入运行常量池中。

5.StringTable介绍

①所有定义的字符串对象,都是在用到时才会存放到StringTable串池中,是hashtable结构,不能扩容。

String v1 = “a”; 用到时才会存放到stringtable中
String v2 = “b”;
String v3 = “ab”;
相当于new StringBuilder().append("a").append("b").toString(); 
toString后就相当于 new String(“ab”);
String v4 = a+b;
javac编译器期间的优化,结果已经在编译期被定义为ab。
String v5 = “a”+“b”;

三、垃圾回收

1.垃圾回收方式

标记清除:分为标记和清除两个阶段,首先标记需要回收的对象,在标记后统一启用清除回收掉所有被标记的对象。是最基础的收集算法,缺点是效率不高,清除后会产生大量不连贯的内存碎片,当需要分配更大对象的时候无法找到连续内存会触发另一次垃圾回收操作。
标记整理:整体上跟标记清除算法一样,但对标记的对象不是直接回收,而是让所有存活的对象向一端移动,再清理掉存活边界以外的内存。
复制方式:将存活对象复制到另一端,再将被复制的一端整体删除,剩下的就是有效的。但缺点是可能内存空间只剩下了一半。(适用于存活对象比较少的情况,新生代)
分代收集:GC分代绝大部分对象的生命周期非常短暂,存活时间短,而且不同的对象的生命周期是不一样的。分代思想就是将Java堆分为新生代和老年代,这样可以根据各个代的特点采用适当的收集算法。在新生代中,每次回收都只会剩下少量的存活对象,那么选用复制算法比较理想,而老年代中对象存活率较高,使用标记清除和标记整理算法回收。

2.垃圾的分代
  • 新生代:年轻代主要存放新建的对象,内存大小相对小一些,垃圾回收比较频繁,当回收时,进入年轻代扫描Eden和A Survivor(幸存区),如果对象依然存活将其复制到B Survivor,如果B满了将复制到OldGen。
  • 老年代:存放生命周期比较强的对象,内存比较大垃圾回收也没有那么频繁。
3.按系统线程分类
  • 串行收集:串行收集采用单线程垃圾回收,实现比较容易效率较高,但是局限性比较明显,无法使用多处理器上,适合收集但处理器机器。
  • 并行收集:采用多线程的垃圾回收机制,速度快、效率高而且cpu数目越多,越能体现并行收集器的优势。
  • 并发收集:比较串行收集、并行收集,他们两个进行垃圾回收时,需要暂停整个环境,而只有垃圾回收程序在运行,因此导致系统在垃圾回收时会有明显的暂停。
4.减少GC的频率

①适当减少堆内存空间。
②合理设置G1垃圾收集器的停顿时间
③垃圾回收的临界线 -XX:InitatingHeapOccupancPercent=50
④增加垃圾回收线程量 -XX:ConcGCThreads=10
⑤新生代占用率高一些,老年代占用低一些。

7.StringTable调优

①调整-XX:StringTableSize=桶个数 (桶个数增加处理时间会减少)

四、内存溢出

1.产生原因

①内存中加载的数据量过于庞大,如一次从数据库取出过多数据
②集合类中有对对象的引用,使用完后未清空,使得JVM不能回收
③代码中存在死循环或循环产生过多重复的对象实体
④使用的第三方软件中的BUG
⑤启动参数内存值设定的过小
解决办法引用该博主的文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值