JVM 基础学习 第一节


# JVM 基础学习 第一节

JVM简介

java虚拟机平台上运行非Java语言编写的程序
java源代码->二进制字节码(.class文件) ->JVM-> 解释器->机器码->CPU
它只关心“字节码”文件

Java不是最强大的语言,但是JVM是最强大的虚拟机
虚拟机:虚拟的计算机,可分为系统虚拟机和程序虚拟机
java虚拟机就是二进制字节码的运行环境
    特点:
    1.一次编写,到处运行
    2.自动内存的管理机制,垃圾回收功能
    3.数组下标越界检查
    4.多态
组成:类加载器、运行时数据区、执行引擎、本地方法库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FUKEvUu8-1638098894306)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211120105541320.png)]

JVM 内存区域

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jHI5OV7X-1638099174268)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211120111331541.png)]

JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区

域【JAVA 堆、方法区】、直接内存。

OOM:Out Of Memory 内存用尽

内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。

内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KXI9YVtc-1638098894310)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211120111712491.png)]

线程

线程作用
虚拟机线程(VM thread)等待JVM到达安全点操作出现,这些操作必须要在独立的线程里执行,因为当堆修改无法进行时,线程都需要JVM位于安全点。这些操作类型有:stop-the-world垃圾回收、线程栈dump、线程暂停、线程偏向锁解除
周期性任务线程这线程负责定时器事件(也就是中断),用来调度周期性操作的执行
GC线程这些线程支持 JVM 中不同的垃圾回收活动。
编译器线程这些线程在运行时将字节码动态编译成本地平台相关的机器码。
信号分发线程这个线程接收发送到 JVM 的信号并调用适当的 JVM 方法处理。

1.程序计数器

程序计数器(Program Counter register:寄存器)
  作用:
    记住下一条jvm指令的执行地址
  特点:
    线程私有的,每个线程都有自己的计数器
    不会存在内存溢出

2.虚拟机栈(Java Virtual Machine Stacks)

1.每个线程运行时所需要的内存,称为虚拟机栈
2.每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
3.每个栈只能有一个活动栈帧,对应着当前正在执行的那个方法   

问题辨析:

1、垃圾回收是否涉及栈帧?
    不需要,方法在执行完成后会弹出栈,自动回收掉,不需要垃圾回收
2、栈内存分配越大越好吗?
    不是,分配过多,可能运行效率变低
3、方法内的局部变量是否线程安全?
    如果方法局部变量没有逃离方法的作用范围,它是线程安全的
    如果是局部变量引用了对象,并逃离方法的作用范围,要考虑变量的线程安全问题

栈内存溢出

栈帧过多导致栈内溢出
栈帧过大导致栈内存溢出
    java.lang.StackOverflowError 栈内存溢出
    -Xss256k 设置栈内存的大小

线程运行诊断

案例1:CPU占用过多
    用top查看进程实时监测,定位那个进程对CPU的占用过高
    ps H -eo pid,tid,%cpu|grep 进程id(用ps命令进一步定位是哪一个线程引起的CPU占用过高
    jstack进程id

案例2:程序运行很长时间没有结果

发生死锁

3.本地方法栈

调用本地方法时所需要的内存,native关键字,使用C语言和C++语言实现的程序

4.堆(Heap)

通过new关键字,创建对象都会使用堆内存
特点:
    它是线程共享的,所以堆中都要考虑线程的安全问题
    有垃圾回收机制

堆内存溢出

堆内存溢出:java.lang.OutofMemoryError:java heap space    

堆内存诊断

1.jps工具

查看当前系统中哪些java进程

2.jmap工具

查看堆内存占用的情况

3.jconsole工具

图形界面的,多功能的检测工具,

5.方法区

以前的永久代使用的堆内存,逻辑上方法区属于堆内存中,但在不同商家的JVM方法区实现的地方不一样
    在java8之后,去除了永久代,使用了元空间,占用的内存是本地内存,操作系统的内存

方法区内存溢出

1.8以前导致内存永久溢出

演示永久代内存溢出:java.lang.OutofMemoryError:PermGen space
-XX:MaxPermSize=8m

1.8之后会导致元空间内存溢出

演示元空间内存溢出:java.lang.OutOfMemoryError:Metaspase
-XX:MaxMetaspaceSize=8m

运行时常量池

二进制字节码(类基本信息、常量池。类方法定义,包含了虚拟机指令)

常量池,就是一张表,虚拟机指令根据这张表找到要执行的类名,方法名、参数类型、字面量等信息
运行时常量池,常量池时*.class文件中的,当类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址   
反编译命令:
    javap -v Demo1.class

常量池中的信息,都会被加载到运行时常量池中,这时a b ab 都是常量池中的符号,还没有变为 java 字符串对象

ldc #2 会把a符号变为“a“字符串对象

ldc #3 会把a符号变为“b“字符串对象

ldc #4 会把a符号变为“ab“字符串对象

String s1="a";
String s2="b";
String s3="ab";在串池中

String s4=s1+s2;在堆中创建了一个新的对象 new stringbuilder().appand("a").appand("b").toString();new String("ab")
    String s5="a"+"b";javac 在编译器期间就拼接成"ab",在串池中
    s3==s4 //(false)
因为s3的"ab"是存在StringTable中的,而s4是新建对象,存放在堆中的,位置不同,比较返回的是false  
String x="ab";        
String s=new String("a")+new String("b")
Stirng s2=s.intern()将这个字符串对象尝试放入串池,如果没有则并不会放入,如果有则放入串池,会把串池中的对象返回
s2==x	(true)
s==x    (false)

在使用这些字符串时才会从常量池中加载到运行时常量池中

StringTable[“a”,“b”,“ab”] hashtable结构,不能扩容

6.StringTable的特性

常量池中的字符串仅是符号,第一次用到时才变为对象
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder(1.8)
字符串拼接的原理是编译器优化
可以使用intern方法,主动将串池中还没有的字符串对象放入串池    
    1.8将这个字符串对象尝试放入串池,如果没有则并不会放入,如果有则放入串池,会把串池中的对象返回
    1.6将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把对象复制一份,放入串池,会把串池中的对象返回

面试题:

public class Demo1 {
    public static void main(String[] args) {
        String s1 = "a";
        String s2 = "b";
        String s3 = "a" + "b";
        String s4 = s1 + s2;
        String s5 = "ab";
        String s6 = s4.intern();

        System.out.println(s3 == s4);//false
        System.out.println(s3 == s5);//true
        System.out.println(s3 == s6);//true

        String x2 = new String("c") + new String("d");
        String x1 = "cd";
        x2.intern();
        //x2.intern();
        //String x1 ="cd";
//        System.out.println(x1 = x2);//true
        System.out.println(x1 = x2);//false
        //如果调换了位置,如果是1.6呢
        //如果常量池中没有,则会在堆中复制一份到常量池中
    }
}

7.StringTable的位置

在JDK8设置:-Xmx10m -XXUseGCOverheadLimit

在JDK6设置:-XX:MaxPermSize=10m

可证明StringTable在堆空间(1.8) 在永久代(1.6)

8.StringTable的性能调优

调整:-XX:StringTableSize=桶个数

考虑将字符串对象是否入池(使用intern方法)

直接内存

1.定义

常见于NIO操作,用于数据缓冲区

分配回收成本高,但读写性能高

不受JVM内存回收管理

2.分配和回收原理

使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法

ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存

JVM 运行时内存

Java堆从GC的角度还可以细分为:新生代(Eden**区FromSurvivor**区ToSurvivor**区)和老年

代。

1.新生代

是用来存放新生的对象。一般占据堆的1/3空间。由于频繁创建对象,所以新生代会频繁触发

MinorGC进行垃圾回收。新生代又分为Eden区、ServivorFrom、ServivorTo三个区。

EdenJava新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代)。当Eden区内存不够的时候就会触发MinorGC,对新生代区进行 一次垃圾回收
Servivor From
上一次GC的幸存者,作为这一次GC的被扫描者。
   
ServivorTo    
保留了一次MinorGC过程中的幸存者
    MinorGC的过程(复制->清空->互换)
    MinorGC采用复制算法。
1.eden、servicorFrom复制到ServicorTo,年龄+1
     首先,把EdenServivorFrom区域中存活的对象复制到ServicorTo区域(如果有对象的年 龄以及达到了老年的标准,则赋值到老年代区),同时把这些对象的年龄+1(如果ServicorTo不够位置了就放到老年区);
2.清空eden、servicorFrom
    然后,清空EdenServicorFrom中的对象;
3ServicorToServicorFrom互换
    最后,ServicorToServicorFrom互换,原ServicorTo成为下一次GC时的ServicorFrom 区。

2.老年代

主要存放应用程序中生命周期长的内存对象。
老年代的对象比较稳定,所以MajorGC不会频繁执行。在进行MajorGC前一般都先进行 了一次MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无法找到足 够大的连续空间分配给新创建的较大对象时也会提前触发一次MajorGC进行垃圾回收腾出空间。
    MajorGC采用标记清除算法:首先扫描一次所有老年代,标记出存活的对象,然后回收没有标记的对象。MajorGC的耗时比较长,因为要扫描再回收。MajorGC会产生内存碎片,为了减少内存损耗,我们一般需要进行合并或者标记出来方便下次直接分配。当老年代也满了装不下的时候,就会抛出OOM(OutofMemory)异常。

3.永久代

指内存的永久保存区域,主要存放ClassMeta(元数据)的信息,Class在被加载的时候被 放入永久区域,它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这 也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。

4.JAVA8与元数据

Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间 的本质和永久代类似,元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。类的元数据放入native memory,字符串池和类的静态变量放入java堆中,这样可以加载多少类的元数据就不再由 MaxPermSize控制,而由系统的实际可用空间来控制。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

抹泪的知更鸟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值