jvm记录(2)

5.堆

Heap堆

  • 通过new关键字,创建对象都会使用堆内存

  • 所有的对象实例以及数组都要在堆上分配

  • Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建

  • 包含字符串常量池

特点:

  • 他是线程共享的,堆中对象都需要考虑线程安全的问题

  • 有垃圾回收机制

堆内存诊断

  1. jps工具

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

  2. jmap工具

    1. 查看堆内存占用情况,jmap -heap 进程id

  3. jconsole工具

    1. 图形界面的,多功能的检测工具,可以连续检测

  4. jvirsualvm

    1. 图形化界面

6.方法区

  1. 所有虚拟机线程的共享区域

  2. 存储了和类相关的信息,比如:field成员变量、method data方法数据,以及成员方法和构造器方法代码部分,包括特殊方法就是类的构造器。总之就是放类的相关信息。

  3. 方法区在虚拟机启动时创建。

  4. 方法区逻辑上是堆的一个组成部分。概念上方法区是堆的一部分,但是实际上根据jvm厂商决定

  5. 存储已被虚拟机加载的类信息(如类名、访问修饰符、常量池、字段描述、方法描述(方法描述符是由方法的参数类型以及返回类型所构成。)等、常量、静态变量、即时编译器编译后的代码、Class对象;

  6. 包含 运行时常量池、静态常量池、虚方法表

方法区的变更:

Java6和6之前,常量池是存放在方法区(永久代)中的。

Java7,将常量池是存放到了堆中。

Java8之后,取消了整个永久代区域,取而代之的是元空间。运行时常量池和静态常量池存放在元空间中,而字符串常量池依然存放在堆中。

 在1.8之后方法区由元空间实现,方法类、类加载、常量池及其他进程,这些都放在本地内存中,也就是操作系统的内存。

方法区内存溢出

动态生成类的字节码,动态的类加载,代理技术广泛应用了字节码动态生成技术

如spring和mybatis中都会用到cglib,spring中用其生成代理类,代理类是aop中的核心。mybatis中用cglib生成mapper接口的实现类。spring会产生大量的运行期间生成的类,也容易发生方法区内存溢出。

常量池

运行代码,会将代码编译为二进制字节码,其中包含:类基本信息、常量池,类方法定义(包含了虚拟机指令)

查看二进制字节码,通过class文件,借助jdk的一个工具,用javap - v 名称.class 来反编译字节码

java代码

 类基本信息:

 常量池:

 类方法定义:

 

静态成员变量:通过#2找到#21和#22,#21找到#28,#28找到java/lang/system的成员变量。#22找到#29和#30,找到out变量并且其类型是java/lang/system

idc:通过#3找到#23,#23是一个utf8类型的字符串 内容是hello world。

虚方法调用invokevirtual:调用类实例方法,方法引用 

常量池

常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量(字符串、整数、布尔类型)等信息

运行时常量池,常量池就是*.class文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并将里面的符号地址编程变成真实地址。运行时常量池就是放在内存中的位置

面试题

 代码会编译为字节码文件,其中常量池中的信息会加载到本地的运行时常量池,也就是放入(StringTable)串池中,并且串池是懒惰的,只有代码执行时才会到串池中找,如果没有才会放入其中,如果有就会使用串池中的对象。

字符串拼接

 字符串拼接:new一个StringBuilder对象的构造器,准备s1参数,使用stringbuilder方法append,把参数s1放入其中,再准备s2参数,把参数s2放入append方法中,最后使用tostring方法,但是tostring方法是放回一个新创建的string对象,值为刚刚拼接的值。

如果拼接的是确定的字符串,如:String s5 = “a” + “b”; javac在编译期间的优化,结果已经在编译期间确定为ab,只需要再串池中寻找,找不到再在串池中创建。

x是放入串池,s是两个变量,放入堆中。s2是不会放入,返回串池已有对象”ab“,以此s2等于x,s不等于x。 

intern()这个方法可以将其放到串池中。

总结:

  • 常量池中的字符串仅是符号,第一次用到时才变成对象

  • 利用串池的机制,来避免重复创建字符串对象

  • 字符串变量拼接的原理是StringBuilder(1.8)(字符串不确定),直接创建,堆中

  • 字符串常量拼接的原理是编译期间优化(字符串可以确定),看串池有没有

  • 可以使用intern方法,主动将串池中还没有的字符串对象放入串池

    • 1.8将这个字符串对象尝试方法串池,如果有则并不会放入,如果没有则放入串池,会把串池中的对象返回

    • 1.6将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池,把串池对象返回。

 

s3是常量字符串拼接编译器会优化为“ab”,而常量池中没有“ab”,放入常量池。

s4是两个变量的拼接,通过StringBuilder拼接,new String(“ab”)是在堆中。

s5是一个自变量,先检查常量池内容,发现已经有了“ab”,不会创建新的对象,直接引用常量池中已有的对象。

s6先看常量池中有“ab”,直接引用常量池中已有对象

x2堆中的对象

x1常量池中对象

x2.intern()发现常量池中已有“cd”对象,没能入池成功。

x1不等于x2

x2堆对象

x2.intern()发现常量池中没有“cd”对象,将”cd“放入常量池。堆中的”cd“对象和常量池中的”cd“对象是同一个对象。

x1发现常量池中已有”cd“对象,不会创建新的,直接引用常量池中的对象

x1等于x2

在jdk1.6中:x2.intern()不会直接入池,而是复制x2对象,将复制好的对象放到常量池中,x1引用的是常量池中复制好的对象。

x1不等于x2

总结:

new String()是在堆中创建对象。

两个new String()相加是通过StringBuilder()生成新的对象。或者是a="a"; b="b",c=a+b。

"a"这种确定的常量是先看运行时常量池在不在,在的话不放,直接引用,没有才放。

”a"+"b"这种是确定的,编译优化可以直接确定为“ab",也同样是看是否存在于常量池中。

==和equals的不同:

1)对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “”是否相等;如果作用于引用类型的变量,则比较的是所指向的对象的地址

2)对于equals不能作用于基本数据类型的变量(如:不能写成 int m=1;int n=1;n.equals(m);)如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址;诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容

==        基本类型:值,应用类型:地址。

equals 引用类型:地址,String重写:值

        String a = "xiaomeng2";
        final String b = "xiaomeng";
        String d = "xiaomeng";
        String c = b + 2;
        String e = d + 2;
        System.out.println((b == d));//true
        System.out.println((a == e));//false
        System.out.println((a == c));//true

a放常量池。

b被final修饰的是常量,编译期间即可知道是什么,放到常量池中。

d直接引用b在常量池中的地址。

c中=运算中,由于b在编译期间即可知道是什么,就相当于“xiaoming”+2.直接引用a在常量池中的地址。

e中,d需要在连接中进行运算,会在堆中生成xiaoming2字符串

移植StringTable位置原因

永久代内存低,永久代FGC(重GC)才会回收,等到老年代空间不足才会触发,触发时机晚,导致StringTable回收效率低,但是StringTable使用频率高,从而,将StringTable放到堆中,只需要minner GC就可以触发回收。降低字符串的内存占用。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值