从 GC 算法的角度,G1 选择的是复合算法,能够简化理解为:java
从性能的角度看,一般关注三个方面,内存占用(footprint)、延时(latency)和吞吐量(throughput),大多数状况下调优会侧重于其中一个或者两个方面的目标,不多有状况能够兼顾三个不同的角度。固然,除了上面一般的三个方面,也可能须要考虑其余 GC 相关的场景,例如,OOM 也可能与不合理的 GC 相关参数有关;或者,应用启动速度方面的需求,GC 也会是个考虑的方面。 基本的调优思路能够总结为:程序员
经过分析肯定具体调整的参数或者软硬件配置。验证是否达到调优目标,若是达到目标,便可以考虑结束调优;不然,重复完成分析、调整、验证这 个过程。面试
新对象预留在年轻代 经过设置一个较大的年轻代预留新对象,设置合理的 Survivor 区而且提供 Survivor 区的使用率,能够将年轻对象保存在年轻代。算法
大对象进入年老代 使用参数-XX:PetenureSizeThreshold 设置大对象直接进入年老代的阈值spring
设置对象进入年老代的年龄 这个阈值的最大值能够经过参数-XX:MaxTenuringThreshold 来设置,默认值是 15编程
稳定的 Java 堆 得到一个稳定的堆大小的方法是使-Xms 和-Xmx 的大小一致,即最大堆和最小堆 (初始堆) 同样。缓存
增大吞吐量提高系统性能 –Xmx380m –Xms3800m:设置 Java 堆的最大值和初始值。通常状况下,为了不堆内存的频繁震荡,致使系统性能降低,咱们的作法是设置最大堆等于最小堆。假设这里把最小堆减小为最大堆的一半,即 1900m,那么 JVM 会尽量在 1900MB 堆空间中运行,若是这样,发生 GC 的可能性就会比较高; -Xss128k:减小线程栈的大小,这样可使剩余的系统内存支持更多的线程; -Xmn2g:设置年轻代区域大小为 2GB; –XX:+UseParallelGC:年轻代使用并行垃圾回收收集器。这是一个关注吞吐量的收集器,能够尽量地减小 GC 时间。 –XX:ParallelGC-Threads:设置用于垃圾回收的线程数,一般状况下,能够设置和 CPU 数量相等。但在 CPU 数量比较多的状况下,设置相对较小的数值也是合理的; –XX:+UseParallelOldGC:设置年老代使用并行回收收集器。安全
尝试使用大的内存分页 –XX:+LargePageSizeInBytes:设置大页的大小。 内存分页 (Paging) 是在使用 MMU 的基础上,提出的一种内存管理机制。它将虚拟地址和物理地址按固定大小(4K)分割成页 (page) 和页帧 (page frame),并保证页与页帧的大小相同。这种机制,从数据结构上,保证了访问内存的高效,并使 OS 能支持非连续性的内存分配。性能优化
使用非占有的垃圾回收器 为下降应用软件的垃圾回收时的停顿,首先考虑的是使用关注系统停顿的 CMS 回收器,其次,为了减小 Full GC 次数,应尽量将对象预留在年轻代。bash
gc()函数的做用只是提醒虚拟机:程序员但愿进行一次垃圾回收。可是它不能保证垃圾回收必定会进行,并且具体何时进行是取决于具体的虚拟机的,不一样的虚拟机有不一样的对策。
Parallel GC的Young区采用的是Mark-Copy算法,Old区采用的是Mark-Sweep-Compact来实现,Parallel执行,因此决定了Parallel GC在执行YGC、FGC时都会Stop-The-World,但完成GC的速度也会比较快。 CMS GC的Young区采用的也是Mark-Copy,Old区采用的是Concurrent Mark-Sweep,因此决定了CMS GC在对old区回收时形成的STW时间会更短,避免对应用产生太大的时延影响。 G1 GC采用了Garbage First算法,比较复杂,实现的好呢,理论上是会比CMS GC能够更高效,同时对应用的影响也很小。 ZGC、Azul Pauseless GC采用的算法很不同,尤为是Pauseless GC,其中的很重要的一个技巧是经过增长Read Barrier来更好的识别对GC而言最关键的references变化的状况。
当young gen中的eden区分配满的时候触发young gc,当年老代内存不足时,将执行Major GC,也叫 Full GC。
gc()函数的做用只是提醒虚拟机:程序员但愿进行一次垃圾回收。可是它不能保证垃圾回收必定会进行,并且具体何时进行是取决于具体的虚拟机的,不一样的虚拟机有不一样的对策。
不一样的引用类型,主要体现的是对象不一样的可达性(reachable)状态和对垃圾收集的影响。
所谓强引用("Strong" Reference),就是咱们最多见的普通对象引用,只要还有强引用指向一个对象,就能代表对象还“活着”,垃圾收集器不会碰这种对象。对于一个普通的对象,若是没有其余的引用关系,只要超过了引用的做用域或者显式地将相应(强)引用赋值为 null,就是能够被垃圾收集的了,固然具体回收时机仍是要看垃圾收集策略。
软引用(SoftReference),是一种相对强引用弱化一些的引用,可让对象豁免一些垃圾收集,只有当 JVM 认为内存不足时,才会去试图回收软引用指向的对象。JVM 会确保在抛出OutOfMemoryError 以前,清理软引用指向的对象。软引用一般用来实现内存敏感的缓存,若是还有空闲内存,就能够暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
SoftReference 在“弱引用WeakReference”中属于最强的引用。SoftReference 所指向的对象,当没有强引用指向它时,会在内存中停留一段的时间,垃圾回收器会根据 JVM 内存的使用状况(内存的紧缺程度)以及 SoftReference 的 get() 方法的调用状况来决定是否对其进行回收。
对于幻象引用(PhantomReference ),有时候也翻译成虚引用,你不能经过它访问对象。幻象引用仅仅是提供了一种确保对象被 finalize 之后,作某些事情的机制,好比,一般用来作所谓的 Post-Mortem 清理机制,如 Java 平台自身 Cleaner 机制等,也有人利用幻象引用监控对象的建立和销毁。
Object counter = new Object();
ReferenceQueue refQueue = new ReferenceQueue<>();
PhantomReference<Object> p = new PhantomReference<>(counter, refQueue);
counter = null;
System.gc();
try {
// Remove 是一个阻塞方法,能够指定 timeout,或者选择一直阻塞
Reference<Object> ref = refQueue.remove(1000L);
if (ref != null) {
// do something
}
} catch (InterruptedException e) {
// Handle it
}
复制代码
通常来讲,咱们把 Java 的类加载过程分为三个主要步骤:加载、连接、初始化。 首先是加载阶段(Loading),它是 Java 将字节码数据从不同的数据源读取到 JVM 中,并映射为 JVM 承认的数据结构(Class 对象),这里的数据源多是各类各样的形态,如 jar 文件、class 文件,甚至是网络数据源等;若是输入数据不是 ClassFile 的结构,则会抛出 ClassFormatError。加载阶段是用户参与的阶段,咱们能够自定义类加载器,去实现本身的类加载过程。
第二阶段是连接(Linking),这是核心的步骤,简单说是把原始的类定义信息平滑地转化入 JVM 运行的过程当中。这里可进一步细分为三个步骤:
最后是初始化阶段(initialization),这一步真正去执行类初始化的代码逻辑,包括静态字段赋值的动做,以及执行类定义中的静态初始化块内的逻辑,编译器在编译阶段就会把这部分逻辑整理好,父类型的初始化逻辑优先于当前类型的逻辑。
简单说就是当类加载器(Class-Loader)试图加载某个类型的时候,除非父加载器找不到相应类型,不然尽量将这个任务代理给当前加载器的父加载器去作。使用委派模型的目的是避免重复加载 Java 类型。
Java 提供了不少服务提供者接口(Service Provider Interface,SPI),容许第三方为这些接口提供实现。常见的 SPI 有 JDBC、JCE、JNDI、JAXP 和 JBI 等。这些 SPI 的接口由 Java 核心库来提供,而这些 SPI 的实现代码则是做为 Java 应用所依赖的 jar 包被包含进类路径(CLASSPATH)里。SPI接口中的代码常常须要加载具体的实现类。那么问题来了,SPI的接口是Java核心库的一部分,是由**启动类加载器(Bootstrap Classloader)来加载的;SPI的实现类是由系统类加载器(System ClassLoader)**来加载的。引导类加载器是没法找到 SPI 的实现类的,由于依照双亲委派模型,BootstrapClassloader没法委派AppClassLoader来加载类。而线程上下文类加载器破坏了“双亲委派模型”,能够在执行线程中抛弃双亲委派加载链模式,使程序能够逆向使用类加载器。
ServiceLoader 的加载代码:
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
复制代码
ContextClassLoader默认存放了AppClassLoader的引用,因为它是在运行时被放在了线程中,因此无论当前程序处于何处(BootstrapClassLoader或是ExtClassLoader等),在任何须要的时候均可以用Thread.currentThread().getContextClassLoader()取出应用程序类加载器来完成须要的操做。
自定义类加载器,常见的场景有:
从本地路径 load class 的例子:
public class CustomClassLoader extends ClassLoader {
@Override
public Class findClass(String name) throws ClassNotFoundException {
byte[] b = loadClassFromFile(name);
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassFromFile(String fileName) {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
fileName.replace('.', File.separatorChar) + ".class");
byte[] buffer;
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = 0;
try {
while ( (nextValue = inputStream.read()) != -1 ) {
byteStream.write(nextValue);
}
} catch (IOException e) {
e.printStackTrace();
}
buffer = byteStream.toByteArray();
return buffer;
}
}
复制代码
反射机制是 Java 语言提供的一种基础功能,赋予程序在运行时自省(introspect,官方用语)的能力。经过反射咱们能够直接操做类或者对象,好比获取某个对象的类定义,获取类声明的属性和方法,调用方法或者构造对象,甚至能够运行时修改类定义。 动态代理是一种方便运行时动态构建代理、动态处理代理方法调用的机制,不少场景都是利用相似机制作到的,好比用来包装 RPC 调用、面向切面的编程(AOP)。 实现动态代理的方式不少,好比 JDK 自身提供的动态代理,就是主要利用了上面提到的反射机制。还有其余的实现方式,好比利用传说中更高性能的字节码操做机制,相似 ASM、cglib(基于 ASM)、Javassist 等。
public class MyDynamicProxy {
public static void main (String[] args) {
HelloImpl hello = new HelloImpl();
MyInvocationHandler handler = new MyInvocationHandler(hello);
// 构造代码实例
Hello proxyHello = (Hello) Proxy.newProxyInstance(HelloImpl.class.getClassLoader(), HelloImpl.class.getInterfaces(), handler);
// 调用代理方法
proxyHello.sayHello();
}
}
interface Hello {
void sayHello();
}
class HelloImpl implements Hello {
@Override
public void sayHello() {
System.out.println("Hello World");
}
}
class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Invoking sayHello");
Object result = method.invoke(target, args);
return result;
}
}
复制代码
JDK动态代理只能对实现了接口的类生成代理,而不能针对类,CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。
JDK Proxy 的优点:
基于相似 cglib 框架的优点:
(1)当Bean实现接口时,Spring就会用JDK的动态代理 (2)当Bean没有实现接口时,Spring使用CGlib是实现 (3)能够强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)
(1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。惟一须要注意的是,CGLib不能对声明为final的方法进行代理,由于CGLib原理是动态生成被代理类的子类。可是JDK也在升级,开始引入不少字节码技术来实现部分动态代理的功能,因此在某些测试下不必定是CGLib更快。
ASM、Javassist、CGLib、Byte Budy。
你们若是想获取更多的面试资料与架构知识,你们能够加个人程序员交流群: 833145934,群内每晚都会有阿里技术大牛讲解的最新Java架构技术。并会录制录播视频分享在群公告中,做为给广大朋友的加群的福利——分布式(Dubbo、Redis、RabbitMQ、Netty、RPC、Zookeeper、高并发、高可用架构)/微服务(Spring Boot、Spring Cloud)/源码(Spring、Mybatis)/性能优化(JVM、TomCat、MySQL)【加群备注好消息领取最新面试资料】