java中jvm

一:介绍

  • 什么是JVM
    定义:java virtual meachine -java运行时环境(java二进制字节码的运行环境)。
  • 好处:
    一次编写到处运行
    自动内存管理,垃圾回收
    数组下标越界检查
    多态
    在这里插入图片描述
    java虚拟机:

java的内存结构包括:

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区

1.1 程序计数器

在这里插入图片描述

  • java代码在执行的过程:首先被编译成二进制字节码(jvm指令),然后通过解释器生成为机器码,最后才会被CPU执行。
  • 而解释器在执行每一条字节码指令时,需要程序计数器来指定需要执行的指令。此时程序计数器中存储的是jvm下一个执行的地址
  • 正是因为能编辑为机器码,jvm才有一次编辑,多平台使用。因为无论linux和windows系统,都会执行机器码

特点:

  • 线程私有
  • 不会出现内存泄漏

1.2 栈

1.2.1栈的定义

定义:

  • 每个线程运行时所需要的内存,称为虚拟机栈
  • 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧(正在执行的方法),对应着当前正在执行的那个方法
  • 特点:先进后出

一个线程需要一个栈;一个栈帧对应一个方法的调用;
栈和栈帧的关系:线程执行方法的时候,会调用方法,会将栈栈放入栈内,当方法执行完后,会将栈帧释放掉
在这里插入图片描述
问题解析:
1:垃圾回收是否涉及栈内存
不涉及,方法执行完后,对应的栈帧就会被出栈,被释放掉,不涉及垃圾回收
2:栈内存分配越大越好吗?
不是:物理内存大小是一定的,当栈内存大时,线程数就会少。
3:方法内的局部变量是否线程安全?

  • 如果方法内局部变量没有逃离方法的作用访问,是线程安全的
  • 如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全

查看线程是否安全:看这个变量对多个线程是共享的,还是私有的

public class Demo{
   static void hello(){
       int x=0;
       for (int i=0;i<100;i++){
       x++;
       }
System.out.println(x);

}
}

此时线程安全
在这里插入图片描述
此时局部变量是线程不安全的

public class Demo{
   static void hello(){
       static int x=0;
       for (int i=0;i<100;i++){
       x++;
       }
System.out.println(x);

}
}

在这里插入图片描述
在这里插入图片描述

1.2.2 栈内存溢出

STACKOVERFLOWERROE
什么情况会导致内存溢出

  • 栈帧多时,一直有栈帧进栈;当递归次数多时
  • 栈帧过大导致内存溢出,

1.2.3 线程运行诊断

  • 案例一:cpu占用过多
    定位

    • 用top定位哪个线程对cpu占用过高
    • ps -H pid,tid,%cpu | grep 进程id,(用ps命令进一步定位那个线程引起的cpu占用过高)
    • jstack 进程id
      • 可以根据线程id,找到问题的线程,进一步定位到出现问题的代码
  • 案例二:一段程序,运行好长时间,没有等到返回的结果

1.3 堆

堆内主要存储的是new的对象

1.3 方法区

1.3.1定义

方法区在虚拟机启动时产生,
其中主要存储加载的类字节码、class/method/field等元数据对象、static-final常量、static变量、jit编译器编译后的代码等数据,另外,方法区包含了一个特殊的区域“运行时常量池”,

1.3.2 方法区内存溢出

1.3.3 运行时常量池

  • 常量池
    Hello word 项目想运行:需要编译成二进制字节码:类基本信息,常量池,类方法定义(包含虚拟机指令)。
    定义:就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
  • 运行时常量池:
    常量池*.class文件中,当类被加载,它的常量池信息会被加载到运行时常量池,并且会将里面的符号地址变成真实地址

1.3.4 String Table

//常量池中的信息,都会被加载到运行时常量池中,这是 a、b、ab都是常量池中的符号,还没有变成java字符串对象
//ldc #2 会把a符号变为“a”字符串对象。
//ldx #3 会把b符号变成“b”对象
当执行哪一个代码时,才会将符号变成字符串,属于懒惰的

  • 在变成字符串后,会准备好一块空间,叫StringTable (串池)
    • 当符号a变成字符串“a”时,会到以a位key,到StringTable中找a。如果没有则会将字符串“a”存入StringTable中。

//StringTable[“a”,“b”,“ab”]
public static void main (String[] args){
	String s1="a";
	String s2="b";
	String s3="ab"
}

下面时编辑时生成的字节码
在这里插入图片描述
下图为执行的顺序
在这里插入图片描述

public class Demo1 {
    public static void main(String[] args) {
        String  s1="a";
        String  s2="b";
        String  s3="ab";
        String s4=s1+s2;//new StringBuilder().append(“a”).append("b").toString  =--------------new String ("ab")
        //执行String S4=s1+s2;
        //先创建new StringBuilder()方法
        //然后调用StringBuilder的append的方法append("a")
        //然后调用StringBuilder的append的方法append("b")
        //最后调用toString方法

		System.out.println(s3==s4);//false
		//s3=ab存储的位置在Stringtable中(串池)
		//s4属于new的对象,存储的位置在堆中

    }
}

在这里插入图片描述

执行 String s5=“a”+“b”============不需要再次保存变量ab
这是因为:javac在编辑时的优化,变量a和b是不会变的,所有结果已经在编辑期间就可以确定(ab)。不需要string builder进行拼接。
而String s4=s1+s2中,s1和s2不是常量,只有在运行时用stringbuilder动态的拼接,去获取s1和s2的值
在这里插入图片描述

1.3.5 字符串对象延迟加载

在这里插入图片描述

1.3.6 StringTable的特性

  • 常量池中的字符串仅是符号,第一次用到的时候才会编程对象
  • 利用串池的机制,来避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuilder(创建new新的对象,对象的存储位置在堆中)
  • 字符串常量的拼接过程是编辑器优化(存放在方法区中的:stringtable中)
  • 可以使用intern方法,主动将串池中还没有的字符串对象放进串池
    • jdk1.8版本将这个字符串放进串池,如果有并不会放入,如果没有则放入串池,都会把串池中的对象返回
    • jdk1.6将这个字符串放入串池,如果有则不会放入,如果没有则会把该对象复制一份,(此时:堆中和stringtable中都会有该对象)放进串池,且把串池中的对象返回
  • jdk1.8+============stringable中没有在这里插入图片描述
  • jdk1.8============stringable中有 在这里插入图片描述
  • jdk1.6版本在这里插入图片描述

1.3.7面试中

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.3.8 stringable中的位置

在这里插入图片描述

1.3.9 StringTable中也会有垃圾回收

StringTable中存的常量,无用的常量,当stringTable中存满时,就会执行垃圾回收

1.4.0StringTable性能调优

  • 如果系统中常量的个数非常多,可以适当增加桶的个数,减少哈希冲突
  • 如果系统中常量的个数非常多,且是重复度很高的,可以使用 intern 方法放入串池中
        放入串池后,使用时都从串池中取,而不是对原来字符串对象直接引用,造成字符串对象无法被回收
        如果不是重复度很高的话就没有意义,因为放入串池中的字符串常量也会非常多,这并不会比直接引用字符串对象好多少
  • 调整 -XXStringtablesize=捅的个数
  • 考虑将字符串对象是否入池

网上案例1: 全国邮局,地址有许多相同的。如果将这些地址信息都放进内存上,存储上限30G。
如果将地址信息通过intern放进Stringtable中,相同的信息就会放在内存中一次,此时占用的内存很小。

  • 现实案例:项目中如果需要许多变量,应考虑将变量放进、stringTable中
    下图为:没有将变量放进StringTable中的代码的内存占用情况
    在这里插入图片描述
    此时字符串占比将近300M
    在这里插入图片描述
  • 调优后的代码
    在这里插入图片描述
    在这里插入图片描述

1.5 直接内存

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

  • 分配成本比较高,读写操作快

  • 不受JVM内存回收管理

  • 使用前:从下面可以看出java内存中和系统内存中都保存了磁盘文件。保存了两份

    • 因为java无法操作本地文件,在java堆内存中划出java缓冲区;
    • 从用户态转移到内核态,本地方法在系统内存中划出一段系统缓冲区,将磁盘文件分部分缓冲到系统缓冲区中,间接的将系统缓冲区中数据传输到java缓冲区中;
    • 内核态转到用户态,调用输出流写入操作,将文件copy到另一个位置,循环copy,直到全部复制完成。
      在这里插入图片描述
  • 使用后:

    • ByteBuffer.allocateDirect(_size),在系统内存中分配直接内存;
    • 系统方法和java方法都可以访问直接内存;
    • 与不使用直接内存相比,减少了一次从系统缓存区向java缓冲区复制的操作,复制效率成倍上升

在这里插入图片描述

  • 疑问:直接内存不受JVM的垃圾回收机制,会不会出现内存泄漏那?
    答案:会的
package com.example.jvm;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

public class Demo3 {

    public static void main(String[] args) {
        List<ByteBuffer> list = new ArrayList<>();
        int i = 0;
        try {
            while(true){
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100*1024*1024);
                list.add(byteBuffer);
                i++;
            }
        }finally {
            System.out.println(i);
        }
    }
}

36  3.6G
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
	at java.nio.Bits.reserveMemory(Bits.java:694)
	at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
	at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
	at com.example.jvm.Demo3.main(Demo3.java:14)

1.5.1 直接内存垃圾回收原理

在这里插入图片描述直接内存不会被JVM垃圾回收机制给处理。是靠unsafe来实现内存的分配和释放
在这里插入图片描述
在这里插入图片描述

2 垃圾回收

目录

  • 如何判断对象可以回收
  • 垃圾回收算法
  • 分代垃圾回收
  • 垃圾回收器
  • 垃圾回收调优

2.1 如何判断对象可以回收

  • 引用计数法
    当这个对象被引用中,计数加1,如果这个对象没有被引用了,这个对象-1.。当为0时,此时代表没有被引用,执行垃圾回收。
  • 弊端
    *在这里插入图片描述
    A对象引用B,B+1;
    B对象引用A, A+1;。A和B没有被其他对象引用,所有AB一直未1,一直不能被垃圾回收。
    Java虚拟机采用的不是这种垃圾回收机制

2.2 可达性分析算法

  • java虚拟机中的垃圾回收器采用可达性分析来探索所有存货的对象
  • 扫描堆中的对象,看是否能够沿着GC Root为起点的引用链找到该对象,找不到,表示可以回收。
  • 那些对象可以作为GC Root对象。

2.3 四种引用

  • 强引用
  • 软引用
  • 弱引用
  • 虚引用
  • 终结期引用
    Java中提供这四种引用类型主要有两个目的:
  • 第一是可以让程序员通过代码的方式决定某些对象的生命周期;
  • 第二是有利于JVM进行垃圾回收。

1.强引用
是指创建一个对象并把这个对象赋给一个引用变量。
Object object =new Object();
String str =“hello”;
强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
如果想中断强引用和某个对象之间的关联,可以显示地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象。
2。软引用
软引用是相对强引用弱化了一些的引用,需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。 对于软引用的对象来说:当系统内存充足时他不会被回收,当系统内存不足时,他会被回收。 软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用。
3.弱引用
  弱引用需要用java.lang.ref.WeakReference类来实现,他比软引用生存周期更短;
  不管JVM内存是否够用,只要有GC运行就会被回收
4。 虚引用  
需要用java.lang.ref.PhantomReference类来实现
 与其他几种引用都不同,虚引用并不会决定对象的生命周期
 如果一个对象持有虚引用,那么他就和没有任何引用一样,随时会被GC回收,也不能单独通过它来访问对象,虚引用必须和引用队列(ReferenceQueue)配合使用
 虚引用的主要作用是跟踪对象被垃圾回收的状态。
设置虚引用的唯一目的就是在这个对象被垃圾回收的时候收到一个系统通知或者后续添加进一步的处理。Java中允许使用finalize()【完成】方法在GC将对象从内存中清除出去之前做必要的清理工作。
在这里插入图片描述

2.4 四种引用应用场景

2.4.1 软引用的应用场景(不重要的信息时图片信息)

强引用
在这里插入图片描述
软引用
在这里插入图片描述
软引用配合队列使用
list的集合中有些是null,没有必要再保存到list中,所以配合queue的形式,将软引用本身进行清理
在这里插入图片描述
弱引用
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值