JVM内存分配机制详解

目录

1. 对象创建流程(TODO)

1.1 jvm分配内存 

1.2 设置对象头 

1.2.1 对象头实例

1.2.2 指针压缩

2.JVM对象内存分配 

2.1 逃逸分析和标量替换

 2.1.1 逃逸分析和标量替换实战

 2.2 eden区分配内存过程

2.3 大对象分配进入老年代

 2.4 长期存活的对象进入老年代

2.5 动态年龄进入老年代

2.6 老年代空间分配担保机制

2.5 计算生产模拟系统JVM参数设置

3.对象内存回收机制

3.1 finalize方法判断对象是否存活 

 3.2 如何判断一个类是一个无用的类


1. 对象创建流程(TODO)

A a = new A()过程 分为:

1. 类加载检查,如果加载过进行下一步,没有加载过使用类加载器加载类

2. 分配内存

3. 初始化

4. 设置对象头

5. 执行init方法

1.1 jvm分配内存 

jvm分配内存方式:

        1.指针碰撞:将一块内存分配为已使用和未使用,中间分配使用指针分隔,分配内存就是将指针移动到与对象大小相同的内存块处。

        2.空闲列表:一般内存中可能有不相邻的内存块,虚拟机会维护一个列表,标记哪些是使用过的内存和未使用的内存。分配内存的时候会更新这个列表。

jvm解决分配内存并发问题的方法:

        1.CAS+失败重试的方式:cas就是判断是否是否相同,如果相同更新的方式,没有更新成功,还有失败重试机制。

        2.TLAB:将分配内存的动作按照线程划分为在不同空间,每个空间预先分配一段内存。默认使用-XX:+/-UseTLAB参数设定是否开启TLAB,默认jdk1.8开启,使用-XX:TLABSIZE指定TLAB大小

1.2 设置对象头 

 对象在内存中分为对象头,实例数据,对齐填充。

对象头:

        1.mark Word

        2.Klass Pointer指针(堆中对象指向方法区的类信息)

        3.数组长度

1.2.1 对象头实例

打印对象头实战: 

1.引入pom.xml

<!--        引入jol包-->
        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.10</version>
        </dependency>
import org.openjdk.jol.info.ClassLayout;

public class Math {

    public static void main(String[] args) {
        //SIZE 前8位markword 中间4位jclass Pointer指针指向方法区类信息 最后4位对齐填充8的整数位
        //总共16字节
        ClassLayout classLayout = ClassLayout.parseInstance(new Object());
        System.out.println(classLayout.toPrintable());

        System.out.println("----------------------");

        //SIZE 前8位markword 中间4位jclass Pointer指针指向方法区类信息 4位数组才有 8位数组大小,1个int占4个字节
        //总共24字节
        ClassLayout classLayout1 = ClassLayout.parseInstance(new int[]{1,2});
        System.out.println(classLayout1.toPrintable());

        System.out.println("----------------------");

        //SIZE 前8位markword 中间4位jclass Pointer指针指向方法区类信息 4位int占4个字节
        //1位 byte 3位对齐整数4填充位 4位string对象占4位 4位Object对象占4位 4位填充位
        //总共32字节
        ClassLayout classLayout2 = ClassLayout.parseInstance(new A());
        System.out.println(classLayout2.toPrintable());

    }
}

class A{
    int a; //占4个字节
    String name; //占4个字节
    byte c; //占1个字节,默认会对齐填充4的整数位,补齐后面3位(alignment/padding gap)
    Object o ;//占4个字节
}

 hostspot源码对象内存分配如下:

 

1.2.2 指针压缩

jdk1.6以后默认开启指针压缩,主要对对象头Kclass Pointer指针进行压缩,以得到压缩内存的目的,帮助减少内存压力。

-XX:-UseCompressedOops(默认开启)

-XX:-UseCompressedOops(关闭指针压缩)

-XX:+UseCompressedClassPointers(默认只开启压缩对象头里的KClass Pointer指针)

为什么要进行指针压缩?

在早期32位操作系统中,内存最多可以占用2的32次方数据,如果使用64位系统,内存可以有2的64次方数据,在进行指针压缩以后将超过2的32次方数据进行压缩可以放入内存,能存储更多数据,在拿到CPU寄存器寻址时会进行解码操作还原数据本来内存大小。

在内存小于4G时,默认不开启指针压缩。

如果内存大于32G,默认使用8字节进行寻址,压缩指针会失效,因为64位操作系统使用32位,内存会多出1.5倍,会导致占用较大带宽,GC承受较大压力,所以互联网一般不采用超过内存32G的服务器。

2.JVM对象内存分配 

2.1 逃逸分析和标量替换

逃逸分析:一个对象在方法参数列表里面,在方法返回值里面返回,就代表这个对象逃逸了。如果只能在某个方法内使用,代表对象没有逃出方法区,jdk7以后默认开启逃逸分析,我们可以使用-XX:-DoEscapeAnalysis关闭逃逸分析。非逃逸的对象在方法区分配内存的时候默认分配内存如果不超过桟帧大小可以放入桟空间中,大大减少了堆内存的压力。

标量替换:在对象确定不能逃逸以后,并且对象还可以分解成成员变量时,jvm就不会创建对象,而是将对象的成员变量由方法使用的成员变量替换,方法使用的成员变量由寄存器或者桟帧中分配内存,这样可以存在不连续的内存空间上。jdk7以后默认开启,可以使用-XX:+EliminateAllocations空间,+号代表开启,-号代表关闭。 

public class EscapeAnalysis {

    /**
     * A对象逃逸出getA方法
     * @return
     */
    public A getA(){
        A a = new A();
        a.setName("逃逸分析");
        return a;
    }

    /**
     * A对象未逃逸出getB方法
     * @return
     */
    public void getB(){
        A a = new A();
        a.setName("逃逸分析");
    }
}

class A{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 2.1.1 逃逸分析和标量替换实战

/**
 * 1.默认会开启逃逸分析和标量替换
 * -Xmx15M -Xms15M -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
 * 2.关闭逃逸分析
 * -Xmx15M -Xms15M -XX:-DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations
 * 3.关闭标量替换
 * -Xmx15M -Xms15M -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations
 */
public class Test {

    /**
     * A对象未逃逸出getB方法
     * @return
     */
    public static void getB(){
        A a = new A();
        a.setName("逃逸分析");
    }

    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        for(int i = 0;i < 100000000;i++){
            getB();
        }
        long end = System.currentTimeMillis();
        System.out.println("占用时间:"+(end - start)/1000+"秒");
    }
}

class A{
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 2.2 eden区分配内存过程

经过测试得知eden区默认63.5M内存,在57.6M时占满Eden区内存,不知道中间为什么会有一部分空间没有占用仍然显示100%的情况。在eden区占满以后往S0区移动,大对象会往老年代放入,后续的对象继续往eden区放入。

/**
 * eden区分配内存
 * -XX:+PrintGCDetails
 */
public class GCTest {

    public static void main(String[] args) {
        byte[] obj1,obj2,obj3,obj4,obj5;
        obj1 = new byte[59000*1024];//eden区默认63.5M内存,在57.6M时占满Eden区内存

        obj2 = new byte[8000*1024];//进行mimor gc将大对象往老年代移动一部分,S0区移动一部分

        obj3 = new byte[8000*1024];//下面三个对象继续往Eden区放入
        obj4 = new byte[8000*1024];
        obj5 = new byte[8000*1024];
    }

}

2.3 大对象分配进入老年代

在使用serial或ParNew垃圾回收器时可以设置,下面使用serial垃圾回收器

-XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC -XX:+PrintGCDetails参数来让大对象直接进入老年代。
/**
 * eden区分配内存
 * -XX:+PrintGCDetails 打印GC详细内容
 * -XX:PretenureSizeThreshold=1000000 单位字节 -XX:+UseSerialGC --在使用serial年轻代垃圾回收器时可以设置大对象参数
 * -XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC -XX:+PrintGCDetails
 * --大对象会直接进入老年代
 */
public class GCTest {

    public static void main(String[] args) {
        byte[] obj1;
        obj1 = new byte[8000*1024];//直接将大对象放入老年代
    }
}

 2.4 长期存活的对象进入老年代

正常年轻代中对象的年龄要达到15次就要进入老年代,但是我们可以通过设置参数来让这个年龄提前进入老年代。不同的垃圾回收器年龄可能不同,CMS垃圾回收器默认6岁。

-XX:MaxTenuringThreshold设置回收年龄

2.5 动态年龄进入老年代

在eden占满以后,发生Minor GC,会把对象从eden挪动到s0区,如果对象超过s0区的一半,会直接进入老年代。公式:年龄>=1的对象内存总和大于s0区的50%就会进入老年代。

2.6 老年代空间分配担保机制

 简单说:就是每次再进入minor GC之前会判断是否要进行一次Full GC,这样就可以减少Minor GC,通过设置-XX:-HandlePromotionFailure来实现。

2.5 计算生产模拟系统JVM参数设置

如下计算后的JVM内存参数建议如下:

java -Xms3072M -Xmx3072M -Xss1M -XX:MetaspaceSize=512M -XX:MaxMetaspaceSize=512M -jar jar包

如果要设置eden,s0,s1区比例,需要设置-XX:+UseAdaptiveSizePolicy(默认开启),参数会默认开启,动态扩容年轻代比例。如果要关闭使用-XX:+UseAdaptiveSizePolicy参数

3.对象内存回收机制

程序计数器法:jdk默认不使用,当一个对象被其他对象引用程序技术器就会加1,如果对象=null没有引用时减1,但是可能会出现A引用B,B引用A导致对象无法回收的情况。

GC Roots引用计数法:从GC Roots往下引用到其他对象形成一条引用链,当下面没有引用时对象被标记为垃圾,当下一次垃圾回收时会被垃圾回收器回收。常用的GC Roots:本地方法桟中引用的对象,方法区中的常量,静态变量等。

java的4大引用:强,虚,软,弱。

强引用:new 对象。这样的对象即使发生异常也不会被强制回收。

弱引用:new SoftReference<User>new User();应用场景:当一个页面需要回退时会返回到上一个页面,当发生GC时会回收掉这个对象。

虚引用和软引用几乎不用,忽略。

3.1 finalize方法判断对象是否存活 

1.第一次判断是否覆盖了finalize方法没有直接将没有与GC Roots关联的对象直接回收。

2.使用finalize方法可以进行对象的自救,比如将对象赋值给引用链上的某一个类变量或常量。

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * 测试使用finallize方法进行对象的自救
 */
public class User {

    private int id ;
    private String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        //对象自救方法
//      OOMTest.list.add(this);
        System.out.println("id为:"+id+"的对象即将被回收!");
    }
}

class OOMTest{
    public static List<User> list = new ArrayList<User>();

    public static void main(String[] args) {
        List<User> strings = new ArrayList<User>();
        int i = 0,j = 0;
        while(true){
            strings.add(new User(i++, UUID.randomUUID().toString()));
            new User(j--,UUID.randomUUID().toString());
        }
    }
}

 3.2 如何判断一个类是一个无用的类

1.java中堆和堆指向方法区的指针都被回收

2.类的类加载器被回收掉,应用场景:tomcat中的大量自定义加载器比如webappClassLoader和jspClassLoader都应该被回收掉。

3.java中不存在引用的对象,如果存在还是会通过反射生成相应的类。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
手把手视频详细讲解项目开发全过程,需要的小伙伴自行百度网盘下载,链接见附件,永久有效。 课程简介 JVMJava 程序的运行环境,学习 JVM,方能了解 Java 程序是如何被执行的,为进一步深入底层原理乃至程序性能调优打好基础。通过学习这门课程,你将掌握:1. JVM 内存结构的组成、各部分功能作用,学会利用内存诊断工具排查内存相关问题;2. JVM 的招牌功能-垃圾回收机制是如何工作的,如何进行垃圾回收调优;3. Java 程序从编译为字节码到加载到运行的全流程,各个阶段的优化处理;4. 了解 Java 内存模型相关知识,见识多线程并发读写共享数据时的问题和 Java 的解决方案。 适应人群 有一定的Java基础,希望提升 Java 内功的人群。 课程亮点 * 系统地学习 JVM 内存结构,垃圾回收、字节码与类加载技术。 * 在内存结构章节,能够学习掌握 JVM内存溢出现象,堆栈内存结构,利用内存诊断工具排查问题。彻底分析 StringTable的相关知识与性能优化,掌握直接内存分配原理和释放手段。 * 在垃圾回收章节,不仅会介绍垃圾回收算法、分代垃圾回收机制,还会重点介绍 G1 垃圾回收器,辨析 Full GC 发生条件,jdk8以来对垃圾回收的优化,以及垃圾回收的调优法则。 * 在字节码与类加载技术章节,会从一个 class 文件开始分析其每一字节的含义。学习字节码指令的的运行流程,字节码指令与常量池、方法区的关系。掌握条件分支、循环控制、异常处理、构造方法在字节码级别的实现原理,利用HSDB工具理解多态原理。还会涉及从编译期的语法糖处理,到类加载的各个阶段,直至运行期的各项优化的详细讲解。最后不要错过方法反射优化的底层分析。 * 最后的加餐环节是带着你理解 Java 内存模型:见识多线程读写共享数据的原子性、可见性、有序性,以及很多人解释不清楚的 happens-before 规则。当然还不能少了 CAS 和 synchronized 优化。 主讲内容 第一章:引言 1. 什么是 JVM ? 2. 学习 JVM 有什么用 ? 3. 常见的 JVM 4. 学习路线 第二章:内存结构 1. 程序计数器 2. 虚拟机栈 3. 本地方法栈 4. 堆 5. 方法区 6. 直接内存 第三章:垃圾回收 1. 如何判断对象可以回收 2. 垃圾回收算法 3. 分代垃圾回收 4. 垃圾回收器 5. 垃圾回收调优 第四章:类加载与字节码技术 1. 类文件结构 2. 字节码指令 3. 编译期处理 4. 类加载阶段 5. 类加载器 6. 运行期优化 第五章:内存模型 1. Java 内存模型 2. 可见性 3. 有序性 4. CAS 与原子类 5. synchronized 优化
2019最新深入理解JVM内存结构及运行原理(JVM调优)高级核心课程视频教程下载。JVMJava知识体系中的重要部分,对JVM底层的了解是每一位Java程序员深入Java技术领域的重要因素。本课程试图通过简单易懂的方式,系统的深入讲解JVM相关知识。包括JVM执行过程、虚拟机类加载机制、运行时数据区、GC、类加载器、内存分配与回收策略等,全套视频加资料高清无密码  第1讲 说在前面的话 免费 00:05:07  第2讲 整个部分要讲的内容说明 免费 00:06:58  第3讲 环境搭建以及jdk,jre,jvm的关系 免费 00:20:48  第4讲 jvm初体验-内存溢出问题的分析与解决 免费 00:17:59  第5讲 jvm再体验-jvm可视化监控工具 免费 00:21:17  第6讲 杂谈 免费 00:12:37  第7讲 Java的发展历史 00:27:24  第8讲 Java的发展历史续 00:02:27  第9讲 Java技术体系 00:08:46  第10讲 jdk8的新特性 00:07:31  第11讲 lanmbda表达式简介 00:07:02  第12讲 Java虚拟机-classic vm 00:06:06  第13讲 Java虚拟机-ExactVM 00:03:35  第14讲 Java虚拟机-HotSpotVM 00:04:23  第15讲 Java虚拟机-kvm 00:03:04  第16讲 Java虚拟机-JRockit 00:04:12  第17讲 Java虚拟机-j9 00:04:23  第18讲 Java虚拟机-dalvik 00:02:20  第19讲 Java虚拟机-MicrosoftJVM 00:03:57  第20讲 Java虚拟机-高性能Java虚拟机 00:02:58  第21讲 Java虚拟机-TaobaoVM 00:03:06  第22讲 Java内存区域-简介 00:07:56  第23讲 Java内存区域-Java虚拟机栈 00:12:04  第24讲 Java内存区域-程序计数器 00:12:54  第25讲 Java内存区域-本地方法栈 00:02:39  第26讲 Java内存区域-堆内存 00:05:08  第27讲 Java内存区域-方法区 00:06:32  第28讲 Java内存区域-直接内存和运行时常量池 00:15:53  第29讲 对象在内存中的布局-对象的创建 00:21:19  第30讲 探究对象的结构 00:13:47  第31讲 深入理解对象的访问定位 00:08:01  第32讲 垃圾回收-概述 00:06:20  第33讲 垃圾回收-判断对象是否存活算法-引用计数法详解 00:14:08  第34讲 垃圾回收-判断对象是否存活算法-可达性分析法详解 00:07:09  第35讲 垃圾回收算法-标记清除算法 00:04:36  第36讲 垃圾回收算法-复制算法 00:14:35  第37讲 垃圾回收算法-标记整理算法和分代收集算法 00:05:24  第38讲 垃圾收集器-serial收集器详解 00:09:45  第39讲 垃圾收集器-parnew收集器详解 00:04:53  第40讲 垃圾收集器-parallel收集器详解 00:11:02  第41讲 垃圾收集器-cms收集器详解 00:14:58  第42讲 最牛的垃圾收集器-g1收集器详解 00:18:04  第43讲 内存分配-概述 00:04:23  第44讲 内存分配-Eden区域 00:22:51  第45讲 内存分配-大对象直接进老年代 00:06:42  第46讲 内存分配-长期存活的对象进入老年代 00:03:40  第47讲 内存分配-空间分配担保 00:04:54  第48讲 内存分配-逃逸分析与栈上分配 00:10:32  第49讲 虚拟机工具介绍 00:10:27  第50讲 虚拟机工具-jps详解 00:11:20  第51讲 虚拟机工具-jstat详解 00:09:20  第52讲 虚拟机工具-jinfo详解 00:05:03  第53讲 虚拟机工具-jmap详解 00:08:48  第54讲 虚拟机工具-jhat详解 00:08:10  第55讲 虚拟机工具-jstack详解 00:10:19  第56讲 可视化虚拟机工具-Jconsole内存监控 00:07:09  第57讲 可视化虚拟机工具-Jconsole线程监控 00:12:18  第58讲 死锁原理以及可视化虚拟机工具-Jconsole线程死锁监控 00:10:38  第59讲 VisualVM使用详解 00:08:03  第60讲 性能调优概述 00:11:22  第61讲 性能调优-案例1 00:23:28  第62讲 性能调优-案例2 00:10:05  第63讲 性能调优-案例3 00:12:41  第64讲 前半部分内容整体回顾 00:15:41  第65讲 Class文件简介和发展历史 免费 00:11:26  第66讲 Class文件结构概述 免费 00:16:50  第67讲 Class文件设计理念以及意义 免费 00:13:41  第68讲 文件结构-魔数 免费 00:09:49  第69讲 文件结构-常量池 免费 00:23:44  第70讲 文件结构-访问标志 免费 00:11:36  第71讲 文件结构-类索引 00:11:26  第72讲 文件结构-字段表集合 00:13:21  第73讲 文件结构-方法表集合 00:10:06  第74讲 文件结构-属性表集合 00:18:23  第75讲 字节码指令简介 00:09:18  第76讲 字节码与数据类型 00:09:34  第77讲 加载指令 00:09:33  第78讲 运算指令 00:10:24  第79讲 类型转换指令 00:13:42  第80讲 对象创建与访问指令 00:09:38  第81讲 操作树栈指令 00:03:27  第82讲 控制转移指令 00:11:58  第83讲 方法调用和返回指令 00:06:37  第84讲 异常处理指令 00:09:44  第85讲 同步指令 00:07:34  第86讲 类加载机制概述 00:07:26  第87讲 类加载时机 00:13:15  第88讲 类加载的过程-加载 00:15:15  第89讲 类加载的过程-验证 00:10:24  第90讲 类加载的过程-准备 00:05:40  第91讲 类加载的过程-解析 00:14:04  第92讲 类加载的过程-初始化 00:19:41  第93讲 类加载器 00:22:41  第94讲 双亲委派模型 00:17:03  第95讲 运行时栈帧结构 00:08:46  第96讲 局部变量表 00:20:48  第97讲 操作数栈 00:08:36  第98讲 动态连接 00:02:56  第99讲 方法返回地址和附加信息 00:03:24  第100讲 方法调用-解析调用 00:09:49  第101讲 方法调用-静态分派调用 00:16:21  第102讲 方法调用-动态分派调用 00:09:02  第103讲 动态类型语言支持 00:09:27  第104讲 字节码执行引擎小结 00:03:38  第105讲 总结与回顾 00:10:55  第106讲 happens-before简单概述 00:15:17  第107讲 重排序问题 00:23:19  第108讲 锁的内存语义 00:13:54  第109讲 volatile的内存语义 00:12:04  第110讲 final域内存语义

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

bingtanghulu_6

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

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

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

打赏作者

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

抵扣说明:

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

余额充值