深入理解java虚拟机第三版读书笔记02

以下是第二章 Java内存区域与内存溢出异常的内容

运行时数据区域

深入理解java虚拟机第三版读书笔记02

程序计数器

特点

  • 程序计数器是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
  • 程序计数器是线程私有的,线程轮换之后通过程序计数器确定要执行的指令位置。
  • 不存在内存溢出。

Java虚拟机栈

特点

  • Java虚拟机栈也是线程私有的,它的生命周期与线程相同。
  • 虚拟机栈描述的是Java方法执行的线程内存模型:每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。栈帧的入栈出栈对应一个方法的调用。
  • 局部变量表存放了编译期可知的各种Java虚拟机基本数据类型、对象引用和返回地址。
  • 局部变量表中的存储空间以 局部变量槽(Slot) 来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配。

异常

  • 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常,可能会有这两种情况:
    • 栈帧过多
    • 栈帧过大
  • 如果Java虚拟机栈容量可以动态扩展,当栈扩展时无法申请到足够的内存会抛出OutOfMemoryError异常。(HotSpot虚拟机的栈容量是不可以动态扩展的,也不会导致这种异常)

代码演示:通过-Xss参数来设定栈的大小

(无限递归导致栈帧过多)

/**
*   VM Args:-Xss128k
*   @author zzm
 */
public class JavaVMStackSOF {     
    private int stackLength = 1;
    public void stackLeak() {         
        stackLength++;         
        stackLeak();
    }
    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();         
        try {
            oom.stackLeak();         
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}

(栈帧太大)

/**
 * @author zzm
 */
public class JavaVMStackSOF {     
    private static int stackLength = 0;
    public static void test() {
        long unused1, unused2, unused3, unused4, unused5,
        unused6, unused7, unused8, unused9, unused10,              
        unused11, unused12, unused13, unused14, unused15,
        unused16, unused17, unused18, unused19, unused20,
        unused21, unused22, unused23, unused24, unused25,
        unused26, unused27, unused28, unused29, unused30,
        unused31, unused32, unused33, unused34, unused35,
        unused36, unused37, unused38, unused39, unused40, 
        unused41, unused42, unused43, unused44, unused45,
        unused46, unused47, unused48, unused49, unused50, 
        unused51, unused52, unused53, unused54, unused55, 
        unused56, unused57, unused58, unused59, unused60,
        unused61, unused62, unused63, unused64, unused65,
        unused66, unused67, unused68, unused69, unused70,
        unused71, unused72, unused73, unused74, unused75,
        unused76, unused77, unused78, unused79, unused80,
        unused81, unused82, unused83, unused84, unused85,
        unused86, unused87, unused88, unused89, unused90,
        unused91, unused92, unused93, unused94, unused95,
        unused96, unused97, unused98, unused99, unused100;
        stackLength ++;
        test();
        unused1 = unused2 = unused3 = unused4 = unused5 =
        unused6 = unused7 = unused8 = unused9 = unused10 = 
        unused11 = unused12 = unused13 = unused14 = unused15 =
        unused16 = unused17 = unused18 = unused19 = unused20 =
        unused21 = unused22 = unused23 = unused24 = unused25 =
        unused26 = unused27 = unused28 = unused29 = unused30 =
        unused31 = unused32 = unused33 = unused34 = unused35 =
        unused36 = unused37 = unused38 = unused39 = unused40 =
        unused41 = unused42 = unused43 = unused44 = unused45 = 
        unused46 = unused47 = unused48 = unused49 = unused50 =
        unused51 = unused52 = unused53 = unused54 = unused55 =
        unused56 = unused57 = unused58 = unused59 = unused60 =
        unused61 = unused62 = unused63 = unused64 = unused65 =
        unused66 = unused67 = unused68 = unused69 = unused70 =
        unused71 = unused72 = unused73 = unused74 = unused75 =
        unused76 = unused77 = unused78 = unused79 = unused80 =
        unused81 = unused82 = unused83 = unused84 = unused85 =
        unused86 = unused87 = unused88 = unused89 = unused90 =
        unused91 = unused92 = unused93 = unused94 = unused95 =
        unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }
    public static void main(String[] args) {
        try {             
            test();         
        }catch (Error e){
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}

本地方法栈

是java虚拟机栈的native方法版。
HotSpot虚拟机中并不区分虚拟机栈和本地方法栈

Java堆

特点

  • 是各个线程共享的。
  • 所有的对象实例以及数组都应当在堆上分配,日后可能出现值类型的支持。
  • Java堆中可以划分出多个线程私有的分配缓冲区,以提升对象分配时的效率。
  • Java堆既可以被实现成固定大小的,也可以是可扩展的,不过当前主流的Java虚拟机都是按照可扩展来实现的(通过参数-Xmx和-Xms设定)。

异常

如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError异常。

代码演示:
(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展,通过参数-XX:+HeapDumpOnOutOf-MemoryError可以让虚拟机在出现内存溢出异常的时候Dump出当前的内存堆转储快照以便进行事后分析)

/**·
*   VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*   @author zzm
*/
public class HeapOOM {
    static class OOMObject {
    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<OOMObject>();
        while (true) {
            list.add(new OOMObject());
        }
    }
}

方法区

特点

  • 是各个进程共享的。
  • 它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
  • JAVA8以前HotSpot使用永久代实现方法区,把堆的垃圾回收机制延申至方法区,JAVA8开始使用位于本地内存中的元空间实现方法区。
  • JAVA虚拟机规范不要求实现垃圾回收。
  • -XX:MaxMetaspaceSize:设置元空间最大值,默认是-1,即不限制,或者说只受限于本地内存大小。
  • -XX:MetaspaceSize:指定元空间的初始空间大小,以字节为单位,达到该值就会触发垃圾收集进行类型卸载,同时收集器会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX:MaxMetaspaceSize(如果设置了的话)的情况下,适当提高该值。
  • -XX:MinMetaspaceFreeRatio:作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。类似的还有-XX:Max-MetaspaceFreeRatio,用于控制最大的元空间剩余容量的百分比。

异常

如果方法区无法满足新的内存分配需求时,将抛出OutOfMemoryError异常。(由于JAVA8后方法区位于本地内存中,很少出现这种情况,一般是涉及到动态加载过多类)

运行时常量池

  • 运行时常量池是方法区的一部分。
  • 类的字节码中有一项信息是常量池表,用于存放编译期生成的类名、方法名、参数类型与字面量,这部分内容将在类加载后存放到方法区的运行时常量池中。
  • Java语言并不要求常量一定只有编译期才能产生,String类的intern方法可以将字符串常量放进字符串常量池表。

异常

常量池无法再申请到内存时会抛出OutOfMemoryError异常。

intern方法有意思的点

public class RuntimeConstantPoolOOM {
    public static void main(String[] args) {
        String str1 = new StringBuilder("计算机").append("软件").toString();
        System.out.println(str1.intern() == str1);
        String str2 = new StringBuilder("ja").append("va").toString();
        System.out.println(str2.intern() == str2);
    }
}

这段代码在JDK 6中运行,会得到两个false,而在JDK 7中运行,会得到一个true和一个false。产生差异的原因是,在JDK 6中,intern()方法会把首次遇到的字符串实例复制到永久代的字符串常量池中存储,返回的也是永久代里面这个字符串实例的引用,而由StringBuilder创建的字符串对象实例在 Java堆上,所以必然不可能是同一个引用,结果将返回false。

而JDK 7(以及部分其他虚拟机,例如JRockit)的intern()方法实现就不需要再拷贝字符串的实例到永久代了,既然字符串常量池已经移到Java堆中,那只需要在常量池里记录一下首次出现的实例引用即可,因此intern()返回的引用和由StringBuilder创建的那个字符串实例就是同一个。而对str2比较返回false,这是因为“java”这个字符串在执行String-Builder.toString()之前就已经出现过了(sun.misc.Version类中加载),字符串常量池中已经有它的引用,不符合intern()方法要求“首次遇到”的原则,“计算机软件”这个字符串则是首次出现的,因此结果返回true。

直接内存

特点

  • 直接内存就是机器的内存,JDK1.4中的NIO可以使用Native函数库直接分配堆外内存
  • 分配回收成本较高,但读写性能高
  • 不受JVM内存回收管理
  • -XX:MaxDirectMemorySize指定可使用的直接内存大小,如果不去指定,则默认与Java堆最大值(由-Xmx指定)一致

分配与回收

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

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e6%b7%b1%e5%85%a5%e7%90%86%e8%a7%a3java%e8%99%9a%e6%8b%9f%e6%9c%ba%e7%ac%ac%e4%b8%89%e7%89%88%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b002/

(0)
彭晨涛彭晨涛管理者
上一篇 2020年1月5日
下一篇 2020年1月6日

相关推荐

  • Java字节码实例探究

    深入理解java虚拟机第三版读书笔记06中介绍了class文件结构,这里我们动手实践,编译一个类查看一下它的字节码。 java源码: public class Main { pri…

    2020年1月23日
    0220
  • Java网络套接字Socket编程

    Socket概述 Socket,译作网络套接字,是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。网络套接字与一个应用进程(ip和端口号)绑定。 Socket与Ser…

    Java 2020年2月5日
    0440
  • NIO底层原理-epoll

    BIO模型存在三个socket: ServerSocket:专门用来监听是否有来自客户端的连接accept返回的Socket:专门用于处理客户端请求的socketSocket:客户…

    Java 2020年2月13日
    0110
  • Java反射机制和动态代理详解

    反射 概念 反射机制: 指的是可以于运行时加载、探知、使用编译期间完全未知的类。 程序在运行状态中,可以动态加载一个只有名称的类,对于任意一个已加载的类,都能够知道这个类的所有属性…

    Java 2020年2月15日
    0580
  • Java基础查缺补漏04

    继续我的复习刷题 接口方法可以使用abstract修饰 问题: java接口的方法修饰符可以为?(忽略内部接口) A. privateB. protectedC. finalD. …

    Java 2020年5月28日
    0200
  • Java自动装箱缓存机制

    尝试运行这段代码: 相似的两段代码,得到的结果却完全不相同。 首先要知道在java中==比较的是对象的引用,从直觉出发,无论是integer1、integer2还是integer3…

    Java 2019年12月5日
    0140
  • Java多线程基础

    多线程应用 异步调用 以调用方角度来讲,如果+ 需要等待结果返回,才能继续运行就是同步+ 不需要等待结果返回,就能继续运行就是异步 同步在多线程中还有另外一个意思,是让多个线程步调…

    2020年1月31日
    0340
  • 深入理解java虚拟机第三版读书笔记05

    续深入理解java虚拟机第三版读书笔记04 HotSpot的算法细节实现 根节点枚举 如何高效的找出所有GC Roots? 迄今为止,所有收集器在根节点枚举这一步骤时都是必须暂停用…

    2020年1月12日
    0570
  • 基于NIO模型改进多人聊天室

    第一版和第二版见基于BIO模型实现多人聊天室 NIO介绍见: NIO、BIO模型对比实现文件的复制NIO网络编程之Selector介绍 第三版:使用NIO模型改进 服务端实现 pu…

    Java 2020年2月7日
    0200
  • ThreadPoolExecutor源码分析-线程池如何实现线程复用?

    线程的复用问题 在开始看线程池的源码之前,先来看这么一个问题: 一个Thread对象可以重复地调用start()方法吗? 试试就知道了: @Test public void tes…

    2020年5月21日
    02520

发表回复

登录后才能评论