JVM

JVM               

  

1.Java虚拟机  :翻译官的角色,编写出的Java程序,是不能够被操作系统所直接识别的,这时候JVM的作用就体现出来了,它负责把程序翻译给系统“听”,告诉它我们的程序需要做什么操作。

2.JVM的生命周期:JVM在Java程序开始执行的时候,它才运行,程序结束的时它就停止。

3.JVM中的线程分为两种:守护线程和普通线程。 守护线程是JVM自己使用的线程,比如垃圾回收(GC)就是一个守护线程

4.编译生成的    .class文件通过Java默认有三种加载器        交由JVM来解析运行

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。

 

JVM组成

1.结构体系

 

 

  • 堆:存放对象实例,几乎所有的对象实例都在这里分配内存,是垃圾收集器管理的主要区域,因此也被称作GC堆。(堆内存分为新生代、老年代和永久代)
  • 虚拟机栈:虚拟机栈描述的是Java方法执行的内存结构:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息,只保存基础数据类型和自定义对象的引用。
  • 本地方法栈:本地方法栈则是为虚拟机使用到的Native方法服务
  • 方法区:存储已被虚拟机加载的类元数据信息(元空间)
  • 程序计数器:当前线程所执行的字节码的行号指示器,每条线程都会有一个独立的程序计数器。

 

对象的访问定位:Java程序通过栈上的reference数据来操作堆上的具体对象。

                           访问方式有       ①使用句柄   ②直接指针

 

 

 

JVM垃圾回收:

stop-the-world 。它会在任何一种GC算法中发生

stop-the-world 意味着JVM因为需要执行GC而停止了应用程序的执行。当stop-the-world 发生时,除GC所需的线程外,所有的线程都进入等待状态,直到GC任务完成。GC优化很多时候就是减少stop-the-world 的发生。

 

什么时候执行GC回收:

eden区空间不够存放新对象的时候,执行Minro GC。升到老年代的对象大于老年代剩余空间的时候执行Full GC

 

JVM GC只回收堆区和方法区内的对象。而栈区的数据,在超出作用域后会被JVM自动释放掉,所以其不在JVM GC的管理范围内。

 

JVM GC怎么判断对象可以被回收了?

· 对象没有引用

· 作用域发生未捕获异常

· 程序在作用域正常执行完毕

· 程序执行了System.exit()

· 程序发生意外终止(被杀线程等)

 

 

 

 主流的垃圾收集器都会采用分代回收算法,因此需要将堆内存分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。

  • 新生代GC(Minor GC):指发生新生代的的垃圾收集动作,对象从这个区域“消失”的过程我们称之为:Minor GC 。Minor GC非常频繁,回收速度一般也比较快。
    • (新生区分为:一个伊甸园空间(Eden)· 两个幸存者空间(Fron Survivor、To Survivor)。默认新生代空间的分配:Eden : Fron : To = 8 : 1 : 1)
  • 老年代GC(Major GC/Full GC:指发生在老年代的GC,发生在老年代的GC次数要比新生代少得多。对象从老年代中消失的过程,称之为:Major GC 或者 Full GC。出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上

默认的新生代(Young generation)、老年代(Old generation)所占空间比例为 1 : 2 。

 

新生代中每个空间的执行顺序如下:

1、绝大多数刚刚被创建的对象会存放在伊甸园空间(Eden)

2、在伊甸园空间执行第一次GC(Minor GC)之后,存活的对象被移动到其中一个幸存者空间(Survivor)。

3、此后,每次伊甸园空间执行GC后,存活的对象会被堆积在同一个幸存者空间。

4、当一个幸存者空间饱和,还在存活的对象会被移动到另一个幸存者空间。然后会清空已经饱和的哪个幸存者空间。

5、在以上步骤中重复N次(N = MaxTenuringThreshold(年龄阀值设定,默认15))依然存活的对象,就会被移动到老年代。

从上面的步骤可以发现,两个幸存者空间,必须有一个是保持空的。如果两个两个幸存者空间都有数据,或两个空间都是空的,那一定是你的系统出现了某种错误。

对象在刚刚被创建之后,是保存在伊甸园空间的(Eden)。那些长期存活的对象会经由幸存者空间(Survivor)转存到老年代空间(Old generation)

 

老年代:

这里的对象几乎都是从Survivor 空间中熬过来的,它们绝不会轻易的狗带。因此,Full GC(Major GC)发生的次数不会有Minor GC 那么频繁

 

垃圾收集算法

有四种:标记-清除算法、复制算法、标记-整理算法、分代收集算法

 

垃圾收集器

如果说收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现。

有:Serial收集器(由复制算法实现)、SerialOld收集器(标记 - 整理算法实现)、ParNew收集器(复制算法)、Parallel Scavenge收集器(复制算法)、ParallelOld(标记 - 整理算法)、CMS收集器(标记 - 清理算法)、G1收集器

 

收集器分类:

新生代收集器:

Serial (-XX:+UseSerialGC)

ParNew(-XX:+UseParNewGC)

ParallelScavenge(-XX:+UseParallelGC)

G1 收集器

 

老年代收集器:

SerialOld(-XX:+UseSerialOldGC)

ParallelOld(-XX:+UseParallelOldGC)

CMS(-XX:+UseConcMarkSweepGC)

G1 收集器

 

 

 

对象已经死亡?

 

1.引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。(是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题)

2.可达性分析算法

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索,节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不可用的

3.如何判断一个常量是废弃常量

运行时常量池主要回收的是废弃的常量。那么,我们如何判断一个常量是废弃常量呢?

假如在常量池中存在字符串 "abc",如果当前没有任何String对象引用该字符串常量的话,就说明常量 "abc" 就是废弃常量,如果这时发生内存回收的话而且有必要的话,"abc" 就会被系统清理出常量池。

4.如何判断一个类是无用的类

同时满足下面3个条件才能算是 “无用的类” :

  • 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  • 加载该类的 ClassLoader 已经被回收。
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

 

 

 

 

 

 

String 类和常量池

 1.String 类

     String str1 = "abcd";
     String str2 = new String("abcd");
     System.out.println(str1==str2);//false

这两种不同的创建方法是有差别的,第一种方式是在常量池中拿对象,第二种方式是直接在堆内存空间创建一个新的对象。

案例:

              String s1 = new String("计算机");
	      String s2 = s1.intern();
	      String s3 = "计算机";
	      System.out.println(s2);//计算机
	      System.out.println(s1 == s2);//false,因为一个是堆内存中的String对象一个是常量池中的String对象,
	      System.out.println(s3 == s2);//true,因为两个都是常量池中的String对象

String.intern() :如果运行时常量池中已经包含一个等于此 String 对象内容的字符串,则返回常量池中该字符串的引用

 

 

2.常量池:

方法区的一部分。用于存放编译期间生成的各种字面量和符号引用

常量池,实际上分为两种形态:静态常量池运行时常量池。常说的常量池,就是指方法区中的运行时常量池

用处:为了避免频繁的创建和销毁对象而影响系统性能,实现了对象的共享。

 好处:节省内存空间、节省运行时间

例如字符串常量池:在编译阶段就把所有字符串文字放到一个常量池中。节省内存空间,节省运行时间。

 

 8种基本类型的包装类和常量池:

  • Java 基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean;这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。
  • 两种浮点数类型的包装类 Float,Double 并没有实现常量池技术

案例:

                Integer i1 = 33;
		Integer i2 = 33;
		System.out.println(i1 == i2);// 输出true
		Integer i11 = 333;
		Integer i22 = 333;
		System.out.println(i11 == i22);// 输出false
		Double i3 = 1.2;
		Double i4 = 1.2;
		System.out.println(i3 == i4);// 输出false

 

 

 3.JVM内存优化:

 

 

Java 对象的创建过程

①类加载检查: 虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

②分配内存: 在类加载检查通过后,接下来虚拟机将为新生对象分配内存  (分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定)

        (补充:内存分配并发问题

在创建对象的时候有一个很重要的问题,就是线程安全,因为在实际开发过程中,创建对象是很频繁的事情,作为虚拟机来说,必须要保证线程是安全的,通常来讲,虚拟机采用两种方式来保证线程安全:

  • CAS+失败重试: CAS 是乐观锁的一种实现方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。虚拟机采用 CAS 配上失败重试的方式保证更新操作的原子性。
  • TLAB: 为每一个线程预先在Eden区分配一块儿内存,JVM在给线程中的对象分配内存时,首先在TLAB分配,当对象大于TLAB中的剩余内存或TLAB的内存已用尽时,再采用上述的CAS进行内存分配

)

③初始化零值

④设置对象头: 初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。

 ⑤执行 init 方法: 执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化

 

 

 

 

 

1.详细jvm内存结构

答:虚拟机栈、堆、方法区、程序计数器、本地方法栈

 

2.什么情况下回出现内存溢出,内存泄漏?

内存泄漏的原因:对象一直被引用、对象不会被使用

       内存溢出的原因:内存泄露、大量的jar,class文件加载、操作大量的对象、nio直接操作内存,

       解决溢出设置参数加大空间,检查代码循环

 

 

 

 

posted @ 2018-10-24 22:55  StingLon  阅读(146)  评论(0)    收藏  举报