JVM解析

一、基本概念 

        定义
        Java Virtual Machine -java程序的运行环境(java二进制字节码的运行环境)
        好处
        1.一次编写,到处运行。JVM屏蔽了java字节码文件和底层操作系统之间的差异。对外提供了
           一个一致的运行环境。
        2.有自动管理机制,提供了垃圾回收的功能。
        3.数组下标越界检查。之前c语言的话,如果越界了数组的新元素会覆盖程序的部分,而jvm会
           抛出异常,不会造成上述情况。
        4.多态。
        比较
        JVM:java的运行环境
        jre:JVM+基础类库
        JDK:JVM+基础类库+编译工具
        学习这个有啥用
        1.面试
        2.理解底层的实现原理
        3.中高级程序员的必备技能
        常见的JVM
        JVM是一个规范,所以很多公司对这个规范进行了一系列的是实现。比如Oracle的HotSpot,
        Eclipse OpenJ9

二、内存结构

1.程序计数器

        定义:Program Counter Register程序计数器(寄存器)
        作用:保存下一条JVM指令的地址。就是下图中 0,3,4,5。根据下图的描述,在解释
                   器将地址是0的指令解释为机器码的时候,程序计数器中会存入3。在上条指令
                   getstatic执行结束后,解释器去程序计数器中找地址为3的指令,并解释为机器
                   码,程序计数器中存4。在物理中,JVM使用寄存器来实现程序计数器。
        特点:(1).是线程私有的。java是支持多线程的,CPU的调度器对不同线程提供不同的时
                   间片。如果在一个时间片内线程1的程序没有执行完,线程1的状态会被保存下来,
                   然后,时间片会分配给线程2的程序执行。每个线程都会有自己的程序计数器,在
                   切换线程时候,各自的程序计数器会保存下来各自线程的下一条命令。
                   (2).这个区域不会造成内存溢出。

2.虚拟机栈

        定义:
                栈是一种先进后出的存储结构,这里的虚拟机栈,就是线程运行需要的内存空间。如果
        有多个线程,就会有多个虚拟机栈。
                栈内的每个元素,我们称之为栈帧。每个栈帧都对应着 一次 方法的调用,也就是说栈帧
        就是方法运行需要的内存空间。包括,参数,局部变量和返回地址所需要的内存空间。每个
        线程只有一个活动栈帧,对应着档期啊正在执行的那个方法。
                调用改方法时会将该栈帧压入虚拟机栈中,调用完毕后该栈帧出栈。如果方法一调用了
        方法二,那虚拟机栈会先把方法一的栈帧压栈,然后再把方法二的栈帧压进栈。

        问题辨析:
                1.垃圾回收会涉及占内存吗?不会因为随着方法调用的结束,方法所占用的内存会随着
        方法的结束被释放掉,不需要垃圾回收。
                2.栈内存分配越大越好嘛?不好,因为栈内存过大可能会使该机器可支持的最大线程数
        量变少,因为内存就那么大,每个栈的内存大了,最大线程就少了。一般1M。
                3.方法内的局部变量是否是线程安全的?其实这个问题可以改为,这个局部变量对应不
        同的线程来说是不是共享的?还是说线程私有的?是安全的。但是如果两个线程共同访问一
        个类中的static变量,就会不安全。
                4.内存溢出问题:(1)当一直调用方法,但是却不释放方法的话,虚拟机栈的栈帧就会越
        来越多,最终导致内存溢出。比如递归调用。(2)栈帧过大。 

        线程运行诊断:
                1.CPU占用过高。解决:top命令可以检测到后端进程的进行对CPU的占用情况。ps H
        -eo pid,tid,%cpu | grep 进程id   命令可以查看线程对CPU的占用情况。jstack 进程id 命令就能
        找到特定某个线程,和与之相关的代码。
                2.迟迟得不到结果,有可能是线程死锁导致了这个问题。解决:使用jstack 进程id 命令,在打印信息中找到“Found one Java-level deadlock”信息......

 3.本地方法栈

        java代码不太能和计算机底层打交道,所以他需要调用位于我们计算机上的一些c语言,c++语言编写的本地代码,而这些本地代码就被存入到我们的本地方法栈中。

4.堆

        定义:
                通过new关键字,创建的对象都会使用堆内存。

        特点:
                1.上面的程序计数器,虚拟机栈,本地方法栈都是线程私有的。堆和下面的方法区可以
        看成是线程共享的,在堆中的对象都需要考虑线程安全的问题。
                2.有垃圾回收机制。

        堆内存溢出问题:
                1. 当一直创建新的对象,这些对象又一直在被使用,就会导致对象越来越多,然后溢
        出。-Xmx可以设置对空间的大小。

        堆内存问题的诊断:
                工具:jps:查看当前系统中有哪些java进程。jmap:查看堆内存占用情况。jconsole:图形
        界面的,多功能的检测工具,可以连续监测。新创建的对象占用的堆内存都会保存在Heap        
         Usage中的Eden Space中。调用的命令分别是:(1) jps   (2)jmap -heap 进程id 。(3)jconsole

        案例:
                垃圾回收之后,内存占用依然很高怎么办?使用jvisualvm。可能是顶一个一个较大的数
        据,然后他又是一直活动的,导致垃圾回收机制回收不掉。

5.方法区

        定义:
                方法区是所有java虚拟机线程共享的区域。他存储了跟类结构相关的信息,比如说运行
        时常量池,类的成员变量,方法数据以及成员方法和构造器方法的代码。方法去虚拟机启动
        时创建,在逻辑上是属于堆的一部分。 但是不同的实现对于方法区域的位置上有所不同。方
        法区是有可能会内存溢出的。 

        内存溢出:
                java1.8后方法区的实现使用的是系统内存。内存溢出不好演示,但是我么你可以用
        -XX:MaxMetaspaceSize=8m来减少方法区元空间大小。在1.6的时候,如果我们要设置永久
        代的大小,命令是-XX:MaxPermSize=8m。如果一直不停的加载类,那么内存就会溢出。在
        生产环境中我们可能由于框架使用不规范,导致我们生成了太多的代理类啥的,就会溢出。

        运行时常量池
                常量池就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类
        型,字面量等信息。运行时常量池,就是当该类被加载时,他的常量池信息就会放入运行时
        的常量池,并把里面的符号地址变成真实的地址。

                 将一个类编译成二进制字节码文件,该文件一般包含三大部分——类基本信息(把一个
        class文件反编译后我们可以看到这些基本信息,比如类文件路径,最后修改时间,编译之前
        的文件,类的访问权限,父类是什么,继承了那些接口,MD5值),常量池,类方法定义(包含
        了虚拟机指令,方法的 )。
                在下图中我们可以看到,在虚拟机指令的命令后面有#数字,这个#数字是这个虚拟机指
        令运行需要操作或查找的某个常量的地址。而这些常量被存储在方法区中。
                
        

串池(StringTable):
        1. 常量池中的字符串仅仅是符号,在第一次用到的时候才会创建出一个对象。
        2.利用串池的原理,可以避免重复创建字符串对象。
        3.字符串变量拼接的原理是StringBuilder,在底层他会创建一个StringBuilder对象,然后把这个
           独享转变成String对象,使用new的方式,所以这个新创建的字符串占用的是堆空间。
        4.字符串常量拼接的原理是编译期优化。比如如果是把"A"和“B”进行拼接,他会直接在串池中
           找有没有“AB”,如果有就使用串池中的这个对象。
        5.可以使用intern方法,主动将串池中还没有的字符串对象放进串池。
           该方法:
                1.8:将某个字符串对象s尝试放进串池中,如果有就不会放入,如果没有就会放入,会
                把串池中的对象s2返回。如果放入成功那么这里的s==s2,如果放入失败s!=s2。
                1.6:将某个字符串对象s尝试放进串池中,如果有就不会放入,如果没有就会把此对象
                复制一份s2放入,会把串池中的对象s2返回。如果放入成功那么这里的s!=s2,如果放入
                失败s!=s2。

串池位置:
        现象:在1.6的时候,串池是在永久代里面的,但是在1.8之后串池的位置转移到了堆中。方法
                   区的实现也从原来的永久代变成了原空间(占用本地内存)。
        原因:因为永久代里面的垃圾回收的效率很低,而串池又是一个经常用的内存。

串池的垃圾回收: 
        串池中的数据长期不使用也是会触发垃圾回收机制的。

串池调优:
        1.因为串池的底层数据结构是hashMap,桶的个数会影响hash冲突的次数,所以其实串池的
        调优可以修改桶个数。-XX:StringTableSize=桶个数。
        2.考虑将字符串对象放进串池中。原理就是,如果要存储一堆字符串,但是这写字符串很多都是重复的,我们就不必把所有的字符串都存进内存中。只要存储字符串时候使用intern方法,把想通的字符串存进串池中就好。


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值