《深入理解Java虚拟机 1,docker教程学习

package com.paddx.test.memory;

import java.io.File;

import java.net.URL;

import java.net.URLClassLoader;

import java.util.ArrayList;

import java.util.List;

public class PermGenOomMock{

public static void main(String[] args) {

    URL url = null;

    List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();

    try {

        url = new File("/tmp").toURI().toURL();

        URL[] urls = {url};

        while (true){

            ClassLoader loader = new URLClassLoader(urls);

            classLoaderList.add(loader);

            loader.loadClass("com.paddx.test.memory.Test");

        }

    } catch (Exception e) {

        e.printStackTrace();

    }

}

}




运行结果如下:



![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWFnZXMyMDE1LmNuYmxvZ3MuY29tL2Jsb2cvODIwNDA2LzIwMTYwMy84MjA0MDYtMjAxNjAzMjcwMDU4NDY5NzktMTEyNDYyNzE3NC5wbmc?x-oss-process=image/format,png)



本例中使用的 JDK 版本是 1.7,指定的 PermGen 区的大小为 8M。通过每次生成不同URLClassLoader对象来加载Test类,从而生成不同的类对象,这样就能看到我们熟悉的 "java.lang.OutOfMemoryError: PermGen space " 异常了。这里之所以采用 JDK 1.7,是因为在 JDK 1.8 中, HotSpot 已经没有 “PermGen space”这个区间了,取而代之是一个叫做 Metaspace(元空间) 的东西。下面我们就来看看 Metaspace 与 PermGen space 的区别。



### 2、Metaspace(元空间)



其实,移除永久代的工作从JDK1.7就开始了。JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap。但永久代仍存在于JDK1.7中,并没完全移除,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。我们可以通过一段程序来比较 JDK 1.6 与 JDK 1.7及 JDK 1.8 的区别,以字符串常量为例:



package com.paddx.test.memory;

import java.util.ArrayList;

import java.util.List;

public class StringOomMock {

static String  base = "string";

public static void main(String[] args) {

    List<String> list = new ArrayList<String>();

    for (int i=0;i< Integer.MAX_VALUE;i++){

        String str = base + base;

        base = str;

        list.add(str.intern());

    }

}

}




这段程序以2的指数级不断的生成新的字符串,这样可以比较快速的消耗内存。我们通过 JDK 1.6、JDK 1.7 和 JDK 1.8 分别运行:



JDK 1.6 的运行结果:



![è¿éåå¾çæè¿°](https://imgconvert.csdnimg.cn/aHR0cDovL2ltYWdlczIwMTUuY25ibG9ncy5jb20vYmxvZy84MjA0MDYvMjAxNjAzLzgyMDQwNi0yMDE2MDMyNzAwNTkyOTM4Ni00MDkyODM0NjIucG5n?x-oss-process=image/format,png)



JDK 1.7的运行结果:



![è¿éåå¾çæè¿°](https://imgconvert.csdnimg.cn/aHR0cDovL2ltYWdlczIwMTUuY25ibG9ncy5jb20vYmxvZy84MjA0MDYvMjAxNjAzLzgyMDQwNi0yMDE2MDMyNzAxMDAzMzgyMy0xMzQxMjI4MjgwLnBuZw?x-oss-process=image/format,png)



JDK 1.8的运行结果:



![è¿éåå¾çæè¿°](https://imgconvert.csdnimg.cn/aHR0cDovL2ltYWdlczIwMTUuY25ibG9ncy5jb20vYmxvZy84MjA0MDYvMjAxNjAzLzgyMDQwNi0yMDE2MDMyNzAxMDE0Mzc3Ni0xNjEyOTc3NTY2LnBuZw?x-oss-process=image/format,png)



从上述结果可以看出,JDK 1.6下,会出现“PermGen Space”的内存溢出,而在 JDK 1.7和 JDK 1.8 中,会出现堆内存溢出,并且 JDK 1.8中 PermSize 和 MaxPermGen 已经无效。因此,可以大致验证 JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代的结论。现在我们看看元空间到底是一个什么东西?



元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:



\-XX:MetaspaceSize,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。  

  -XX:MaxMetaspaceSize,最大空间,默认是没有限制的。



  除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:  

  -XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空间容量的百分比,减少为分配空间所导致的垃圾收集  

  -XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空间容量的百分比,减少为释放空间所导致的垃圾收集



现在我们在 JDK 8下重新运行一下代码段 4,不过这次不再指定 PermSize 和 MaxPermSize。而是指定 MetaSpaceSize 和 MaxMetaSpaceSize的大小。输出结果如下:



![è¿éåå¾çæè¿°](https://imgconvert.csdnimg.cn/aHR0cDovL2ltYWdlczIwMTUuY25ibG9ncy5jb20vYmxvZy84MjA0MDYvMjAxNjAzLzgyMDQwNi0yMDE2MDMyNzAxMDIzMzkzMy02OTkxMDYxMjMucG5n?x-oss-process=image/format,png)



从输出结果,我们可以看出,这次不再出现永久代溢出,而是出现了元空间的溢出。



**第三章 垃圾收集器与内存分配策略**

====================



**一、内存分配**

----------



这部分我们说一下对象在java堆中是如何分配、布局、访问以及内存分配的原则。



### 1、对象的创建



我们用new来创建对象,来看看系统运行到new时,虚拟机在干什么。此时的类就像一块肉,他要经过层层安检,才能到达人类的饭桌。



![](https://img-blog.csdnimg.cn/201906181953527.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FzYmJs,size_16,color_FFFFFF,t_70)



(1)查看在常量池中是否有对应的符号引用。【在方法区中进行】



(2)查看此类是否被加载、解析和初始化过。【在方法区中进行】



(3)领取新生对象的内存。有两种方式:指针碰撞和空闲列表。【在堆中进行】



(4)将分配到的内存空间初始化为零。



(5)对对象进行必要的设置,比如其实哪个类的实例,对象的哈希码之类的。这些信息存放在对象的对象头中。



(6)如果java代码对对象进行了赋值,则会走到第六步,执行<init>方法。此方法的作用就是对对象进行初始化。



### 2、对象的内存布局



对象在内存中的存储布局分为三个部分:对象头+实例数据+对其补充



*   对象头



对象头里面有两部分信息:



(1)运行时数据,包括哈希码、GC分代年龄、锁状态标志灯。



(2)类型指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。



*   实例数据



实例数据中存放的是代码中定义的各种类型的字段内容。



*   对其填充



对齐填充起的是占位符的作用,不是必然存在的,其只要保证对象的大小是8字节的整数倍即可。



### 3、对象的访问定位



建立完对象后,我们就可以使用对象了。通过句柄和直接指针两种方式。



*   句柄



句柄访问就是在java堆中划分出一块内存区域作为句柄池,句柄中包含了实例数据和类型数据各自具体的地址信息。



![](https://img-blog.csdnimg.cn/20190618201352170.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FzYmJs,size_16,color_FFFFFF,t_70)



*   直接指针



直接指针之所以“直接”,是因为它去除了句柄这个中介。所以在速度上比句柄快。  

在HotSpot虚拟机中,使用的是这种方式。



![](https://img-blog.csdnimg.cn/20190618201408896.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FzYmJs,size_16,color_FFFFFF,t_70)



说完了对象在java堆中是如何分配,布局和访问的,接下来我们说说内存分配的原则。



### 4、内存分配的原则



![å¨è¿éæå¥å¾çæè¿°](https://img-blog.csdnimg.cn/20190618202533861.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FzYmJs,size_16,color_FFFFFF,t_70)



堆大致分为新生代,老年代,永久代。对象的内存分配主要分配在新生代的Eden区,少数情况下会直接分配到老年代中。分配的规则不是100%固定的,取决于垃圾收集器组合和参数设置等。下面有几条分配原则可供参考。



> 1.  对象优先在Eden分配

> 2.  大对象直接进入老年代

> 3.  长期存活的对象将进入老年代

> 4.  动态对象年龄判定

> 5.  空间分配担保



**二、垃圾回收机制**

------------



英文名儿是GC(Garbage Collection)。



![å¨è¿éæå¥å¾çæè¿°](https://img-blog.csdnimg.cn/2019061910455734.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FzYmJs,size_16,color_FFFFFF,t_70)



### 1、哪些内存需要回收?



堆和方法区中的内存需要回收,其它的不用回收。



因为只有堆和方法区是线程共享的,其余的是与线程“同生共死”的,线程结束,内存自然就跟着回收了,所以不用管它们。



### 2、什么时候回收?



(1)在堆里面:



当对象“死了”的时候就要对其进行内存回收了。啥叫对象死了?就是没有地方引用它了,它无用了。那怎么判断它是否死了呢?有两种方法。



*   引用计数算法



给对象添加一个引用计数器,每当有一个地方引用它时,计数器的值就+1,当引用失效时,计数器的值就-1,当计数器的值为0时,代表此对象已不被引用,也就是“可以死了”。



但这有一个弊端,就是循环引用的问题。就像下图,堆里的两个对象即使无用了也没办法对其进行回收,因为它们互相引用着,计数器的值至少为1。



![å¨è¿éæå¥å¾çæè¿°](https://img-blog.csdnimg.cn/201906191053460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FzYmJs,size_16,color_FFFFFF,t_70)



*   可达性分析



所有生成的对象都是一个成为“GC Roots”的根的子树。从GC Roots开始向下搜索,搜索所经过的路径成为引用链。当一个对象到GC Roots没有任何引用链可以到达时,就称这个对象是不可达的,也就是可以被GC回收了。这个是java中采用较多的方式。



就像下图中的堆中未被引用的对象,就可以对其进行回收了。



![å¨è¿éæå¥å¾çæè¿°](https://img-blog.csdnimg.cn/20190619110132365.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FzYmJs,size_16,color_FFFFFF,t_70)



怎么判断一个对象是否还存在着引用?java中的引用分为4种:



*   强引用:Object object = new Object(),只要强引用存在,GC永远不会回收掉被引用的对象。

*   软引用:描述一些还有用但非必需的对象,当系统即将发生内存溢出时,就会将其进行回收。

*   弱引用:只要进行GC,就会对其进行回收。

*   虚引用:这是最弱的一种引用关系,无法通过虚引用来取得一个对象实例。它的作用是:能在这个对象被收集器回收时收到一个系统通知。



(2)在方法区里面:



我们知道,方法区里存储的是已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。所以我们在方法区里面进行垃圾回收,回收的是一些废弃的常量和无用的类。



*   怎么判断一个常量是否被废弃了?



看引用计数就可以,如果没有对象引用该常量,则说明常量被废弃了,就可以回收了。



*   怎么判断一个类时无用的类?


# 总结

三个工作日收到了offer,头条面试体验还是很棒的,这次的头条面试好像每面技术都问了我算法,然后就是中间件、MySQL、Redis、Kafka、网络等等。

**[CodeChina开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频】](https://codechina.csdn.net/m0_60958482/java-p7)**

**如果你对下面我说的这些笔记感兴趣,可以点赞+关注**

*   **第一个是算法**

关于算法,我觉得最好的是刷题,作死的刷的,多做多练习,加上自己的理解,还是比较容易拿下的。

而且,**我貌似是将《算法刷题LeetCode中文版》、《算法的乐趣》大概都过了一遍,尤其是这本**

**《算法刷题LeetCode中文版》总共有15个章节:编程技巧、线性表、字符串、栈和队列、树、排序、查找、暴力枚举法、广度优先搜索、深度优先搜索、分治法、贪心法、动态规划、图、细节实现题**

![最新出炉,头条三面技术四面HR,看我如何一步一步攻克面试官?](https://img-blog.csdnimg.cn/img_convert/52f53e987ef04bc0c37e5de8f7434614.png)

**《算法的乐趣》共有23个章节:**

![最新出炉,头条三面技术四面HR,看我如何一步一步攻克面试官?](https://img-blog.csdnimg.cn/img_convert/01de851e568d9980c57d14245ce3a011.png)

![最新出炉,头条三面技术四面HR,看我如何一步一步攻克面试官?](https://img-blog.csdnimg.cn/img_convert/e2386caaeb41555a88a36b034da627c3.png)

*   **第二个是Redis、MySQL、kafka(给大家看下我都有哪些复习笔记)**

基本上都是面试真题解析、笔记和学习大纲图,感觉复习也就需要这些吧(个人意见)

![最新出炉,头条三面技术四面HR,看我如何一步一步攻克面试官?](https://img-blog.csdnimg.cn/img_convert/2dab9d98e6fe75e3f7e2824a91b46672.png)

*   **第三个是网络(给大家看一本我之前得到的《JAVA核心知识整理》包括30个章节分类,这本283页的JAVA核心知识整理还是很不错的,一次性总结了30个分享的大知识点)**

》总共有15个章节:编程技巧、线性表、字符串、栈和队列、树、排序、查找、暴力枚举法、广度优先搜索、深度优先搜索、分治法、贪心法、动态规划、图、细节实现题**

[外链图片转存中...(img-Pz6kldmC-1630847849590)]

**《算法的乐趣》共有23个章节:**

[外链图片转存中...(img-MUKVQgEH-1630847849592)]

[外链图片转存中...(img-wSw14Z1V-1630847849593)]

*   **第二个是Redis、MySQL、kafka(给大家看下我都有哪些复习笔记)**

基本上都是面试真题解析、笔记和学习大纲图,感觉复习也就需要这些吧(个人意见)

[外链图片转存中...(img-MK7UhnLf-1630847849595)]

*   **第三个是网络(给大家看一本我之前得到的《JAVA核心知识整理》包括30个章节分类,这本283页的JAVA核心知识整理还是很不错的,一次性总结了30个分享的大知识点)**

![最新出炉,头条三面技术四面HR,看我如何一步一步攻克面试官?](https://img-blog.csdnimg.cn/img_convert/8533791251d1066fedf8b611a39e7be3.png)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值