$ kubectl get node WARN[0000] Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode to modify kube config permissions error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied
$ kubectl get pods NAME READY STATUS RESTARTS AGE hello-node-ccf4b9788-d8k9b 1/1 Running 0 15h
+
查看 Pod 中容器的应用程序日志。
1 2 3
$ kubectl logs hello-node-ccf4b9788-d8k9b I0130 19:26:57.751131 1 log.go:195] Started HTTP server on port 8080 I0130 19:26:57.751350 1 log.go:195] Started UDP server on port 8081
$ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 15h hello-node LoadBalancer 10.43.37.170 192.168.46.128 8080:32117/TCP 15h
+
使用 curl 发起请求:
1 2
$ curl http://localhost:8080 NOW: 2024-01-31 10:55:14.228709273 +0000 UTC m=+25932.159732511
+
再次查看 Pod 中容器的应用程序日志。
1 2 3 4
$ kubectl logs hello-node-ccf4b9788-d8k9b I0130 19:26:57.751131 1 log.go:195] Started HTTP server on port 8080 I0130 19:26:57.751350 1 log.go:195] Started UDP server on port 8081 I0130 19:32:21.074992 1 log.go:195] GET /
印象里每次安装 ElK 组件的体验都不是很好,或多或少都遇到过奇奇怪怪的问题。本文几乎完全按照官方文档:Getting started with the Elastic Stack and Docker Compose: Part 1 通过 docker compose 安装 elasticsearch、kibana、metricbeat、filebeat 和 logstash,但是移除了 ssl 相关的配置。你可以直接按照原文档进行安装,但是对照本文可以帮助你更快速地移除不需要的配置以及绕开可能踩到的坑。 此安装方式尽量使用环境变量代替编写配置文件,便于在备份和分享时将敏感信息留存在本地。本次安装时间为 2023-12-14,使用官方镜像,版本为 8.11.2。
+
本文详细介绍了 Java 中 synchronized 锁的机制、存储结构、优化措施以及升级过程,并通过 jol-core 演示 Mark Word 的变化来验证锁升级的多个 case。
本文介绍了 MySQL 中 skip-name-resolve 参数对连接的优化作用,随之而来的权限表仅可使用 IP 的限制,以及如何在无法提前确定 IP 的情况下使用 grant 命令搭配通配符 % 进行授权。
+
印象里每次安装 ElK 组件的体验都不是很好,或多或少都遇到过奇奇怪怪的问题。本文几乎完全按照官方文档:Getting started with the Elastic Stack and Docker Compose: Part 1 通过 docker compose 安装 elasticsearch、kibana、metricbeat、filebeat 和 logstash,但是移除了 ssl 相关的配置。你可以直接按照原文档进行安装,但是对照本文可以帮助你更快速地移除不需要的配置以及绕开可能踩到的坑。 此安装方式尽量使用环境变量代替编写配置文件,便于在备份和分享时将敏感信息留存在本地。本次安装时间为 2023-12-14,使用官方镜像,版本为 8.11.2。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 以二进制名称获取类加载的锁进行同步 synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 首先检查类是否已加载,根据该方法注释可知: // 如果当前类加载器已经被 Java 虚拟机记录为具有该二进制名称的类的加载器(initiating loader),Java 虚拟机可以直接返回 Class 对象。 Class<?> c = findLoadedClass(name); if (c == null) { longt0= System.nanoTime(); try { // 如果类还未加载,先委派给父·类加载器进行加载,如果父·类加载器为 null,则使用虚拟机内建的类加载器进行加载 if (parent != null) { // 递归调用 c = parent.loadClass(name, false); } else { // 递归调用的终结点 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader // 当父·类加载器长尝试加载但是失败,捕获异常但是什么都不做,因为接下来,当前类加载器需要自己也尝试加载。 }
if (c == null) { // If still not found, then invoke findClass in order // to find the class. longt1= System.nanoTime(); // 父·类加载器未找到类,当前类加载器自己找。 c = findClass(name);
// this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
/** * Registers the given class loader type as parallel capabale. * Returns {@code true} is successfully registered; {@code false} if * loader's super class is not registered. */ staticbooleanregister(Class<? extends ClassLoader> c) { synchronized (loaderTypes) { if (loaderTypes.contains(c.getSuperclass())) { // register the class loader as parallel capable // if and only if all of its super classes are. // Note: given current classloading sequence, if // the immediate super class is parallel capable, // all the super classes higher up must be too. // 当且仅当其所有超类都具有并行能力时,才将类加载器注册为具有并行能力。 // 注意:给定当前的类加载顺序(加载类时,Java 虚拟机总是先尝试加载其父类),如果直接超类具有并行能力,则所有更高的超类也必然具有并行能力。 loaderTypes.add(c); returntrue; } else { returnfalse; } } }
/** * Returns {@code true} if the given class loader type is * registered as parallel capable. */ staticbooleanisRegistered(Class<? extends ClassLoader> c) { synchronized (loaderTypes) { return loaderTypes.contains(c); } } }
privatesynchronized Loader getLoader(int index) { if (closed) { returnnull; } // Expand URL search path until the request can be satisfied // or the URL stack is empty. while (loaders.size() < index + 1) { // Pop the next URL from the URL stack // 如果 index 超过数组范围,需要从未打开的 URL 中取出一个,创建 Loader 并返回 URL url; synchronized (urls) { if (urls.empty()) { returnnull; } else { url = urls.pop(); } } // Skip this URL if it already has a Loader. (Loader // may be null in the case where URL has not been opened // but is referenced by a JAR index.) StringurlNoFragString= URLUtil.urlNoFragString(url); if (lmap.containsKey(urlNoFragString)) { continue; } // Otherwise, create a new Loader for the URL. Loader loader; try { // 根据 URL 创建 Loader loader = getLoader(url); // If the loader defines a local class path then add the // URLs to the list of URLs to be opened. URL[] urls = loader.getClassPath(); if (urls != null) { push(urls); } } catch (IOException e) { // Silently ignore for now... continue; } catch (SecurityException se) { // Always silently ignore. The context, if there is one, that // this URLClassPath was given during construction will never // have permission to access the URL. if (DEBUG) { System.err.println("Failed to access " + url + ", " + se ); } continue; } // Finally, add the Loader to the search path. validateLookupCache(loaders.size(), urlNoFragString); loaders.add(loader); lmap.put(urlNoFragString, loader); } if (DEBUG_LOOKUP_CACHE) { System.out.println("NOCACHE: Loading from : " + index ); } return loaders.get(index); }
// 检测是否有 LoadTimeWeaver,如果存在就准备编织。 if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(newLoadTimeWeaverAwareProcessor(beanFactory)); // Set a temporary ClassLoader for type matching. beanFactory.setTempClassLoader(newContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); }
// 手动注册默认的环境 beans。 if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment()); } if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties()); } if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment()); } }
方法 defineClass,顾名思义,就是定义类,将字节数据转换为 Class 实例。在 ClassLoader 以及其子类中有很多同名方法,方法内各种处理和包装,最终都是为了使用 name 和字节数据等参数,调用 native 方法获得一个 Class 实例。 以下是定义类时最终可能调用的 native 方法。
+
privatenative Class<?> defineClass0(String name, byte[] b, int off, int len, ProtectionDomain pd);
privatenative Class<?> defineClass1(String name, byte[] b, int off, int len, ProtectionDomain pd, String source);
privatenative Class<?> defineClass2(String name, java.nio.ByteBuffer b, int off, int len, ProtectionDomain pd, String source);
+
+
其方法参数有:
+
+
name,目标类的名称。
+
byte[] 或 ByteBuffer 类型的字节数据,off 和 len 只是为了定位传入的字节数组中关于目标类的字节数据,通常分别是 0 和字节数组的长度,毕竟专门构造一个包含无关数据的字节数组很无聊。
private Class<?> defineClass(String name, Resource res) throws IOException { longt0= System.nanoTime(); // 获取最后一个 . 的位置 inti= name.lastIndexOf('.'); // 返回资源的 CodeSourceURL URLurl= res.getCodeSourceURL(); if (i != -1) { // 截取包名 com.example Stringpkgname= name.substring(0, i); // Check if package already loaded. Manifestman= res.getManifest(); definePackageInternal(pkgname, man, url); } // Now read the class bytes and define the class // 先尝试以 ByteBuffer 的形式返回字节数据,如果资源的输入流不是在 ByteBuffer 之上实现的,则返回 null java.nio.ByteBufferbb= res.getByteBuffer(); if (bb != null) { // Use (direct) ByteBuffer: // 不常用 CodeSigner[] signers = res.getCodeSigners(); CodeSourcecs=newCodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); // 调用 java.security.SecureClassLoader#defineClass(java.lang.String, java.nio.ByteBuffer, java.security.CodeSource) return defineClass(name, bb, cs); } else { // 以字节数组的形式返回资源数据 byte[] b = res.getBytes(); // must read certificates AFTER reading bytes. // 必须再读取字节数据后读取证书,todo: CodeSigner[] signers = res.getCodeSigners(); // 根据 URL 和签名者创建 CodeSource CodeSourcecs=newCodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); // 调用 java.security.SecureClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.CodeSource) return defineClass(name, b, 0, b.length, cs); } }
+
+
Resource 类提供了 getBytes 方法,此方法以字节数组的形式返回字节数据。
+
publicbyte[] getBytes() throws IOException { byte[] b; // Get stream before content length so that a FileNotFoundException // can propagate upwards without being caught too early // 在获取内容长度之前获取流,以便 FileNotFoundException 可以向上传播而不会过早被捕获(todo: 不理解) // 获取缓存的 InputStream InputStreamin= cachedInputStream();
// This code has been uglified to protect against interrupts. // Even if a thread has been interrupted when loading resources, // the IO should not abort, so must carefully retry, failing only // if the retry leads to some other IO exception. // 该代码为了防止中断有点丑陋。即使线程在加载资源时被中断,IO 也不应该中止,因此必须小心重试,只有当重试导致其他 IO 异常时才会失败。 // 检测当前线程是否收到中断信号,收到的话则返回 true 且清除中断状态,重新变更为未中断状态。 booleanisInterrupted= Thread.interrupted(); int len; for (;;) { try { // 获取内容长度,顺利的话就跳出循环 len = getContentLength(); break; } catch (InterruptedIOException iioe) { // 如果获取内容长度时,线程被中断抛出了异常,捕获后清除中断状态 Thread.interrupted(); isInterrupted = true; } }
if (p instanceof FilePermission) { // if the permission has a separator char on the end, // it means the codebase is a directory, and we need // to add an additional permission to read recursively // 如果文件路径以文件分隔符结尾,表示目录,需要在末尾添加"-"改为递归读的权限 Stringpath= p.getName(); if (path.endsWith(File.separator)) { path += "-"; p = newFilePermission(path, SecurityConstants.FILE_READ_ACTION); } } elseif ((p == null) && (url.getProtocol().equals("file"))) { Stringpath= url.getFile().replace('/', File.separatorChar); path = ParseUtil.decode(path); if (path.endsWith(File.separator)) path += "-"; p = newFilePermission(path, SecurityConstants.FILE_READ_ACTION); } else { /** * Not loading from a 'file:' URL so we want to give the class * permission to connect to and accept from the remote host * after we've made sure the host is the correct one and is valid. */ URLlocUrl= url; if (urlConnection instanceof JarURLConnection) { locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); } Stringhost= locUrl.getHost(); if (host != null && (host.length() > 0)) p = newSocketPermission(host, SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION); }
// make sure the person that created this class loader // would have this permission
// Use byte[] if not a direct ByteBufer: if (!b.isDirect()) { if (b.hasArray()) { return defineClass(name, b.array(), b.position() + b.arrayOffset(), len, protectionDomain); } else { // no array, or read-only array byte[] tb = newbyte[len]; b.get(tb); // get bytes out of byte buffer. return defineClass(name, tb, 0, len, protectionDomain); } }
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd) { // 检查 name 为 null 或者有可能是有效的二进制名称 if (!checkName(name)) thrownewNoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias // relies on the fact that spoofing is impossible if a class has a name // of the form "java.*" // 如果 name 以 java. 开头,则抛出异常 if ((name != null) && name.startsWith("java.")) { thrownewSecurityException ("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); } if (pd == null) { // 如果未传入 ProtectionDomain,取默认的 ProtectionDomain pd = defaultDomain; }
// 检测是否有 LoadTimeWeaver,如果存在就准备编织。 if (beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) { beanFactory.addBeanPostProcessor(newLoadTimeWeaverAwareProcessor(beanFactory)); // Set a temporary ClassLoader for type matching. beanFactory.setTempClassLoader(newContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); }
// 手动注册默认的环境 beans。 if (!beanFactory.containsLocalBean(ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(ENVIRONMENT_BEAN_NAME, getEnvironment()); } if (!beanFactory.containsLocalBean(SYSTEM_PROPERTIES_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_PROPERTIES_BEAN_NAME, getEnvironment().getSystemProperties()); } if (!beanFactory.containsLocalBean(SYSTEM_ENVIRONMENT_BEAN_NAME)) { beanFactory.registerSingleton(SYSTEM_ENVIRONMENT_BEAN_NAME, getEnvironment().getSystemEnvironment()); } }
Heap def new generation total 9216K, used 2010K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 24% used [0x00000000fec00000, 0x00000000fedf68c8, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3288K, capacity 4496K, committed 4864K, reserved 1056768K class space used 348K, capacity 388K, committed 512K, reserved 1048576K
新生代中的空间占比 eden:from:to 在默认情况下是 8:1:1,与观察到的数据 8192K:1024K:1024K 一致。 新生代的空间 eden + from + to 为 10240K,符合 -Xmn10M 设置的大小。 total 显示为 9216K,即 eden + from 的大小,是因为 to 的空间不计算在内。新生代可用的空间只有 eden + from,to 空间只是在使用标记-复制算法进行垃圾回收时使用。 老年代的空间为 10240K。 目前仅 eden 中已用 2010K,约占 eden 空间的 24%。
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>(); list.add(newbyte[_7MB]); }
+
+
[GC (Allocation Failure) [DefNew: 2013K->721K(9216K), 0.0105099 secs] 2013K->721K(19456K), 0.0105455 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap def new generation total 9216K, used 8135K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 90% used [0x00000000fec00000, 0x00000000ff33d8c0, 0x00000000ff400000) from space 1024K, 70% used [0x00000000ff500000, 0x00000000ff5b45f0, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3354K, capacity 4496K, committed 4864K, reserved 1056768K class space used 360K, capacity 388K, committed 512K, reserved 1048576K
from 的已用空间的地址为 [0x00000000ff500000, 0x00000000ff5b45f0),空间大小为 738800 byte,约 721K,与 GC 后的新生代空间占用大小一致。在垃圾回收后,eden 区域存活的对象全部转移到了原 to 空间,from 和 to 空间的角色相互转换(从地址空间的信息可以看到此时 to 的地址指针比 from 的地址指针小)。 eden 的已用空间的地址为 [0x00000000fec00000, 0x00000000ff33d8c0),空间大小为 7592128 byte,约 7.24M,比 7M 大不少。此时 eden 区域除了 byte[] 对象外,还存储了其他对象,比如为了创建 List<byte[]> 对象而新加载的类对象。
+
eden 空间足够时不发生 GC
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>(); list.add(newbyte[_7MB]); list.add(newbyte[_512KB]); }
+
+
[GC (Allocation Failure) [DefNew: 2013K->721K(9216K), 0.0011172 secs] 2013K->721K(19456K), 0.0011443 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 8647K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 96% used [0x00000000fec00000, 0x00000000ff3bd8d0, 0x00000000ff400000) from space 1024K, 70% used [0x00000000ff500000, 0x00000000ff5b45f0, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3354K, capacity 4496K, committed 4864K, reserved 1056768K class space used 360K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [DefNew: 2013K->721K(9216K), 0.0013580 secs] 2013K->721K(19456K), 0.0013932 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 8565K->512K(9216K), 0.0046378 secs] 8565K->8396K(19456K), 0.0046540 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 1350K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 10% used [0x00000000fec00000, 0x00000000fecd1a20, 0x00000000ff400000) from space 1024K, 50% used [0x00000000ff400000, 0x00000000ff480048, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 7884K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 77% used [0x00000000ff600000, 0x00000000ffdb33a0, 0x00000000ffdb3400, 0x0000000100000000) Metaspace used 3354K, capacity 4496K, committed 4864K, reserved 1056768K class space used 360K, capacity 388K, committed 512K, reserved 1048576K
+
+
在第三次添加时,由于 eden 空间不足,因此又发生了第二次垃圾回收。 [DefNew: 8565K->512K(9216K), 0.0046378 secs],新生代的空间占用下降到了 512K,应该是在 from 中留下了第二次添加时的 512K。 在第二次添加完成后,eden[0x00000000fec00000, 0x00000000ff3bd8d0) 和 from[0x00000000ff500000, 0x00000000ff5b45f0) 占用的空间为 8116432 + 738800 = 8855232 约 8647.7K,略大于 8565K。很奇怪,第二次垃圾回收前,新生代的空间占用为什么有小幅度下降。 8565K->8396K(19456K), 0.0046540 secs,堆的占用空间并未发生明显下降。部分对象因为新生代空间不足,提前晋升到了老年代中。8396K - 512 K 剩余 7884K,全部晋升到老年代,符合 77% 的统计数据。 eden 中加入了第三次添加时的对象,大于 512K 不少。 此时 eden、from、tenured 中均有不好确认成分的空间占用,比如 from 中多了 56 字节。
+
新生代空间不足,大对象直接在老年代创建
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>(); list.add(newbyte[_8MB]); }
+
+
Heap def newgeneration total 9216K, used 2177K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee20730, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000) Metaspace used 3353K, capacity 4496K, committed 4864K, reserved 1056768K classspace used 360K, capacity 388K, committed 512K, reserved 1048576K
+
+
在 Eden 空间肯定不足而老年代空间足够的情况下,大对象会直接在老年代中创建,此时不会发生 GC。
+
内存不足 OOM
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>(); list.add(newbyte[_8MB]); list.add(newbyte[_8MB]); }
Heap def new generation total 9216K, used 1502K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 18% used [0x00000000fec00000, 0x00000000fed77a00, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 9063K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 88% used [0x00000000ff600000, 0x00000000ffed9c50, 0x00000000ffed9e00, 0x0000000100000000) Metaspace used 4787K, capacity 4884K, committed 4992K, reserved 1056768K class space used 522K, capacity 558K, committed 640K, reserved 1048576K
+
+
当新生代和老年代的空间均不足时,在尝试 GC 和 Full GC 后仍不能成功分配对象,就会发生 OutOfMemoryError。
+
线程中发生内存不足,不会影响其他线程
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>();
[GC (Allocation Failure) [DefNew: 2013K->721K(9216K), 0.0012274 secs][Tenured: 8192K->8912K(10240K), 0.0113036 secs] 10205K->8912K(19456K), [Metaspace: 3345K->3345K(1056768K)], 0.0125751 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [Full GC (Allocation Failure) [Tenured: 8912K->8895K(10240K), 0.0011880 secs] 8912K->8895K(19456K), [Metaspace: 3345K->3345K(1056768K)], 0.0012009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 246K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 3% used [0x00000000fec00000, 0x00000000fec3d890, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 8895K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 86% used [0x00000000ff600000, 0x00000000ffeafce0, 0x00000000ffeafe00, 0x0000000100000000) Metaspace used 3380K, capacity 4496K, committed 4864K, reserved 1056768K class space used 363K, capacity 388K, committed 512K, reserved 1048576K Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.moralok.jvm.gc.JvmGcTest.main(JvmGcTest.java:21)
[GC (Allocation Failure) [DefNew: 2013K->693K(9216K), 0.0015517 secs] 2013K->693K(19456K), 0.0015828 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 8885K->0K(9216K), 0.0048110 secs] 8885K->8885K(19456K), 0.0048264 secs] [Times: user=0.00 sys=0.02, real=0.00 secs] Heap def new generation total 9216K, used 410K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 5% used [0x00000000fec00000, 0x00000000fec66958, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 8885K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 86% used [0x00000000ff600000, 0x00000000ffead580, 0x00000000ffead600, 0x0000000100000000) Metaspace used 3321K, capacity 4496K, committed 4864K, reserved 1056768K class space used 354K, capacity 388K, committed 512K, reserved 1048576K
+
+
尽管最终大部分对象提前晋升到老年代,但是可以看到第二次 GC 前的新生代空间占用,可见数组分配时,所需空间刚好为 Eden 空间大小时,还是会在 eden 创建对象。
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 以二进制名称获取类加载的锁进行同步 synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 首先检查类是否已加载,根据该方法注释可知: // 如果当前类加载器已经被 Java 虚拟机记录为具有该二进制名称的类的加载器(initiating loader),Java 虚拟机可以直接返回 Class 对象。 Class<?> c = findLoadedClass(name); if (c == null) { longt0= System.nanoTime(); try { // 如果类还未加载,先委派给父·类加载器进行加载,如果父·类加载器为 null,则使用虚拟机内建的类加载器进行加载 if (parent != null) { // 递归调用 c = parent.loadClass(name, false); } else { // 递归调用的终结点 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader // 当父·类加载器长尝试加载但是失败,捕获异常但是什么都不做,因为接下来,当前类加载器需要自己也尝试加载。 }
if (c == null) { // If still not found, then invoke findClass in order // to find the class. longt1= System.nanoTime(); // 父·类加载器未找到类,当前类加载器自己找。 c = findClass(name);
// this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
/** * Registers the given class loader type as parallel capabale. * Returns {@code true} is successfully registered; {@code false} if * loader's super class is not registered. */ staticbooleanregister(Class<? extends ClassLoader> c) { synchronized (loaderTypes) { if (loaderTypes.contains(c.getSuperclass())) { // register the class loader as parallel capable // if and only if all of its super classes are. // Note: given current classloading sequence, if // the immediate super class is parallel capable, // all the super classes higher up must be too. // 当且仅当其所有超类都具有并行能力时,才将类加载器注册为具有并行能力。 // 注意:给定当前的类加载顺序(加载类时,Java 虚拟机总是先尝试加载其父类),如果直接超类具有并行能力,则所有更高的超类也必然具有并行能力。 loaderTypes.add(c); returntrue; } else { returnfalse; } } }
/** * Returns {@code true} if the given class loader type is * registered as parallel capable. */ staticbooleanisRegistered(Class<? extends ClassLoader> c) { synchronized (loaderTypes) { return loaderTypes.contains(c); } } }
privatesynchronized Loader getLoader(int index) { if (closed) { returnnull; } // Expand URL search path until the request can be satisfied // or the URL stack is empty. while (loaders.size() < index + 1) { // Pop the next URL from the URL stack // 如果 index 超过数组范围,需要从未打开的 URL 中取出一个,创建 Loader 并返回 URL url; synchronized (urls) { if (urls.empty()) { returnnull; } else { url = urls.pop(); } } // Skip this URL if it already has a Loader. (Loader // may be null in the case where URL has not been opened // but is referenced by a JAR index.) StringurlNoFragString= URLUtil.urlNoFragString(url); if (lmap.containsKey(urlNoFragString)) { continue; } // Otherwise, create a new Loader for the URL. Loader loader; try { // 根据 URL 创建 Loader loader = getLoader(url); // If the loader defines a local class path then add the // URLs to the list of URLs to be opened. URL[] urls = loader.getClassPath(); if (urls != null) { push(urls); } } catch (IOException e) { // Silently ignore for now... continue; } catch (SecurityException se) { // Always silently ignore. The context, if there is one, that // this URLClassPath was given during construction will never // have permission to access the URL. if (DEBUG) { System.err.println("Failed to access " + url + ", " + se ); } continue; } // Finally, add the Loader to the search path. validateLookupCache(loaders.size(), urlNoFragString); loaders.add(loader); lmap.put(urlNoFragString, loader); } if (DEBUG_LOOKUP_CACHE) { System.out.println("NOCACHE: Loading from : " + index ); } return loaders.get(index); }
-
-
URLClassPath#getLoader(java.net.URL)
根据指定的 URL 创建 Loader,不同类型的 URL 会返回不同具体实现的 Loader。
-
-
如果 URL 不是以 / 结尾,认为是 Jar 文件,则返回 JarLoader 类型,比如 file:/C:/Users/xxx/.jdks/corretto-1.8.0_342/jre/lib/rt.jar。
方法 defineClass,顾名思义,就是定义类,将字节数据转换为 Class 实例。在 ClassLoader 以及其子类中有很多同名方法,方法内各种处理和包装,最终都是为了使用 name 和字节数据等参数,调用 native 方法获得一个 Class 实例。 以下是定义类时最终可能调用的 native 方法。
-
privatenative Class<?> defineClass0(String name, byte[] b, int off, int len, ProtectionDomain pd);
privatenative Class<?> defineClass1(String name, byte[] b, int off, int len, ProtectionDomain pd, String source);
privatenative Class<?> defineClass2(String name, java.nio.ByteBuffer b, int off, int len, ProtectionDomain pd, String source);
-
-
其方法参数有:
-
-
name,目标类的名称。
-
byte[] 或 ByteBuffer 类型的字节数据,off 和 len 只是为了定位传入的字节数组中关于目标类的字节数据,通常分别是 0 和字节数组的长度,毕竟专门构造一个包含无关数据的字节数组很无聊。
private Class<?> defineClass(String name, Resource res) throws IOException { longt0= System.nanoTime(); // 获取最后一个 . 的位置 inti= name.lastIndexOf('.'); // 返回资源的 CodeSourceURL URLurl= res.getCodeSourceURL(); if (i != -1) { // 截取包名 com.example Stringpkgname= name.substring(0, i); // Check if package already loaded. Manifestman= res.getManifest(); definePackageInternal(pkgname, man, url); } // Now read the class bytes and define the class // 先尝试以 ByteBuffer 的形式返回字节数据,如果资源的输入流不是在 ByteBuffer 之上实现的,则返回 null java.nio.ByteBufferbb= res.getByteBuffer(); if (bb != null) { // Use (direct) ByteBuffer: // 不常用 CodeSigner[] signers = res.getCodeSigners(); CodeSourcecs=newCodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); // 调用 java.security.SecureClassLoader#defineClass(java.lang.String, java.nio.ByteBuffer, java.security.CodeSource) return defineClass(name, bb, cs); } else { // 以字节数组的形式返回资源数据 byte[] b = res.getBytes(); // must read certificates AFTER reading bytes. // 必须再读取字节数据后读取证书,todo: CodeSigner[] signers = res.getCodeSigners(); // 根据 URL 和签名者创建 CodeSource CodeSourcecs=newCodeSource(url, signers); sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0); // 调用 java.security.SecureClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.CodeSource) return defineClass(name, b, 0, b.length, cs); } }
-
-
Resource 类提供了 getBytes 方法,此方法以字节数组的形式返回字节数据。
-
publicbyte[] getBytes() throws IOException { byte[] b; // Get stream before content length so that a FileNotFoundException // can propagate upwards without being caught too early // 在获取内容长度之前获取流,以便 FileNotFoundException 可以向上传播而不会过早被捕获(todo: 不理解) // 获取缓存的 InputStream InputStreamin= cachedInputStream();
// This code has been uglified to protect against interrupts. // Even if a thread has been interrupted when loading resources, // the IO should not abort, so must carefully retry, failing only // if the retry leads to some other IO exception. // 该代码为了防止中断有点丑陋。即使线程在加载资源时被中断,IO 也不应该中止,因此必须小心重试,只有当重试导致其他 IO 异常时才会失败。 // 检测当前线程是否收到中断信号,收到的话则返回 true 且清除中断状态,重新变更为未中断状态。 booleanisInterrupted= Thread.interrupted(); int len; for (;;) { try { // 获取内容长度,顺利的话就跳出循环 len = getContentLength(); break; } catch (InterruptedIOException iioe) { // 如果获取内容长度时,线程被中断抛出了异常,捕获后清除中断状态 Thread.interrupted(); isInterrupted = true; } }
if (p instanceof FilePermission) { // if the permission has a separator char on the end, // it means the codebase is a directory, and we need // to add an additional permission to read recursively // 如果文件路径以文件分隔符结尾,表示目录,需要在末尾添加"-"改为递归读的权限 Stringpath= p.getName(); if (path.endsWith(File.separator)) { path += "-"; p = newFilePermission(path, SecurityConstants.FILE_READ_ACTION); } } elseif ((p == null) && (url.getProtocol().equals("file"))) { Stringpath= url.getFile().replace('/', File.separatorChar); path = ParseUtil.decode(path); if (path.endsWith(File.separator)) path += "-"; p = newFilePermission(path, SecurityConstants.FILE_READ_ACTION); } else { /** * Not loading from a 'file:' URL so we want to give the class * permission to connect to and accept from the remote host * after we've made sure the host is the correct one and is valid. */ URLlocUrl= url; if (urlConnection instanceof JarURLConnection) { locUrl = ((JarURLConnection)urlConnection).getJarFileURL(); } Stringhost= locUrl.getHost(); if (host != null && (host.length() > 0)) p = newSocketPermission(host, SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION); }
// make sure the person that created this class loader // would have this permission
// Use byte[] if not a direct ByteBufer: if (!b.isDirect()) { if (b.hasArray()) { return defineClass(name, b.array(), b.position() + b.arrayOffset(), len, protectionDomain); } else { // no array, or read-only array byte[] tb = newbyte[len]; b.get(tb); // get bytes out of byte buffer. return defineClass(name, tb, 0, len, protectionDomain); } }
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd) { // 检查 name 为 null 或者有可能是有效的二进制名称 if (!checkName(name)) thrownewNoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias // relies on the fact that spoofing is impossible if a class has a name // of the form "java.*" // 如果 name 以 java. 开头,则抛出异常 if ((name != null) && name.startsWith("java.")) { thrownewSecurityException ("Prohibited package name: " + name.substring(0, name.lastIndexOf('.'))); } if (pd == null) { // 如果未传入 ProtectionDomain,取默认的 ProtectionDomain pd = defaultDomain; }
Heap def new generation total 9216K, used 2010K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 24% used [0x00000000fec00000, 0x00000000fedf68c8, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3288K, capacity 4496K, committed 4864K, reserved 1056768K class space used 348K, capacity 388K, committed 512K, reserved 1048576K
新生代中的空间占比 eden:from:to 在默认情况下是 8:1:1,与观察到的数据 8192K:1024K:1024K 一致。 新生代的空间 eden + from + to 为 10240K,符合 -Xmn10M 设置的大小。 total 显示为 9216K,即 eden + from 的大小,是因为 to 的空间不计算在内。新生代可用的空间只有 eden + from,to 空间只是在使用标记-复制算法进行垃圾回收时使用。 老年代的空间为 10240K。 目前仅 eden 中已用 2010K,约占 eden 空间的 24%。
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>(); list.add(newbyte[_7MB]); }
-
-
[GC (Allocation Failure) [DefNew: 2013K->721K(9216K), 0.0105099 secs] 2013K->721K(19456K), 0.0105455 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap def new generation total 9216K, used 8135K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 90% used [0x00000000fec00000, 0x00000000ff33d8c0, 0x00000000ff400000) from space 1024K, 70% used [0x00000000ff500000, 0x00000000ff5b45f0, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3354K, capacity 4496K, committed 4864K, reserved 1056768K class space used 360K, capacity 388K, committed 512K, reserved 1048576K
from 的已用空间的地址为 [0x00000000ff500000, 0x00000000ff5b45f0),空间大小为 738800 byte,约 721K,与 GC 后的新生代空间占用大小一致。在垃圾回收后,eden 区域存活的对象全部转移到了原 to 空间,from 和 to 空间的角色相互转换(从地址空间的信息可以看到此时 to 的地址指针比 from 的地址指针小)。 eden 的已用空间的地址为 [0x00000000fec00000, 0x00000000ff33d8c0),空间大小为 7592128 byte,约 7.24M,比 7M 大不少。此时 eden 区域除了 byte[] 对象外,还存储了其他对象,比如为了创建 List<byte[]> 对象而新加载的类对象。
-
eden 空间足够时不发生 GC
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>(); list.add(newbyte[_7MB]); list.add(newbyte[_512KB]); }
-
-
[GC (Allocation Failure) [DefNew: 2013K->721K(9216K), 0.0011172 secs] 2013K->721K(19456K), 0.0011443 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 8647K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 96% used [0x00000000fec00000, 0x00000000ff3bd8d0, 0x00000000ff400000) from space 1024K, 70% used [0x00000000ff500000, 0x00000000ff5b45f0, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3354K, capacity 4496K, committed 4864K, reserved 1056768K class space used 360K, capacity 388K, committed 512K, reserved 1048576K
[GC (Allocation Failure) [DefNew: 2013K->721K(9216K), 0.0013580 secs] 2013K->721K(19456K), 0.0013932 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 8565K->512K(9216K), 0.0046378 secs] 8565K->8396K(19456K), 0.0046540 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 1350K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 10% used [0x00000000fec00000, 0x00000000fecd1a20, 0x00000000ff400000) from space 1024K, 50% used [0x00000000ff400000, 0x00000000ff480048, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 7884K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 77% used [0x00000000ff600000, 0x00000000ffdb33a0, 0x00000000ffdb3400, 0x0000000100000000) Metaspace used 3354K, capacity 4496K, committed 4864K, reserved 1056768K class space used 360K, capacity 388K, committed 512K, reserved 1048576K
-
-
在第三次添加时,由于 eden 空间不足,因此又发生了第二次垃圾回收。 [DefNew: 8565K->512K(9216K), 0.0046378 secs],新生代的空间占用下降到了 512K,应该是在 from 中留下了第二次添加时的 512K。 在第二次添加完成后,eden[0x00000000fec00000, 0x00000000ff3bd8d0) 和 from[0x00000000ff500000, 0x00000000ff5b45f0) 占用的空间为 8116432 + 738800 = 8855232 约 8647.7K,略大于 8565K。很奇怪,第二次垃圾回收前,新生代的空间占用为什么有小幅度下降。 8565K->8396K(19456K), 0.0046540 secs,堆的占用空间并未发生明显下降。部分对象因为新生代空间不足,提前晋升到了老年代中。8396K - 512 K 剩余 7884K,全部晋升到老年代,符合 77% 的统计数据。 eden 中加入了第三次添加时的对象,大于 512K 不少。 此时 eden、from、tenured 中均有不好确认成分的空间占用,比如 from 中多了 56 字节。
-
新生代空间不足,大对象直接在老年代创建
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>(); list.add(newbyte[_8MB]); }
-
-
Heap def newgeneration total 9216K, used 2177K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 26% used [0x00000000fec00000, 0x00000000fee20730, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000) Metaspace used 3353K, capacity 4496K, committed 4864K, reserved 1056768K classspace used 360K, capacity 388K, committed 512K, reserved 1048576K
-
-
在 Eden 空间肯定不足而老年代空间足够的情况下,大对象会直接在老年代中创建,此时不会发生 GC。
-
内存不足 OOM
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>(); list.add(newbyte[_8MB]); list.add(newbyte[_8MB]); }
Heap def new generation total 9216K, used 1502K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 18% used [0x00000000fec00000, 0x00000000fed77a00, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 9063K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 88% used [0x00000000ff600000, 0x00000000ffed9c50, 0x00000000ffed9e00, 0x0000000100000000) Metaspace used 4787K, capacity 4884K, committed 4992K, reserved 1056768K class space used 522K, capacity 558K, committed 640K, reserved 1048576K
-
-
当新生代和老年代的空间均不足时,在尝试 GC 和 Full GC 后仍不能成功分配对象,就会发生 OutOfMemoryError。
-
线程中发生内存不足,不会影响其他线程
publicstaticvoidmain(String[] args) { List<byte[]> list = newArrayList<>();
[GC (Allocation Failure) [DefNew: 2013K->721K(9216K), 0.0012274 secs][Tenured: 8192K->8912K(10240K), 0.0113036 secs] 10205K->8912K(19456K), [Metaspace: 3345K->3345K(1056768K)], 0.0125751 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [Full GC (Allocation Failure) [Tenured: 8912K->8895K(10240K), 0.0011880 secs] 8912K->8895K(19456K), [Metaspace: 3345K->3345K(1056768K)], 0.0012009 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 246K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 3% used [0x00000000fec00000, 0x00000000fec3d890, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 8895K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 86% used [0x00000000ff600000, 0x00000000ffeafce0, 0x00000000ffeafe00, 0x0000000100000000) Metaspace used 3380K, capacity 4496K, committed 4864K, reserved 1056768K class space used 363K, capacity 388K, committed 512K, reserved 1048576K Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.moralok.jvm.gc.JvmGcTest.main(JvmGcTest.java:21)
[GC (Allocation Failure) [DefNew: 2013K->693K(9216K), 0.0015517 secs] 2013K->693K(19456K), 0.0015828 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 8885K->0K(9216K), 0.0048110 secs] 8885K->8885K(19456K), 0.0048264 secs] [Times: user=0.00 sys=0.02, real=0.00 secs] Heap def new generation total 9216K, used 410K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 5% used [0x00000000fec00000, 0x00000000fec66958, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 8885K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 86% used [0x00000000ff600000, 0x00000000ffead580, 0x00000000ffead600, 0x0000000100000000) Metaspace used 3321K, capacity 4496K, committed 4864K, reserved 1056768K class space used 354K, capacity 388K, committed 512K, reserved 1048576K
-
-
尽管最终大部分对象提前晋升到老年代,但是可以看到第二次 GC 前的新生代空间占用,可见数组分配时,所需空间刚好为 Eden 空间大小时,还是会在 eden 创建对象。
CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'XXXXXXXX' WITH MAX_USER_CONNECTIONS 3; GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';
CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'XXXXXXXX' WITH MAX_USER_CONNECTIONS 3; GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';
publicstatic <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { thrownewIllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { thrownewIllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { thrownewIllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); }
publicstatic <T> ExtensionLoader<T> getExtensionLoader(Class<T> type) { if (type == null) { thrownewIllegalArgumentException("Extension type == null"); } if (!type.isInterface()) { thrownewIllegalArgumentException("Extension type (" + type + ") is not an interface!"); } if (!withExtensionAnnotation(type)) { thrownewIllegalArgumentException("Extension type (" + type + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); }
这时候,并不区分要导入的目标的 Class 有什么特别之处,Import 注解的语义,此时宽泛地说就是:“将 value 中的类导入”。但是显而易见,这样的方式不够灵活,因此才有了另外两种更有灵活性的导入方式:ImportSelector 和 ImportBeanDefinitionRegistrar,Spring 最终不会真的注册这两种类,而是注册它们“介绍”的类,相当于把确定导入什么类的工作委托给它们。
这时候,并不区分要导入的目标的 Class 有什么特别之处,Import 注解的语义,此时宽泛地说就是:“将 value 中的类导入”。但是显而易见,这样的方式不够灵活,因此才有了另外两种更有灵活性的导入方式:ImportSelector 和 ImportBeanDefinitionRegistrar,Spring 最终不会真的注册这两种类,而是注册它们“介绍”的类,相当于把确定导入什么类的工作委托给它们。
-]]>
-
- java
- spring
-
-
-
- 当 MySQL 以 skip-name-resolve 模式启动时如何使用 grant 命令
- /2023/12/13/how-to-grant-when-MySQL-started-with-skip-name-resolve-mode/
- 本文介绍了 MySQL 中 skip-name-resolve 参数对连接的优化作用,随之而来的权限表仅可使用 IP 的限制,以及如何在无法提前确定 IP 的情况下使用 grant 命令搭配通配符 % 进行授权。
-
+
// Track which attribute values have already been replaced so that we can short // circuit the search algorithms. Set<String> valuesAlreadyReplaced = newHashSet<>(); // 获取注解的属性方法(SpringBootApplication) for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) { StringattributeName= attributeMethod.getName(); // 获取被覆盖的别名 StringattributeOverrideName= AnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType);
// Explicit annotation attribute override declared via @AliasFor if (attributeOverrideName != null) { // 被覆盖的属性的值是否已经被替换 if (valuesAlreadyReplaced.contains(attributeOverrideName)) { continue; }
// Track which attribute values have already been replaced so that we can short // circuit the search algorithms. Set<String> valuesAlreadyReplaced = newHashSet<>();
// Something to validate or replace with an alias? if (valuePresent || aliasPresent) { // 如果属性已有值且别名属性也有值,校验是否相等 if (valuePresent && aliasPresent) { // Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals(). if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) { StringelementAsString= (annotatedElement != null ? annotatedElement.toString() : "unknown element"); thrownewAnnotationConfigurationException(String.format( "In AnnotationAttributes for annotation [%s] declared on %s, " + "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " + "but only one is permitted.", attributes.displayName, elementAsString, attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue))); } } elseif (aliasPresent) { // 复制别名属性的值给属性 attributes.put(attributeName, adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap)); valuesAlreadyReplaced.add(attributeName); } else { // 复制属性的值给别名属性 attributes.put(aliasedAttributeName, adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap)); valuesAlreadyReplaced.add(aliasedAttributeName); } } } } // 校验完毕 attributes.validated = true; }
// 将 `value` 从 `DefaultValueHolder` 替换为原始的 `value` for (String attributeName : attributes.keySet()) { if (valuesAlreadyReplaced.contains(attributeName)) { continue; } Objectvalue= attributes.get(attributeName); if (value instanceof DefaultValueHolder) { value = ((DefaultValueHolder) value).defaultValue; attributes.put(attributeName, adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap)); } } }
因为缺乏对具体的 MySQL 代码实现的了解,所以没有办法对上述过程进行深入和准确的描述。在官方的表述中,根据 IP 查询主机名和根据主机名查询 IP 似乎都存在;但在查阅到的资料中,部分文章提到的一般是“反向解析”,也就是根据 IP 查询主机名;但是更多的文章对这部分比较含糊其辞。
-
-
怎么使用 grant 命令
如果你并不考虑对特定用户多加限制,那么使用通配符 % 即可,它将允许从任意 IP 连接 MySQL。
-
CREATEUSER'exporter'@'%' IDENTIFIED BY'XXXXXXXX'WITH MAX_USER_CONNECTIONS 3; GRANT PROCESS, REPLICATION CLIENT, SELECTON*.*TO'exporter'@'%';
-
-
但是在我的情况中,新建的用户是给 mysqld-exporter 获取 MySQL 的监控数据的,显然限制该用户的 IP、连接数以及权限很有必要。可是如果使用的是 Docker 或者其他无法提前确定 IP 的环境怎么办呢?还是可以使用通配符 % 限制为仅某一 IP 范围可以访问。
-
CREATEUSER'exporter'@'172.19.%.%' IDENTIFIED BY'XXXXXXXX'WITH MAX_USER_CONNECTIONS 3; GRANT PROCESS, REPLICATION CLIENT, SELECTON*.*TO'exporter'@'172.19.%.%';
]]>
+]]>
- mysql
+ java
+ spring
+ spring boot
@@ -4677,6 +4757,41 @@
kibana
+
+ 当 MySQL 以 skip-name-resolve 模式启动时如何使用 grant 命令
+ /2023/12/13/how-to-grant-when-MySQL-started-with-skip-name-resolve-mode/
+ 本文介绍了 MySQL 中 skip-name-resolve 参数对连接的优化作用,随之而来的权限表仅可使用 IP 的限制,以及如何在无法提前确定 IP 的情况下使用 grant 命令搭配通配符 % 进行授权。
+
+
+
背景介绍
在 MySQL 新建一个用户 exporter 给 mysqld-exporter 使用,然后通过 grant 命令授予该用户部分权限。执行命令如下:
+
CREATEUSER'exporter'@'localhost' IDENTIFIED BY'XXXXXXXX'WITH MAX_USER_CONNECTIONS 3; GRANT PROCESS, REPLICATION CLIENT, SELECTON*.*TO'exporter'@'localhost';
+
+
在执行时提示“这次 grant 需要关闭 skip-name-resolve 参数然后重启 MySQL 才能生效”。
+
0 row(s) affected, 1 warning(s): 1285 MySQL is started in --skip-name-resolve mode; you must restart it without this switch for this grant to work
// Track which attribute values have already been replaced so that we can short // circuit the search algorithms. Set<String> valuesAlreadyReplaced = newHashSet<>(); // 获取注解的属性方法(SpringBootApplication) for (Method attributeMethod : AnnotationUtils.getAttributeMethods(annotation.annotationType())) { StringattributeName= attributeMethod.getName(); // 获取被覆盖的别名 StringattributeOverrideName= AnnotationUtils.getAttributeOverrideName(attributeMethod, targetAnnotationType);
// Explicit annotation attribute override declared via @AliasFor if (attributeOverrideName != null) { // 被覆盖的属性的值是否已经被替换 if (valuesAlreadyReplaced.contains(attributeOverrideName)) { continue; }
// Track which attribute values have already been replaced so that we can short // circuit the search algorithms. Set<String> valuesAlreadyReplaced = newHashSet<>();
// Something to validate or replace with an alias? if (valuePresent || aliasPresent) { // 如果属性已有值且别名属性也有值,校验是否相等 if (valuePresent && aliasPresent) { // Since annotation attributes can be arrays, we must use ObjectUtils.nullSafeEquals(). if (!ObjectUtils.nullSafeEquals(value, aliasedValue)) { StringelementAsString= (annotatedElement != null ? annotatedElement.toString() : "unknown element"); thrownewAnnotationConfigurationException(String.format( "In AnnotationAttributes for annotation [%s] declared on %s, " + "attribute '%s' and its alias '%s' are declared with values of [%s] and [%s], " + "but only one is permitted.", attributes.displayName, elementAsString, attributeName, aliasedAttributeName, ObjectUtils.nullSafeToString(value), ObjectUtils.nullSafeToString(aliasedValue))); } } elseif (aliasPresent) { // 复制别名属性的值给属性 attributes.put(attributeName, adaptValue(annotatedElement, aliasedValue, classValuesAsString, nestedAnnotationsAsMap)); valuesAlreadyReplaced.add(attributeName); } else { // 复制属性的值给别名属性 attributes.put(aliasedAttributeName, adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap)); valuesAlreadyReplaced.add(aliasedAttributeName); } } } } // 校验完毕 attributes.validated = true; }
// 将 `value` 从 `DefaultValueHolder` 替换为原始的 `value` for (String attributeName : attributes.keySet()) { if (valuesAlreadyReplaced.contains(attributeName)) { continue; } Objectvalue= attributes.get(attributeName); if (value instanceof DefaultValueHolder) { value = ((DefaultValueHolder) value).defaultValue; attributes.put(attributeName, adaptValue(annotatedElement, value, classValuesAsString, nestedAnnotationsAsMap)); } } }
$ kubectl get node WARN[0000] Unable to read /etc/rancher/k3s/k3s.yaml, please start server with --write-kubeconfig-mode to modify kube config permissions error: error loading config file "/etc/rancher/k3s/k3s.yaml": open /etc/rancher/k3s/k3s.yaml: permission denied
$ kubectl get pods NAME READY STATUS RESTARTS AGE hello-node-ccf4b9788-d8k9b 1/1 Running 0 15h
+
查看 Pod 中容器的应用程序日志。
$ kubectl logs hello-node-ccf4b9788-d8k9b I0130 19:26:57.751131 1 log.go:195] Started HTTP server on port 8080 I0130 19:26:57.751350 1 log.go:195] Started UDP server on port 8081
$ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 15h hello-node LoadBalancer 10.43.37.170 192.168.46.128 8080:32117/TCP 15h
+
使用 curl 发起请求:
$ curl http://localhost:8080 NOW: 2024-01-31 10:55:14.228709273 +0000 UTC m=+25932.159732511
+
再次查看 Pod 中容器的应用程序日志。
$ kubectl logs hello-node-ccf4b9788-d8k9b I0130 19:26:57.751131 1 log.go:195] Started HTTP server on port 8080 I0130 19:26:57.751350 1 log.go:195] Started UDP server on port 8081 I0130 19:32:21.074992 1 log.go:195] GET /