jvm-运行时内存篇

1. 程序计数器

程序计数器作用:记住下一条jvm指令的执行地址

  • 作用:记住下一条指令的执行地址
  • 特点
    • 是线程私有的
    • 不会存在内存溢出

计算机在运行程序时,对于多线程来说,会给每一个内存分配一个时间片。当时间片结束,切换线程时,每个线程都会有其私有的程序计数器来保存它当前的状态。当该线程再次抢到时间片时,便可以通过程序计数器接着继续运行。

2. 栈

栈是运行时的单位,堆是存储的单位
栈不存在gc, 但是存在 StackOverFlowError

2.1 栈大小 参数 -Xss

-Xss size #设置栈空间大小
设置栈空间数过大,会导致系统可用于创建 线程的数量减少
默认栈大小 5前 256k
5后包括 1024k
xx

2.2 栈帧

每一个栈帧对应一个方法。
a

局部变量表

槽位的长度在字节码文件中已经可以确定了。
存储方法 的形参,方法内定义的变量
基本数据类型,直接存局部变量表中,引用类型存的地址。
一个slot 4个字节,那么 double long 需要两个slot
非静态方法,this默认占据局部变量表的slot_0

    public String test2(Date date,String name){
        date=null;
        name="111";
        double weight=14.5;// 占用2个slot
        char gender='男';
        return date+name;
    }

局部变量表最大长度 为6
有5个变量
槽位副用
占用几个slot 3个
this a b/c 服用

    public void test3(){
       int a=3;
       {
           int b=2;
           b=a+1;
       }
       int c=a+1;
   }
gc root

局部变量表中的变量是重要的垃圾回收根节点,只要被局部变量表中直接或者间接的引用的对象不会被垃圾回收。
常量 、静态变量

操作数栈

深度在字节码文件种可以确定了。
宽化类型转化 — 自动类型升

long n=10;
int i=10;
n=n*i;// 自动类型提示 把i 转为long 在计算

方法返回值

动态链接 指向运行时常量池的地址引用

每一个栈帧内部都包含一个·指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)

在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池里。比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,·那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。

符号

invokeVirtual 调用对象的实例方法,根据对象实际类型进行分派, 虚派方法
invokeinterface 调用接口方法
invokespecial 实例初始化、私有、父类方法 这些是静态绑定,不会在调用时动态派发
invokeStatic 类中的静态方法,静态绑定
inovkedynamic 调用动态绑定方法

指向运行时常量池中的方法引用
a
就是在栈帧存储了一个地址,这个地址指向了到底这个栈帧(方法)在运行时常量池的位置

2.3 栈 小问题

栈溢出情况

StackOverflow
栈会出现两个情况。

  1. 栈溢出
    无限制的递归调用,导致栈溢出
    递归写的没问题,但是每个栈帧的局部变量表太大了。栈帧太大了,确定了栈大小,导致栈溢出
  2. 栈的大小是可以动态变化的时候,当栈的大小到达了整个内存空间不足了,就是抛出了oom 异常。

调整栈大小,就能保证不溢出吗

不能,调整栈大小直会减少栈溢出的可能

分配的栈内存越大越好吗

设置栈空间数过大,会导致每个线程栈空间变大,会导致系统可用于创建 线程的数量减少

栈会涉及到gc吗

不会
gc 只涉及到 堆和方法区,堆和方法区有oom的可能
程序计数器只记录 运行下一行的地址,不涉及溢出和gc
栈、本地方法栈 可能涉及栈溢出,不涉及gc

方法中定义的局部变量是否线程安全

如果局部变量在内部产生并在内部消亡的,那就是线程安全的

  • 如果方法内局部变量没有逃离方法的作用范围,它是线程安全
  • 如果是局部变量引用了对象,并逃离方法的作用方法,需要考虑线程安全
/**方法中定义的局部变量是否线程安全?   具体问题具体分析
 * @author shkstart
 * @create 15:53
 */
public class LocalVariableThreadSafe {
    //s1的声明方式是线程安全的,因为线程私有,在线程内创建的s1 ,不会被其它线程调用
    public static void method1() {
        //StringBuilder:线程不安全
        StringBuilder s1 = new StringBuilder();
        s1.append("a");
        s1.append("b");
        //...
    }

    //stringBuilder的操作过程:是线程不安全的,
    // 因为stringBuilder是外面传进来的,有可能被多个线程调用
    public static void method2(StringBuilder stringBuilder) {
        stringBuilder.append("a");
        stringBuilder.append("b");
        //...
    }

    //stringBuilder的操作:是线程不安全的;因为返回了一个stringBuilder,
    // stringBuilder有可能被其他线程共享
    public static StringBuilder method3() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("a");
        stringBuilder.append("b");
        return stringBuilder;
    }

    //stringBuilder的操作:是线程安全的;因为返回了一个stringBuilder.toString()相当于new了一个String,
    // 所以stringBuilder没有被其他线程共享的可能
    public static String method4() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("a");
        stringBuilder.append("b");
        return stringBuilder.toString();

        /**
         * 结论:如果局部变量在内部产生并在内部消亡的,那就是线程安全的
         */
    }
}
 

3.堆

对象都分配在堆上?

栈上分配
如果对象只在当前方法中使用,那么可以在栈上分配对象
标量替换栈上不是放的完整的对象。一般是放置对象的 属性

所有线程都共享堆吗? tlab

可以在堆上划分 线程私有的缓冲区 TLAB
TLAB 作用: 每个线程创建对象,为了不加锁加快效率,jvm为每个线程分配了一个私有的缓存区域 在eden中
a

3.1 堆的内部结构

7

  • Java 7及之前堆内存逻辑上分为三部分:新生区+养老区+永久区
    • Young Generation Space 新生区 Young/New
      • 又被划分为Eden区和Survivor区
    • Tenure generation space 养老区 Old/Tenure
    • Permanent Space 永久区 Perm
      s
  • Java 8及之后堆内存逻辑上分为三部分:新生区+养老区+元空间
    • Young Generation Space 新生区 Young/New
      • 又被划分为Eden区和Survivor区
    • Tenure generation space 养老区 Old/Tenure
    • Meta Space 元空间 Meta

3.2 如何设置堆大小

1
heap
最大值 物理内存的1/4,
默认值初始化值 物理内存的1/64

-Xms 堆空间初始化大小
-Xmx 堆空间最大大小
写在参数后:
-Xms6m -Xmx80m
将-xms 和-xmx 相同 为了在gc后,不需要重新计算堆空间大小,提升性能。

3.2.1 新生代 老年代比例设置

-XX:NewRatio=2 新时代1,老年代2 新生代占整个堆的1/3

-xmn 可以直接设置新生代大小一般默认

3.2.2 Eden 和幸存者区比例设置

-XX:SurvivorRatio=8 需要参数写上才行

3.2.3 空间分配担保策略 ?

在minor gc前,检查老年代最大连续可用空间是否大于新生代所有对象的总空间
true 执行minor gc 安全
false

  • 如果-XX:HandlePromotionFalilure=true ,继续检查老年代连续空间是否大于历次晋升到老年代对象的大小
    • true 执行minor gc
      a

3.3对象如何分配 ***

对象分配过程 -详细

1.new的对象先放eden区。此区有大小限制。
2.当伊甸园的空间填满时,程序又需要创建对象,JVM的垃圾回收器将对eden区进行垃圾回收(Minor GC/YGC),将伊甸园区中的不再被其他对象所引用的对象进行销毁。再加载这个新的对象放到伊甸园区 如果eden放不下 考虑超大对象 放到old区。
3.然后将伊甸园中的剩余对象移动到幸存者0区
4.如果再次触发垃圾回收,此时上次幸存下来的放到幸存者0区的,如果没有回收,就会放到幸存者1区。
5.如果再次经历垃圾回收,此时会重新放回幸存者0区,接着再去幸存者1区。
6.啥时候能去养老区呢?可以设置次数。默认是15次。
可以设置参数:-XX:MaxTenuringThreshold=< N > 设置对象晋升老年代的年龄阈值。
7.在养老区,相对悠闲。当养老区内存不足时,再次触发GC:Major GC,进行养老区的内存清理。
8.若养老区执行了Major GC之后发现依然无法进行对象的保存,就会产生OOM异常

3.3.1 对象分配原则 动态分配

  • 优先分配到Eden
  • 大对象直接分配到老年代
    • 尽量避免程序中出现过多的大对象
  • 长期存活的对象分配到老年代
  • 动态对象年龄判断
    • 如果Survivor 区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。
  • 空间分配担保
    • -XX:HandlePromotionFailure

3.4 minor gc,major gc,full gc

3.4.1 minor gc 触发机制

eden 满了 触发minor gc 顺便 清理survivor0
stw

3.4.2 major gc 触发机制

老年代空间不足,先尝试 minor gc,如果之后还不足 触发major gc

3.4.3 full gc 触发机制

system.gc 系统建议执行Full GC,但是不必然执行
老年代 空间不足
方法区空间不足
通过minor gc 进入老年代对象的内存平均大小 大于 老年代的可用内存(空间分配担保策略失败)

3.5 tlab

4.方法区

a
aa

aa
方法区
a14

4.1 放了什么 干了什么

  1. 类型信息:包名称+类名,父类完全名称,访问修饰符,接口列表
  2. field 字段信息 修饰符,类型,名称。
  3. 方法信息
  4. jit 代码缓存
  5. 运行时常量池。
    注意: 常量池表:是class 文件的一部分,用于存放编译器生成的字面量("aam",1,3 这种)和符号引用。 并且这部分内容将在类 加载之后存放到方法区的运行时常量池
    常量池有什么:
    数量值
    字符串值
    类引用
    字段引用
    方法引用。

4.2 hotspot 方法区演进

4.3 设置方法区大小 参数

-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize 的值是-1,即没有限制。

4.3 为什么要将永久代 改为 元空间

1.永久代设置空间大小比较难以确定,某下场合动态加载类多,容易oom,空间小了 容易full gc 元空间 使用直接内存。
2.对永久代调优比较困难 少出现full gc

4.4 StringTable 静态变量 为什么 调整

jdk7中将StringTable放到了堆空间中因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。
这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。

在1.6之前。StringTable在方法区永久代中,但是放在这之中,虚拟机只会在full GC时才会对StringTable进行垃圾回收。

但是其实StringTable的操作是非常频繁的,如果没有即使进行垃圾回收,容易造成永久代空间不足。

在1.7后,StringTable放在了堆中,使其垃圾回收的效率更高。

放在了堆中

4.5 方法区垃圾回收什么

主要回收 常量池中废弃的常量不再使用的类型
常量池的有什么?
常量池中主要存放两大常量:字面量和符号引用
字面量:使用""引起来的字符串、使用final修饰的基本数据类型变量
符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符

4.6 方法区演进细节

l

5. stringtable 和直接内存

5.1 stringtable

5.1.1 特性

常量池中的字符串仅是符号,第一次用到时才变成对象。
利用串池的机制,来避免重复创建字符串对象
字符串变量拼接的原理是StringBuilder(1.8)
字符串常量拼接的原理是编译期优化

5.1.2 StringTable 垃圾回收

StringTable中的字符串常量不再引用后,也会被垃圾回收。
清理了一些未引用的字符串常量。
a

public class Demo1_23 {
    public static void main(String[] args) {
        int i = 0;
        try {
            for (int j = 0; j < 10; j++) {
                String.valueOf(j).intern();
                i++;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            System.out.println(i);
        }
    }
}

aa

for (int j = 0; j < 100000; j++) {
    String.valueOf(j).intern();
    i++;
}

11

5.1.3 StringTable性能调优

  • 调整 -XX:StringTableSize=桶个数
  • 考虑是否将字符串对象放入池中,即用intern
参数调优

StringTable的数据结构实现是哈希表,调优即对哈希表的桶的个数的调整。
-XX:StringTableSize=1009

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值