JVM黑马版:笔记、应用、速查

2023-11-02

前言

由于工作中时常和JVM打交道,但是对JVM的体系缺乏系统深入了解。日前跟随b站上黑马程序猿的课程成体系地学习了JVM,结合工作中的实践写就了此笔记。

黑马原视频地址:https://www.bilibili.com/video/BV1yE411Z7AP

1、概述:什么是JVM

2、内存结构

学习概述图:会根据下图依次介绍之:
在这里插入图片描述

2.1 程序计数器

在这里插入图片描述

在这里插入图片描述
程序计数器线程私有的理解:
在这里插入图片描述

2.2 虚拟机栈

在这里插入图片描述

栈帧的概念:一次方法调用

每个方法需要的内存存放于栈帧,方法的局部变量也存放于栈帧
在这里插入图片描述

问题辨析

在这里插入图片描述
1,垃圾回收是否涉及栈内存?

​ 不会,栈内存就是一次次的方法调用所产生的栈帧内存,栈帧内存在每一次的方法调用结束后后被弹出栈,自动的被回收掉,不需要垃圾回收。

2,栈内存分配越大越好么?

​ 栈内存划分的越大会使得线程数变少,因为我们物理内存的大小是一定的

如果是static修饰,会被多个线程使用,需要考虑线程安全问题:
在这里插入图片描述
局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全
在这里插入图片描述

栈内存溢出(Stack Overflow)

栈帧过多导致内存溢出(案例:疯狂的递归调用)

栈帧过大导致内存溢出(不易出现,因为局部变量只占一点空间)

可以使用虚拟机参数:Xss 来设置栈区内存大小。

线程运行诊断

在这里插入图片描述

2.3 本地方法栈

native关键字

native关键字说明其修饰的方法是一个原生态方法,方法对应的实现不是在当前文件,而是在用其他语言(如C和C++)实现的文件中。Java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言来实现对底层的访问。

JNI是Java本机接口(Java Native Interface),是一个本机编程接口,它是Java软件开发工具箱(java Software Development Kit,SDK)的一部分。JNI允许Java代码使用以其他语言编写的代码和代码库。Invocation API(JNI的一部分)可以用来将Java虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用Java代码。

本地方法栈

其实就是给本地方法运行提供一个内存空间。

本地方法栈用于支持 native 方法的执行,存储了每个 native 方法调用的状态。本地方法栈和虚拟机方法栈运行机制一致,它们唯一的区别就是,虚拟机栈是执行 Java 方法的,而本地方法栈是用来执行 native 方法的,在很多虚拟机中(如 Sun 的 JDK 默认的 HotSpot 虚拟机),会将本地方法栈与虚拟机栈放在一起使用。

2.4 堆

堆概念

在这里插入图片描述

堆内存溢出(OutOfMemoryError)

代码示例:
在这里插入图片描述
上图例子是对象体积越来越大,最终撑爆了堆内存。
虚拟机参数:-Xmx 用来控制堆内存大小。例:-Xmx8m,指定堆内存为8MB。

堆内存诊断

在这里插入图片描述

jmap查看堆内存占用情况:
在这里插入图片描述

2.5 方法区

方法区定义:

  • 方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量(const)、静态变量(static)、即时编译器编译后的代码等数据,方法编译出的字节码就是保存在这个区域
  • 虚拟机启动时,方法区被创建。
  • 方法区内存不足也会抛出OutOfMemoryError
  • 在Java1.6里面 ,方法区在堆上,占用堆内存,称为永久代,然而Java1.8以后叫元空间占用本地内存(操作系统内存)

反射与方法区

在这里插入图片描述

方法区内存溢出

在这里插入图片描述

常量池和运行时常量池

1、 .class文件中静态的常量池 无非就是一张表,虚拟机指令通过这张常量表找到要执行的类名,方法名等信息(不进JVM运行时候就有了,就是.class文件的一张表而已)

2、 运行时常量池在1.8以后,是方法区的一部分。
在这里插入图片描述
其实黑马这里说的不明白,我自己查阅了知乎,对常量池有了更深的理解。常量池分两种:一是.class文件中静态的常量池,二是.class文件中的静态常量池被加载到JVM中而形成的运行时常量池。如下图所示:

https://zhuanlan.zhihu.com/p/141072562

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

如何查看静态常量池案例:
从编译目录(out目录),找到HelloWorld.java的class文件:HalloWorld.class,然后执行命令javap -v HelloWorld.class,即可看到反编译后的详细信息。

在这里插入图片描述
可以看到.class详细信息如下所示:

Classfile /home/daji/data/datas/studyFiles/资料-解密JVM/jvm/out/production/jvm/cn/itcast/jvm/t5/HelloWorld.class
  Last modified 2021年10月29日; size 567 bytes
  SHA-256 checksum 37204bf6e654f64ae56660a1e8becfaa98b3ae7592b81b4b6e331de92a460b96
  Compiled from "HelloWorld.java"
public class cn.itcast.jvm.t5.HelloWorld
  minor version: 0
  major version: 52
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #5                          // cn/itcast/jvm/t5/HelloWorld
  super_class: #6                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            // hello world
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            // cn/itcast/jvm/t5/HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcn/itcast/jvm/t5/HelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               hello world
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               cn/itcast/jvm/t5/HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public cn.itcast.jvm.t5.HelloWorld();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 4: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcn/itcast/jvm/t5/HelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 6: 0
        line 7: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

还有一个非常重要的字符串常量池,趁热打铁直接移步到 2.5.5 b 去看!

各种池总结(2022年8月23日 )

方法区和常量池对比

方法区:方法区与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量(const)、静态变量(static)、即时编译器编译后的代码等数据,方法编译出的字节码就是保存在这个区域。

常量池:是方法区的一部分,虚拟机指令通过这张常量表找到要执行的类名,方法名

常量池分2种:静态常量池,运行时常量池,常量池在方法区中。

还有一种池叫字符串池,以及各种包装类型(Integer)的缓冲池。
字符串池1.8在堆中,1.8之前在方法区里。

最终总结:字符串池在堆中,常量池在方法区中。方法区占用操作系统内存,不占用堆内存。(1.8)

在Java1.6里面 ,方法区在堆上,占用堆内存,称为永久代,1.8中 方法区占用操作系统内存,不占用堆内存。

2.5.5(非常重要:StringTable 意为字符串常量池)

a. 引入:一个StringTable的面试题

在这里插入图片描述

b. 常量池和串池(字符串常量池)的关系

在这里插入图片描述

在这里插入图片描述

HashSet 和 HashTable的概念!!!

c.字符串变量拼接的实质(StringBuilder+new)

字符串变量拼接的实质如下图所示:
在这里插入图片描述
通过上面可知,s1 s2 s3不是new出来的,而s4是new出来的。那么这两种创建字符串的方式有什么区别吗?

建议必须通读这个博客,便于理解:
https://blog.csdn.net/weixin_41098980/article/details/80060200
在这里插入图片描述
简单说来,String str = “abc” 这种方式,只会在字符串常量池里面添加一个"abc"的串;如果下次再声明String str1 = “abc”,那么str和str1的内存地址是一样的(因为字符串常量池已经有abc了,不会重复申请。这就是字符串常量池的作用节省空间)

但是使用new String 创建字符串就不同了:
String str = new String(“abc”);至少会创建一个对象,也有可能创建两个。因为用到new关键字,肯定会在堆中创建一个String对象,如果字符池中已经存在”abc”,则不会在字符串池中添加一个"abc"的串,如果不存在,则会在字符串常量池中也添加一个"abc"的串。

因此,尽量少用new创建字符串,节省堆内存

d. 字符串常量拼接的实质(编译期优化)

这个例子和c例对比学习。

常量用+号拼接会怎样呢(c例是变量拼接)?

如下图所示:

在这里插入图片描述

那最后还有一个问题,常量和变量用+拼接在一起呢?答案是当成变量处理(StringBuilder+new出来)!

f. StringTable_intern_1.8 和 1.6

在这里插入图片描述
先看1.8:

1.8情况1:

8

1.8情况2:

在这里插入图片描述

再看1.6:

1.6和 1.8的区别就是下面一点:

在这里插入图片描述
例子直接看g的面试题。

g. StringTable面试题

再回到最开头的面试题(预测四个sout语句输出的是true还是false):

在这里插入图片描述
答案是false true true false

最后一问的调换位置版本:

在这里插入图片描述
最后一问调换位置的代码如上图所示,如果是1.8执行,就是true,如果是1.6执行,就是false。因为1.6是创建了个副本。

看到这,应该非常轻松的做对了,如果你做不对,或者有疑惑,真该好好反思下了,因为都是学过的。

h. StringTable位置

StringTable (字符串常量池)的位置:
1.8在堆里(常量池仍然在方法区(元空间)中);
1.6在方法区(永久代)中的常量池中。

案例如下图所示:

在这里插入图片描述

1.8将StringTable从方法区(也就是永久代)转移到了堆里,原因是:永久代垃圾回收效率特别低;而堆里垃圾回收效率会高。

到现在为止了,堆,方法区,常量池,StringTable四者之间的关系已经可以总结出为一张图:

在这里插入图片描述

i. StringTable垃圾回收

见案例Demo1_7.java 如果堆内存满了就会触发回收机制,回收掉StringTable没用的字符串。

j. StringTable调优

调优案例1(调整虚拟机参数):

StringTable是类似于HashSet的数据结构(也有一说是其本质上就是一个HashSet<String>

因此对其进行调优,可以通过调整虚拟机参数-XX:StringTableSize=桶个数。
举例:如果你的程序有10w个字符串要入池,你将你的虚拟机参数调整成:-XX:StringTableSize=1009;那么平均每个桶里面就要进100个串,这样容易引发哈希碰撞,入桶时间会很长。

然而你将你虚拟机调整成:-XX:StringTableSize=10000,那么每个桶只会进10个串,不容易引起哈希冲突;加载速度就会变快很多。

调优案例2(尽量使用intern):

美团要处理一大堆用户的address信息(30w条),很多用户的address是重复的。之前每次都要创建对象或是在已有对象中追加,但是这样非常耗费堆内存。

于是美团就一直调用.intern方法,如果遇到重复,就不会入串池,从而大大节省了内存空间。

如下两个图所示:

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

2.6 直接内存

直接内存的概念和定义

概念:直接内存属于操作系统的内存。它常用在NIO中。

  • 常见于 NIO 操作时,用于数据缓冲区
  • 分配回收成本较高,但读写性能高
  • 不受 JVM 内存回收管理

NIO的ByteBuffer开辟的内存空间就是来源于直接内存。学习这个的前置条件是了解NIO。如下图所示:

在这里插入图片描述

补充: NIO的ByteBuffer有两个方法,其中allocateDirect分配的字节缓冲区用中文叫做直接缓冲区(DirectByteBuffer),用allocate分配的ByteBuffer叫做堆字节缓冲区(HeapByteBuffer)。

读取文件时,传统IO的Buffer(byte[] buffer = new byte[1024])性能是不如NIO的ByteBuffer的。下图是传统的Buffer(该buffer建立在堆中):

通过下图可以看到,程序要想读取磁盘文件,先要经过系统缓冲区,然后经过Java堆内存上的缓冲区,才能读到数据。 有中间商赚差价,读取速度就慢了。

在这里插入图片描述
再看下面一张图,这是使用直接内存之后。Java程序可以直接操作直接内存,磁盘文件只需要放入直接内存的缓冲区中即可被Java程序读取到,所以这个直接内存可以大大加快IO效率。(本质上是加快了读buffer的效率)

在这里插入图片描述

直接内存溢出、分配、释放

直接内存也会溢出。下面介绍操作系统(不是JVM,直接内存不受JVM管理)如何分配和释放直接内存。

分配和回收的原理:

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

只需要记住分配和回收的原理即可,并不需要程序猿真正调用。

这个Unsafe非常底层,如果想更深的了解,详见下图Demo:
在这里插入图片描述

JVM如何禁用显式垃圾回收,及其对直接内存的影响

在使用虚拟机参数时,有时候会加上下面的参数来禁用显式垃圾回收:

-XX:+DisableExplicitGC

Explicit adj.显式的,明晰的

如果这样设置的话,会导致我们自己写的代码(手动gc):System.gc();无效

看到这大家可能有一个疑问:直接内存是不受JVM管理的,你禁止垃圾回收与否与我直接内存有什么关系?!

大家别忘了上文说的: NIO 的ByteBuffer 可以创建直接内存!

一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存。
所以GC可以释放掉直接内存。

假如我们的虚拟机参数真的设置了-XX:+DisableExplicitGC ,我们不能调用System.gc();回收掉NIO的 ByteBuffer了,但是仍然想释放掉直接内存,怎么办??

答案是使用上文所说的Unsafe来手动回收直接内存,绕开JVM虚拟机的垃圾回收!

其实,这个直接内存的内存地址,实际上是通过一个虚引用关联到Java虚拟机的。具体请看下文(页内跳转) 3.4 虚引用应用

关于直接内存,介绍到这就比较完善了!结合NIO,Buffer会更好的理解!

内存泄露和内存溢出

前面说的都是内存溢出(OOM, 内存不够了)

而内存泄露的概念是:

内存泄漏(memory leak) 是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。
2.
内存溢出(out of memory) 指程序申请内存时,没有足够的内存供申请者使用。

内存泄漏是指对象实例在新建和使用完毕后,仍然被引用,没能被垃圾回收释放,一直积累,直到没有剩余内存可用。如果内存泄露,我们要找出泄露的对象是怎么被 GC ROOT 引用起
来,然后通过引用链来具体分析泄露的原因。分析内存泄漏的工具有:Jprofiler,visualvm等。

3、垃圾回收

3.1 垃圾回收概述

学这一章节,必须了解什么是引用,什么是对象。非常重要!

引用和对象的区别

看这一段代码:User user = new User()

左边的user只是个引用变量(可以类比于C语言的指针变量)而已,它首先是一个变量,它在函数中被创建,所以存放在虚拟机的栈帧里。

而右边的new User()是一个对象,是一块地址空间,它存放在堆中!

3.2 判断垃圾

引用计数法:

引用计数法(Reference Counting)比较简单,对每一个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。
对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1;当引用失效时,引用计数器就减1。只要对象的引用计数器的值为0,即表示对象A不能在被使用,可进行回收。

缺点:循环引用问题,如下图所示:

在这里插入图片描述
但是在这种情况下,即使已经没有对象引用A和B了,仍然不能将其回收。这就是循环引用的问题,这是一条致命缺陷,导致现在已经不用引用计数法了。

可达性分析算法:

概念:
在这里插入图片描述
使用Eclipse提供的一个工具,可以看到到底哪些对象可以作为GC Root对象(工具是什么不重要,怎么使用也不重要,以下内容才是重点):

  • 第一类
    最核心的类,比如Object,String,HashMap这种,不会被回收。
    在这里插入图片描述

  • 第二类
    在这里插入图片描述

  • 第三类(特别重要!)
    活动线程中,局部变量(引用)所引用的对象,是可以作为Root对象的。
    活动线程中,局部变量(引用)所引用的对象,是可以作为Root对象的。
    活动线程中,局部变量(引用)所引用的对象,是可以作为Root对象的。
    这句话一定要理解,太重要了。如果理解不了这句话,看下文的[引用和对象的区别],然后回来理解这句话。
    这句话的潜台词其实就是,如果没有局部变量引用这块存放在堆中的地址空间(也就是对象)了,那它就不是根对象了
    看下图:
    在这里插入图片描述
    上图提到了每个线程都有自己独立的栈,这个概念可以看我之前写的文章:

在这里插入图片描述

在这里插入图片描述

  • 第四类:
    在这里插入图片描述

引用和对象的区别

看这一段代码:User user = new User()

左边的user只是个引用变量(可以类比于C语言的指针变量)而已,它首先是一个变量,它在函数中被创建,所以存放在虚拟机的栈帧里。

而右边的new User()是一个对象,是一块地址空间,它存放在堆中!

3.3 五种引用:强软弱虚终

概念如图所示:
在这里插入图片描述

对上图概念的补充说明:

  • 举例:User user = new User()。这就是一个强引用,强引用只要在GC Root引用链上就不会被回收。
  • 软引用和弱引用的区别:被弱引用引用的对象只要垃圾回收了就会被回收,被软引用引用的对象只有在垃圾回收后,内存仍然不足,才会被干掉。
  • 引用队列的概念:弱引用和软引用自身也是需要占用内存空间的,如果被弱引用/软引用引用的对象已被回收,那么软引用和弱引用自身,会进入引用队列。
  • 虚引用和终结器引用必须配合引用队列使用:当虚引用和终结器引用创建时,它们会关联一个引用队列。

3.4 虚引用介绍


虚引用应用于前面讲的直接内存,如下图所示:

在这里插入图片描述

创建ByteBuffer并使用allocateDirect开辟直接内存时,除了创建ByteBuffer对象会建立一个强引用之外,还会将直接内存地址传递给虚引用对象。

如果日后ByteBuffer这个强引用对象被垃圾回收了,但是那个直接内存空间并不能被Java的垃圾回收回收掉。虚拟机是如何回收直接内存地址的呢?

答案是在ByteBuffer被回收的时候,让虚引用对象进入引用队列。而虚引用所在的引用队列,会由一个叫ReferenceHandler的线程来定期回收该对象, 该线程回收的时候,其实就是调用前面说过的Unsafe.freeMemory()来回收掉这个直接内存。

总之,虚引用和终结器引用必须配合引用队列使用:当虚引用和终结器引用创建时,它们会关联一个引用队列。

3.5 终结器引用介绍

所有的Java对象都会继承Object类。而Object父类里面会有一个finallize()的方法(终结方法)。

它的作用类似于C++的析构函数。

无需手动编码,但其内部配合引用队列使用,在垃圾回收时,终结器引用入队(被引用对象暂时没有被回收),再 由一个叫FinallizeHandler的线程, 通过终结器引用找到被引用对象并调用它的 finalize方法,第二次 GC 时才能回收被引用对象

终结器引用回收效率很低,使用finallize方法,终结器引用释放资源效率很低,不推荐!

3.5 软引用介绍

和上面的虚引用应用场景一样,也是内存空间的开辟。

看下面的例子,下图为使用强引用开辟内存空间,会报错。

在这里插入图片描述
上图的场景比较常见,比如读取网络上的图片,然后将这些图片资源暂存到业务层进行进一步的处理。当读取图片很多的时候,就会导致堆内存溢出。即使这些图片资源并不在核心业务逻辑里。

像这种非核心业务,我们能不能想个办法在内存紧张时直接释放掉,日后如果想用,再重新读取就好了(类似于狗熊掰棒子,掰一个掉一个)

这种场景可以用软引用和弱引用实现。下图为软引用案例(用一个软引用对象new SoftReference<byte[]>

在这里插入图片描述

在这里插入图片描述
为什么只有最后一个留下来呢?因为申请到最后一个的时候,内存满了,于是触发了一次垃圾回收。导致前面几个都被回收掉了(潜台词是:如果内存没满,那么前几个就不会被回收)

将软引用本身从引用队列中清除
软引用自身也是一个引用。当软引用自身关联的那块内存空间被GC掉之后,那么软引用自身也应该被回收。(这有点像指向指针的指针)

如何清除软引用本身呢?答案是配合引用队列。见下图(可以对比上面刚写的没回收软引用本身的例子学习):

在这里插入图片描述

在这里插入图片描述

3.6 弱引用介绍

弱引用是更弱的软引用。 和软引用场景类似,只不过换成了new WeakReference这个对象。

但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
弱引用需要用
java.lang.ref.WeakReference类来实现,它比软引用的生存期更短。
对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的内存。

在这里插入图片描述

软引用和弱引用的实际应用场景:

https://blog.csdn.net/arui319/article/details/8489451

3.7 回收算法(这实际上就是操作系统的知识点)

a.标记清除

优点:速度较快

缺点:会产生内存碎片

如下图所示:
在这里插入图片描述

b.标记整理

为了解决碎片问题,实际上就是整理了一下(也是操作系统的概念),几个小块内存合成了一个大块内存。

缺点就是速度慢了。

在这里插入图片描述

c.复制

第一步:将From能被内存找到的对象复制到另一块内存空间中:
在这里插入图片描述
在这里插入图片描述
复制好之后,就可以将原来的内存空间(From区)整个删光!

最后交换From区和To区的位置 (交换指针的方式。下文会讲到)

在这里插入图片描述

这样就解决了内存碎片。它的缺点显而易见:需要占用双倍的内存空间(T
o空间)。

3.8 分代回收

上面的垃圾回收算法,JVM都有采用。分代回收机制就是综合采用上面算法的一种回收体系。JVM会针对不同的区域(新生代,老年代)采用不同的垃圾回收算法。

新生代存放的是相对来讲不重要的对象(有的甚至用过即失),老年代存放的是相对重要(不易回收)的内存空间。

a.Minor GC,以及晋升到老年代

在这里插入图片描述
下一步:

在这里插入图片描述
经过这通操作,伊甸园空了。就可以继续入对象,直到第二次将伊甸园占满。触发第二次GC。第二次GC的不同之处会在检查伊甸园区的同时,检查 幸存区From。(此时幸存区From里面的所有对象寿命至少是1了),然后尝试标记幸存区From、伊甸园没被root引用到的对象。

然后就是重复之前的步骤,将未被标记的移动到To区域 -> 删光伊甸园 -> To和From互换位置 -> From区对象寿命+1

由于是第二次GC,有些From区对象寿命就有可能是2。以此类推经过几次迭代,From区的老不死对象寿命就会越来越长。

幸存区From的寿命达到一定大小时(比如15),这个老不死的对象就别在新生代的From区呆着了,直接晋升到老年代!

老不死的对象被证明了它的价值比较高,于是它晋升到了老年代,老年代的垃圾回收频率比较低,不会频繁回收。

b.Full GC

但是随着我们一次次的调用,老年代的对象终将被占满。此时会先尝试Minor GC,如果之后空间仍然不足,就会触发 Full GC。

Full GC 会对整个新生代,老年代的所有对象进行一次清理。

老年代用的就不是复制算法了,是标记清除+标记整理。

如果老年代回收了空间仍然不足,就会OOM —— OutOfMemoryError

总结

对下图概念的解释:

  • stop the world : 字面意义,时间静止。当minor gc发生时,只有minor gc会运行,它工作的过程时,其他所有用户的所有线程都必须暂停。等待minor gc工作完,它们才能恢复运行。
  • STW 就是stop the world
    在这里插入图片描述

3.9 GC相关参数,看懂GC日志

在这里插入图片描述
看懂GC日志(使用命令-XX:+PrintGCDetails打印GC详情)(下图相当重要!!!):

在这里插入图片描述
关于上图的补充说明:幸存区from为啥占用了50% 因为触发普通Minor GC之后,From区域和to区域交换(新生代Minor GC用的就是复制算法)。本来这50%应该放在to区的。

tenured: 老年代。当伊甸园和From区,to区域实在是放不下了,就算执行了MinorGC,也放不下这块大对象,那么就不会管这个寿命的限制了,大对象直接晋升到老年代。

大对象直接晋升老年代策略

大对象直接晋升看下例:
在这里插入图片描述

如果老年代和新生代都塞不下了,那么就会直接触发OOM!(见下图):

在这里插入图片描述

关于OOM的问题

问题:一个进程有3个线程,如果一个线程抛出oom,其他两个线程还能运行么?

这个题非常容易出错。按照常理,堆空间是线程共享的,一个线程OOM了,其它所有线程应该都抛出OOM才对。但是事实上其它线程会活的好好的。

原因:当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉,从而不会影响其他线程的运行

问题2:主线程抛出异常挂掉了,子线程会活着吗?

答案:仍然会活着!主线程正常执行完毕,或者主线程挂了,只要子线程不是守护线程,都会活的好好的。

3.10 垃圾回收器

概述:三类垃圾回收器:

在这里插入图片描述

补充:串行(Serial) 并行(Parallel) 并发(Concurrency)的概念:

  • 串行(Serial),就是串行化嘛,很好理解。单核情况下大家以葫芦娃救爷爷的方式一个一个上。

  • 并发(Concurrency),是串行化的升级,在单核情况下,有一堆线程可以争抢时间片。虽然同一时刻上仍旧是只有一个线程在执行,但是由于CPU处理速度特别快,我们看起来好像是一个核心同时处理了多个任务似的。 当然你多核的情况下也可以这么做,让每个核都并发。

  • 并行(Parallel),侧重描述的是多核的概念。由于有了多核,线程运行在每个单独的核既可以独占式,也可以抢占式(并发争抢时间片)。不管是独占式还是抢占式,由于是多核的缘故,任务在我们看来是并行执行的。

  • 所以我们说:并发在单核状态下不一定满足并行的概念。在微观上看来,并发仍然是串行的。只不过CPU执行的速度简直太快了,在宏观上看,并发就如同并行一般!

所以我认为:并发和并行更多的是表述侧重点的不同,并发和并行其实都可以用来指代:在同一时刻,可以执行多个任务。但是如果硬要单独把并发和并行拎出来说,就要从CPU核心数来入手了。并行(多核)的概念更大一些。并发(单核)只能让自己在宏观上让自己看起来像并行而已。

a. 【串行】串行垃圾回收器:

根据上面串行的描述,它适用于单核机器(配置较低的机器首选)。

下图虽然有多个核,但是垃圾回收的时候,其他核上面的线程也阻塞了。这本质上就是个串行的,核再多也没用!!

当垃圾回收线程运行时,会触发STW.

在这里插入图片描述

b. 【并行】(ParallelGC)吞吐量优先:1.8下JDK默认方式!

Parallel 并行的;Concurrency 并发的。看名字 UseParallelGC,它是一个并行的垃圾回收器。它的确是并行的。而下面介绍的响应时间优先的垃圾回收器就是并发的。

参数设置:

  • -XX:+UseParallelGC ~ -XX:+UseParallelOldGC

    这两个开关代表使用吞吐量优先的垃圾回收机制。JDK1.8默认打开这两个选项。这两个开关左边是开启新生代右边是开启老年代,开启一个另一个也开启

  • -XX:+UseAdaptiveSizePolicy
    自适应调整新生代区域大小和各部分(伊甸园、晋升阈值等)比例。

  • -XX:GCTimeRatio=ratio
    调整垃圾回收时间和总时间的占比。公式:1/(1+ratio)
    如果你的ratio设为99,那么根据公式可知:程序运行了100min,垃圾回收的时间是1min。ratio默认就是99,程序猿一般可以将其设为19。

  • -XX:MaxGCPauseMillis=ms
    执行垃圾回收线程时,最大暂停毫秒数。默认是200ms

第三行和第四行的指标是矛盾的。一个是比率,一个是写死的,是个对立指标。必须根据实际应用选取之。

  • -XX:ParallelGCThreads=n
    修改垃圾回收并行线程数。垃圾回收器会开启多个垃圾回收线程一拥而上。垃圾回收线程的个数默认情况下与你的CPU核数相关。如果你想控制数目,请修改n。

过程如下图所示:比起前面说的串行垃圾回收器,其最大区别是当线程运行到安全点时,会STW,只不过这个STW会执行一大堆垃圾回收线程。在执行垃圾回收的过程中,CPU会急剧飙升到100% 。这就是它的特点。
在这里插入图片描述

c. 【并发】响应时间优先(CMS)

Parallel 并行的;Concurrency 并发的。区别于上面的吞吐量优先(并行垃圾回收),它是并发的。

参数说明:

  • -XX:+UseConcMarkSweepGC
    Conc是Concurrent缩写,Mark 标记,Sweep清除。看名字,它是一款基于标记清除算法且并发的垃圾回收器,且它工作在老年代。
    由于是标记清除算法,会产生比较多的内存碎片。这样会导致并发失败。这时候就会使ConcMarkSweepGC退化成上文介绍过的串行垃圾回收器(SerialOld),这个SerialOld是基于标记整理算法的。串行化的同时帮你整理一下内存碎片。

    垃圾产生太快,并行标记和复制速度跟不上线程产生垃圾的速度就会退化,并触发Full GC
    关于这一点,下文在FullGC时还会讲到。退化成串行化垃圾回收器实质上就会触发Full GC

  • 与上面的ConcMarkSweepGC配合的一个垃圾回收器是:XX:+UseParNewGC,它工作在新生代,基于复制算法。它们是成对出现的

  • -XX:ParallelGCThreads=n ~ -XX:ConcGCThreads=threads
    并发时的线程数,受到这两个参数的影响。第一个参数-XX:ParallelGCThreads=n和之前介绍过的 吞吐量优先的参数完全一样。去上面重新看一遍定义即可。
    而同一个CPU中并发的GC线程数设置就不一样了,-XX:ConcGCThreads=threads建议设置的线程数是-XX:ParallelGCThreads=n(也就是核数)的四分之一。

    这一对概念比较难懂。实际只需要记住:第一个参数设置时,设置你的核数。第二个参数设置时,设置为第一个参数的四分之一。
    该算法工作流程如下图所示,其比较复杂。
    并发的解释:如下图所示:在某些时机,不用STW。但是有些时机必须STW
    在这里插入图片描述

根据上图补充说明几个参数:

而且它由于清理时是不发生STW的,所以在并发清理阶段,它可能会产生浮动垃圾(在垃圾回收时会产生新的垃圾),浮动垃圾只有在下一次GC时才可以清除。也就是它清不干净。

这样会带来一个问题:它不能像之前的垃圾回收算法等到内存不足了再触发GC,它必须预留一个空间来保留浮动垃圾(浮动垃圾是上次的垃圾,会影响这次的GC标记)。

为了解决这个问题,可以使用下面的参数:

  • -XX:CMSInitiatingOccupancyFraction=percent
    percent的意思是内存占比,假如你的percent设为80,那么你的老年代空间不用等到全部占满了才触发GC,老年代空间占到了80%你就可以触发GC了(因为有一些浮动垃圾,留这20%空间是给浮动垃圾的。)

在早期的JVM,percent设置的值是65%左右。

  • 还有一个参数: -XX:+CMSScavengeBeforeRemark

    它应用的场景是:在上图的重新标记阶段(第三次标记),有可能新生代对象会引用老年代的对象。
    作用是:这个选项如果打开,就会在重新标记之前,对新生代对象进行一次MinorGC,这样就不用重新标记新生代对象了,只用标记老年代对象。减轻了重新标记阶段的压力。

    该参数会在 GC 调优案例中重点说明,这个概念比较重要!! (点击页内跳转到 GC 调优案例)

与吞吐量优先的比较,以及优缺点:

该算法工作时,垃圾回收时CPU占用率不高,但是由于垃圾回收线程和用户线程大多数时间是共存的(是因为不STW)。所以用户线程在大多数情况下是受影响的。相比吞吐量优先的垃圾回收器,它牺牲了程序在绝大多数情况下的吞吐量,但是换取的是响应更加及时。

前面还说过,响应时间优先的垃圾回收器对老年代进行清除时,会由于磁盘碎片的产生退化成串行垃圾回收器。一旦发生了退化,对CPU的消耗是比较大的。这也是ConcMarkSweepGC的一个缺点

3.11 G1(Garbage First)工作流程

概述:

在这里插入图片描述
它借鉴了吞吐量优先,和CMS(响应时间优先)这两种垃圾回收器,同时注重吞吐量和低延迟。

它对超大堆内存的支持比CMS做的要好。

它是并发的垃圾回收器。

工作流程:

在这里插入图片描述
如上图所示三部曲:

  • 第一步(上图中的绿色三角):先进行一次新生代的垃圾回收。
  • 第2步(上图中的蓝色三角):进行一次新生代垃圾回收+并发标记。
  • 第3步(上图的橘色三角):进行一次混合回收阶段

a. 新生代的垃圾回收(会STW)

新生代垃圾回收第一步: 如下图所示,三个E为Eden space(伊甸园区)已满

在这里插入图片描述
新生代垃圾回收第二步: 新生代垃圾回收之后,将幸存对象用复制算法,复制到下图中的幸存区S(Survival)区,如下图所示:
在这里插入图片描述

新生代垃圾回收第三步: 幸存区S过大后,又会触发一次新生代垃圾回收。在这次垃圾回收中,S区够了年龄(寿命)的,就会晋升到老年代O(Old)区,不够年龄的会复制到其它的S区域。
在这里插入图片描述

b. 新生代的垃圾回收和并发标记阶段(Young Collection + ConcurrenMark):

该算法对新生代GC时,会STW,但是CM阶段(ConcurrentMark并发标记)阶段并不会STW。

E:Eddn space;
S:Survival space;
O:Old space

在这里插入图片描述

这里的老年代无用对象只是被并发标记了一次,并没有回收。而新生代会在这个阶段进行一次GC。

c. 混合收集阶段(Mixed Collection),会STW

它会对E、S、O区进行一次全面的垃圾回收:

在这里插入图片描述

混合收集阶段首先会先执行一次新生代的垃圾回收(前面讲过): 将E区回收,幸存对象到S区,如果S区域满了,够寿命的晋升到O区,不够寿命的复制到其它S区域。

然后执行一次Young Collection + ConcurrenMark阶段(就是上文的b)阶段: 在进行新生代垃圾回收的同时,标记老年代无用对象。

最后对被标记的老年代无用对象进行一次最终标记和GC。 但是G1会根据你设置的参数:-XX:MaxGCPauseMillis=ms (设置最大暂停时长)来进行一次有选择的老年代垃圾回收。为什么呢?因为老年代垃圾回收太大,STW可能时间较长,这样就超过了刚刚设置的最大暂停时长了。

为了要达到这个-XX:MaxGCPauseMillis=ms (设置最大暂停时长)的目标,G1会挑出回收价值较高的O区进行有选择的垃圾回收。

进行完GC后,会执行拷贝存活(Evacuation)阶段, 将老年代幸存对象会被复制到新的老年代区域:O --> O。如下图所示:
在这里插入图片描述

总之,最终标记阶段,和拷贝存活阶段,都会STW。

思考:最终标记阶段为什么要STW呢?

G1在这方面的特点和CMS垃圾回收器一样:和前文讲的一样:因为会有并发标记阶段,并发标记不产生STW,用户的线程和标记线程会并发运行,所以会产生浮动垃圾,(在垃圾标记回收时会产生新的垃圾),浮动垃圾只有在下一次GC时才可以清除。

CMS对此的解决方案是设置参数:

在这里插入图片描述

而G1的解决方案,就是在最终标记阶段进行一次STW,标记掉那些浮动垃圾, 然后再进行回收,已确保清除干净

总结:Full GC

前面学了四种垃圾回收算法,它们对老年代内存不足的处理是不一样的,Full GC的触发也是不同的。

在这里插入图片描述
总之,只要是基于并行标记和复制和回收的垃圾回收算法,老年代空间不足,都不会马上触发Full GC,而是只有在垃圾回收太快跟不上并行处理速度时,才会触发Full GC

那如何降低 Full GC 概率呢? 请页内跳转 3.17

  • 补充:关于 Major GC:
    在这里插入图片描述

3.12 G1新生代跨代引用问题(卡表机制)

先复习一下新生代垃圾回收的过程:先找到root对象,然后进行可达性分析,将存活对象复制到幸存区。

有一些根对象是来自老年代的 (老年代引用新生代,跨代引用) 。老年代的对象太多了,所以在查找root对象时,遍历整个老年代对象查找效率非常低。因此,采用了卡表机制。

  • CardTable(卡表)机制

其实就是对老年代分区再次进行细分,分成一个一个的CardTable:

在这里插入图片描述
如果有一个card引用了新对象,就将其标注为脏card。如果下次进行root对象扫描的时候,不用扫描整个老年代分区,只需要扫描脏卡即可。

新生代会由一个 Remembered Set区域,记录外部对我的引用。也就是说记录老年代引用我的区域(脏卡)。将来进行新生代垃圾回收时,先根据Remembered Set找到对应的脏卡,到脏卡中遍历找到 GC Root,再从root进行可达性分析,从而找到新生代的垃圾进行回收。

在这里插入图片描述

3.13 Remark(标记)阶段,写屏障技术

这一章和 3.12的卡表机制联系紧密,视频里讲的不甚完善,可以自行百度。

推荐一篇CSDN文章,讲得很好:

https://blog.csdn.net/Yao_ziwei/article/details/117518283

3.14 G1 字符串去重优化

这里和之前讲的 intern()方法(使用串池实现)不同:

在这里插入图片描述

3.15 G1 类卸载

有些类是由类加载器加载的(常用于框架),所有对象都经过并发标记后,就能知道哪些类不再被使用,当一个类加载器的所有类和它们的实例都不再使用,则卸载它所加载的所有类。

-XX:+ClassUnloadingWithConcurrentMark 默认启用

而jdk自己的类加载器不会被卸载。只有自定义的类卸载器会被卸载。

于 JDK 8u40 版本开始支持。

3.16 JDK 8u60 回收巨型对象

JDK 8u60 版本对回收巨型对象进行了优化。

之前在介绍G1 分区的时候,除了 E、S、O 三个区域,还有巨型对象区域:

在这里插入图片描述
总之,其回收被优先考虑。它是在新生代垃圾回收时被处理掉的。

3.17 JDK 9 并发标记起始时间的调整

先复习一下前面讲的Full GC:

当你垃圾回收速度跟不上你垃圾产生的速度,G1和CMS就会退化为Full GC。

所以可以提前让并发标记、复制和回收这个过程提前。这个过程提前开始了,则会降低Full GC的概率,至少是延缓Full GC的到来。

可以通过调整参数的方式实现:

在这里插入图片描述
JDK9进行了优化,设置初始值之后不用管,可以动态调整。

JDK9对垃圾回收器进行了大规模增强,修复了无数bug。这里不展开讲了。

4、垃圾回收调优

4.1 概述

  • 掌握 GC 相关的 VM 参数,会基本的空间调整
  • 掌握相关工具
  • 明白一点:调优跟应用、环境有关,没有放之四海而皆准的法则

根据你的优化目标(要低延迟还是要高吞吐量)来选择合适的垃圾回收器。

4.2 GC调优的思路(先找代码,后找JVM)

经常Full GC,很有可能是你代码写的有问题,先别甩锅给JVM。先检查一下你的代码,看看以下四个场景,是否命中了?

场景一:从SQL入手
假如你写这么一条:select * from BigTable这条语句。这条语句在MyBatis的结果集映射(ResultSet)中会将所有这个表的数据拿出来放到java内存中,这样内存必爆。

解决方法就是不要 select * 。或者使用limit 关键字,减少查询数据量。

场景二:对象过大
一个实体类里面有很多字段,但是并不是所有字段都是我们需要的,我们可能只需要部分属性。我们可以只查部分属性,或者单独建立一个小的VO类,该VO类只包括部分字段用于展示。

场景三:包装类
能不使用包装类型就不使用,因为最小的包装类型 Integer 也要16个字节,而一个普通的int 只要4字节

场景四:使用软弱引用
比如buffer这种缓冲区,可以用软引用、弱引用等等。或者像这种缓冲区交给中间件来实现,比如Redis等等。

下面几个章节会介绍一下JVM层面的调优:

4.3 GC 调优 新生代

先来看新生代的几个特点:

在这里插入图片描述
由此可知,新生代的GC代价远远低于老年代,我们应该先从新生代入手。

新生代内存设置多大合适?(下面翻译自官网)

-Xmn

Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery). GC is
performed in this region more often than in other regions. If the size for the young generation is
too small, then a lot of minor garbage collections are performed. If the size is too large, then only
full garbage collections are performed, which can take a long time to complete. Oracle
recommends that you keep the size for the young generation greater than 25% and less than
50% of the overall heap size.

-Xmn这个参数的意思是:设置新生代初始和最大大小。GC在这一个区域发生的更加频繁(相对于老年代)。

如果新生代区域过小,则会执行很多次Minor GC,如果新生代内存过大,则只有Full GC才会发挥作用。因此Oracle建议您将新生代大小保持在总堆大小的25%以上,50%以下。

因此合理的新生代配置如下图(幸存区也属于新生代):
在这里插入图片描述

4.4 GC 调优 老年代

先检查自己的代码问题,再尝试调优新生代,实在不行了再考虑调优老年代。理由在上文讲过。

老年代调优方法在3.17讲过(如下图所示):

在这里插入图片描述

4.5 GC 调优 案例

  • 案例1 Full GC 和 Minor GC频繁

    原因:新生代过小导致MinorGC频繁,新生代内存不足会使得一些对象未经晋升机制就直接跑到老年代(原因见前文:大对象直接晋升老年代策略),导致老年代内存不足引发Full GC

    解决方案:增大新生代内存。降低MinorGC概率,并且增大幸存区晋升阈值。避免过早晋升到老年代

  • 案例2 请求高峰期发生 Full GC,单次暂停时间特别长 (CMS)

    原因分析:先根据GC日志定位到底是CMS哪个暂停时间特别长。如果定位到第三次暂停时间(重新标记时间) 特别长,那么解决方案其实之前讲CMS时就讲过了,增加参数即可
    在这里插入图片描述

  • 案例3 老年代充裕情况下,发生 Full GC (CMS jdk1.7)

    1.7的方法区叫永久代(占用堆内存,受垃圾回收管理),1.8以后的方法区叫元空间(占用操作系统内存空间,不受垃圾回收机制管理)

    虽然老年代充裕,但是也有可能是永久代内存不足导致的,解决方案就是增大永久代。

5、类加载与字节码技术

5.1 javap 工具反汇编,查看java汇编指令

这里就不跟着黑马看了,直接去Oracle官方文档看:

https://docs.oracle.com/javase/8/docs/technotes/tools/windows/javap.html

下面给翻译一下:

The javap command disassembles one or more class files. The output depends on the options used. When no options are used, then the javap command prints the package, protected and public fields, and methods of the classes passed to it. The javap command prints its output to stdout.

javap命令可以反汇编一个或多个class文件,输出取决于参数的选择。如果你不选择任何参数(比如javap HelloWorld.class),就会在控制台打印出包,protected方法和public方法,普通方法。

使用:
javap [options] classfile…

它作用在一个.class 文件上。如果你用的是idea,你得去out文件夹下找到.class文件,并在这个目录下使用 javap命令

一般使用 javap -v HelloWorld.class这个命令,-v显示更多信息

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JVM黑马版:笔记、应用、速查 的相关文章

  • SwiftUI - 使用“ObservableObject”和@EnvironmentObject 有条件地显示视图

    我想在我的应用程序中有条件地显示不同的视图 如果某个布尔值为 true 则将显示一个视图 如果为 false 将显示不同的视图 该布尔值位于 ObservableObject 类中 并根据将要显示的视图之一进行更改 PracticeStat
  • PHP 类扩展字符串变量

    是否可以声明一个类并让它扩展一个变量 class Child extends parentClass 是的 它是通过 eval 实现的 但不建议这样做
  • 比较一个类的两个实例

    我有一堂这样的课 public class TestData public string Name get set public string type get set public List
  • 在Java中从控制台打开包中的类

    因此 当我尝试从命令提示符打开一个不在包中的 java 类时 一切正常 但是当我尝试打开一个包中的类时 它会给我 NoClassDefFoundError 错误 当我尝试打开类 java somepackage someclass 时列出包
  • 如何在没有 Node.JS 的情况下运行 UglifyJS2

    无论如何都要跑UglifyJS2 https github com mishoo UglifyJS2没有node js 假设我想使用 JavaScript 脚本引擎在 JVM 进程中运行它 怎么做 我看到米秀回答你了https github
  • 具有成员 std::mutex (或其他不可复制对象)的类的复制或移动构造函数?

    class A private class B private std mutex mu A parent NULL public B A const parent ptr parent parent ptr B const A B b c
  • 将 Kotlin .kt 类打包到 JAR 中

    我如何构建HelloWorld kt as a JAR以便它运行 thufir dur kotlin thufir dur kotlin kotlinc HelloWorld kt include runtime d HelloWorld
  • CSS class 和 id 同名

    css class 和 id 同名有什么问题吗 就像文章 帖子页脚的 footer 和页面页脚的 footer 一样 不 完全可以接受 一个类是使用 a 来定义的 并且 ID 是使用定义的 因此 就浏览器而言 它们是两个完全独立的项目 唯一
  • Java:为什么.class文件中的方法类型包含返回类型,而不仅仅是签名?

    class 文件的常量池中有一个 NameAndType 结构 它用于动态绑定 该类可以 导出 的所有方法都被描述为 签名 返回类型 喜欢 getVector Ljava util Vector 当某些 jar 中方法的返回类型发生更改时
  • 向Java类库添加函数

    我使用的 Java 类库在很多方面都不完整 有很多类我认为应该内置其他成员函数 但是 我不确定添加这些成员函数的最佳实践 让我们调用不足的基类A class A public A long arbitrary arguments publi
  • Python函数重载

    我知道 Python 不支持方法重载 但我遇到了一个问题 我似乎无法以一种很好的 Pythonic 方式解决它 我正在制作一个游戏 其中一个角色需要射击各种子弹 但是如何编写不同的函数来创建这些子弹 例如 假设我有一个函数 可以创建一颗以给
  • 当放入对象方法内时,引用拉入封装方法的方法会移出范围

    当我将引用拉入包的方法放入另一个方法中时 它会离开范围并失败 执行此操作的正确方法是什么 我尝试玩弄 自我 但我是新人 但没有成功 所需的解决方案 不起作用 返回错误 nil NilClass 的未定义方法 accounts NoMetho
  • java中如何找到class文件的包

    我正在编写一个使用 class 文件的 java 程序 我希望能够读取文件系统上的 class 文件 使用 InputStream 并确定它所在的包 该 class 文件可能不在一个好的包目录结构中 它可能位于某个随机位置 我怎样才能做到这
  • Java 加载类时如何管理内存?

    想象一下 我有一个包含 10 个方法的类 我需要从该类中实例化 10 个对象 问题是 JVM 会在对象创建时为 10 个实例分配 10 个不同的内存空间 我的意思是在我调用构造函数时 即 new MyClass 吗 或者它会在内存中加载一次
  • Python:在方法内部时,类属性是否等同于局部变量?

    在Python中 我知道查找本地范围的变量比查找全局范围的变量要快得多 所以 a 4 def function for x in range 10000
  • 什么时候需要使用 new 来初始化 F# 类型?

    给定一个类 例如 type MyClass member this Greet x printfn Hello s x 使用初始化实例是否合适 let x new MyClass 或没有new 另外 什么时候使用new构造函数比 a 更有用
  • 类变量在其定义范围内?

    这可能是一个愚蠢的问题 我正在尝试制作文本泥 我需要每个 Room 类包含其他 Room 类 以便在尝试移动到它们或从它们获取信息时可以引用 但是 我不能这样做 因为我显然无法在其定义中声明一个类 那么 我该怎么做呢 当我说我做不到时 我的
  • 最终类中的静态函数是否隐式最终?

    我的问题基本上与this https stackoverflow com q 8766476 3882565一 但这是否也适用于static功能 我想了解 编译器是否处理所有static函数在一个final类为final 是否添加final
  • 使用 Eclipse 创建新的 android 项目后,Eclipse 中的 SRC 文件夹为空(未创建 MainActivity 类)

    使用 Eclipse 创建新的 android 项目后 Eclipse 中的 SRC 文件夹为空 未创建 MainActivity 类 我使用 Eclipse 创建了一个新的 android 项目 但我在项目资源管理器中看到 SRC 文件夹
  • Java 比 Xmx 参数消耗更多内存

    我有一个非常简单的 Web 服务器类 基于 Java SEHttpServer class 当我使用此命令启动编译的类来限制内存使用时 java Xmx5m Xss5m Xrs Xint Xbatch Test 现在如果我使用检查内存top

随机推荐

  • 165. 比较版本号

    165 比较版本号 题目描述 给你两个版本号 version1 和 version2 请你比较它们 版本号由一个或多个修订号组成 各修订号由一个 连接 每个修订号由 多位数字 组成 可能包含 前导零 每个版本号至少包含一个字符 修订号从左到
  • 最挑战程序员的9大任务,你都干过吗?

    那些非程序员认为软件开发是非常困难的 确实如此 但这种困难不像那些外行人理解的那样 最近在 Quora 上的一次讨论 程序员分享了他们认为工作中的最大困难 在这里为大家精选出其中的 9 个 1 怎样才是最佳解决方案 任务描述 给你一系列的需
  • 容器化部署的微服务 远程调试(debug)

    一 项目微服务的部署采用如下方式 1 微服务的jar包 使用dockeFile文件 创建为镜像image 2 利用该镜像创建一个容器 3 启动容器 微服务即启动 二 远程调试 debug 设置 1 idea 本地设置 2 编辑 创建镜像的d
  • 2018-互联网优质资源汇总

    Jamin s Blog 个人网站 虽然时间比较早 但对很对ios的主题进行深入的讲解 HTTPS HTTPS进阶 APP启动 并发编程RunLoop 等 Spring Boot 汇总 微笑很纯洁 CSDN博主 Spring Boot资源的
  • 【C++】面向对象之多态

    文章内的所有调试都是在vs2022下进行的 部分小细节可能因编译器不同存在差异 文章目录 多态的定义和实现 概念引入 多态的构成条件 虚函数重写 通过基类的指针或者引用调用虚函数 override和final 抽象类 概念 实现继承和接口继
  • 部分优秀博客主链接汇总(linux c/c++ java go php android ios 前端 j2ee windows linux 算法 ACM AI 深度/机器学习 opencv nlp)

    给大家推荐一个学习人工智能的网站 人工智能社区 https chenyu blog csdn net article details 79449026 a href http www pudn com pudn a a href https
  • 【狂神说】Mybatis学习笔记(全)

    狂神说 Mybatis最新完整教程IDEA版参考链接 https www bilibili com video BV1NE411Q7Nx 狂神说 Java学习完整路线https www bilibili com read cv5702420
  • 用户输入一行明文(字符串),针对字母进行加密(偏移量设置为 3),非字母部分保留原型。

    要求 用户输入一行明文 字符串 针对字母进行加密 偏移量设置为 3 非字母部分保留原型 include
  • uniapp获取用户数据昵称为“微信用户”(小程序)@杨章隐

    原文 微信小程序获取用户信息nickname为 微信用户 Web Try harder的博客 CSDN博客 小程序获取昵称是微信用户 原因 微信 公众平台调整了相关策略 开发者调用type getuserinfo 和直接调用wx getus
  • .Error (10200): Verilog HDL Conditional Statement error at : cannot match operand(s) in the conditio...

    always 记得初始化使用数据 加上 if rst n 不要直接接if dsp xint1 转载于 https www cnblogs com navieli archive 2013 03 11 2954183 html
  • 【Mo 人工智能技术博客】使用 Seq2Seq 实现中英文翻译

    1 介绍 1 1 Deep NLP 自然语言处理 Natural Language Processing NLP 是计算机科学 人工智能和语言学领域交叉的分支学科 主要让计算机处理或理解自然语言 如机器翻译 问答系统等 但是因其在表示 学习
  • python计算高德地图距离和面积

    python计算高德地图距离和面积 因为项目中经常使用高德的距离和面积计算组件 但是高德并未公布计算逻辑 这就导致项目中数据出问题时不知道该如何去定位 因此花费了1天时间把距离计算和面积计算用python语言整理了出来 距离计算公式 fro
  • VUE首屏加载loading效果

    在使用乾坤微前端框架中 遇见图片资源路径存在访问问题 在不搭CDN的情况下 使用base64方式将图片资源打入代码 这样会导致包体积过大 加载时间长 所以加了一个loading效果 1 在index html入口增加 loading li
  • @Profile使用及SpringBoot获取profile值

    之前开发用过 maven 的环境隔离 现在使用springboot的 Profile功能 发现spring体系真的大到我只是学习了皮毛 相比面试问的 IOC bean的作用域等 突然觉得很可笑 官方文档关于 Profile 的使用 http
  • 删除两个字典中非公共的键和值

    删除两个字典中非公共的键和值 需求 对比两个字典 找出公共元素 将非公共元素删除 dict1 dict2 res for i in dict1 if i not in dict2 print i del dict1 i res append
  • Vue3项目中使用TypeScript

    单文件用法 在单文件组件中使用 TypeScript 需要在 小结 注意 当 script 中使用了 ts 模板 template 在绑定表达式时也支持ts 如果在表达式中不指名类型时 编译器会报警告提示 正确写法 表达式指定类型 组合式A
  • 一文说清楚c++模板Template的用法

    一 引言 模板 Template 指c 程序设计语言中采用类型作为参数的程序设计 二 函数模板 1 通用格式 函数模板定义格式 template
  • Git:同步他人的远程仓库至自己的Git服务器并自动定时更新

    昨天晚上将2015年一篇不负责的Git服务器搭建的教程更新了一下 详见 在CentOS下搭建自己的Git服务器及使用nginx配置gitweb面板 今天折腾了一些本地的项目 倒是可以了 现在的需要是 将远程的仓库 同步一份到自己的git服务
  • C#复习题1(含答案及解析)

    1 单选题 下面有关for循环的正确描述是 A for循环只能用于循环次数已经确定的情况 B for循环的执行流程是先执行循环体语句 后判断表达式 C 在for循环中 表达式1和3可以省略 但表达式2是不能省略 D for循环的循环体中 可
  • JVM黑马版:笔记、应用、速查

    前言 由于工作中时常和JVM打交道 但是对JVM的体系缺乏系统深入了解 日前跟随b站上黑马程序猿的课程成体系地学习了JVM 结合工作中的实践写就了此笔记 黑马原视频地址 https www bilibili com video BV1yE4